package uk.co.senab.photoview;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.drawable.BitmapDrawable;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.ImageView;
import java.util.Stack;
public class ColourImageView extends ImageView {
public void clearPoints() {
undopoints.clear();
redopoints.clear();
}
public enum Model {
FILLCOLOR,
FILLGRADUALCOLOR,
PICKCOLOR,
DRAW_LINE,
}
private Bitmap mBitmap;
/**
* ???????
*/
private int mBorderColor = -1;
private Stack<Point> mStacks = new Stack<Point>();
private int mColor = 0xFF00BCD4;
private int stacksize = 10;
private Stack<Bitmap> bmstackundo;
private Stack<Bitmap> bmstackredo;
private Stack<Point> undopoints;
private Stack<Point> redopoints;
private OnRedoUndoListener onRedoUndoListener;
private AsyncTask loaderTask;
private Model model = Model.FILLCOLOR;
private OnColorPickListener onColorPickListener;
private OnDrawLineListener onDrawLineListener;
public ColourImageView(Context context, AttributeSet attrs) {
super(context, attrs);
initStack();
// TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ColourImageView);
// mBorderColor = ta.getColor(R.styleable.ColourImageView_border_color, -1);
// hasBorderColor = (mBorderColor != -1);
//
// L.e("hasBorderColor = " + hasBorderColor + " , mBorderColor = " + mBorderColor);
//
// ta.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public Bitmap createBitMap() {
BitmapDrawable drawable = (BitmapDrawable) getDrawable();
Bitmap bm = drawable.getBitmap();
return bm.copy(bm.getConfig(), true);
}
public void createBitMap(Bitmap bt) {
mBitmap = bt.copy(bt.getConfig(), true);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//???
//fillColorToSameArea(x, y);
}
return super.onTouchEvent(event);
}
public void pickColor(int x, int y) {
int color = 0;
boolean status;
try {
if (!isBorderColor(mBitmap.getPixel(x, y)) && mBitmap.getPixel(x, y) != Color.TRANSPARENT) {
color = mBitmap.getPixel(x, y);
status = true;
} else {
status = false;
}
} catch (Exception e) {
status = false;
}
if (onColorPickListener != null) {
onColorPickListener.onColorPick(status, color);
}
}
/**
* ???x,y?????????????????
*
* @param x
* @param y
*/
public void fillColorToSameArea(int x, int y) {
//there x,y may be many problems such x<0 x>getwidth catch all the exceptions and this touch do nothing!
try {
//if FILLGRADUALCOLOR model then check is white area if not return
// if(model == Model.FILLGRADUALCOLOR && mBitmap.getPixel(x, y) != Color.WHITE){
// return;
// }
//if click pixel is transparent or border or same color do nothing
if (mBitmap.getPixel(x, y) != mColor && !isBorderColor(mBitmap.getPixel(x, y)) && mBitmap.getPixel(x, y) != Color.TRANSPARENT) {
ProgressLoading.show(getContext(), true);
ProgressLoading.setOndismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialogInterface) {
if (loaderTask != null) {
//destroy asyntask
loaderTask.cancel(true);
}
}
});
loaderTask = new AsyncTask() {
Bitmap bm = mBitmap;
@Override
protected Object doInBackground(Object[] objects) {
//save bm to undo stack
try {
pushUndoStack(bm.copy(bm.getConfig(), true));
int pixel = bm.getPixel((int) objects[0], (int) objects[1]);
int w = bm.getWidth();
int h = bm.getHeight();
//?????bitmap?????????
int[] pixels = new int[w * h];
bm.getPixels(pixels, 0, w, 0, 0, w, h);
//???
fillColor(pixels, w, h, pixel, mColor, (int) objects[0], (int) objects[1]);
//????????bitmap
bm.setPixels(pixels, 0, w, 0, 0, w, h);
return true;
} catch (Exception e) {
bmstackundo.pop();
return false;
}
}
@Override
protected void onPostExecute(Object o) {
super.onPostExecute(o);
ProgressLoading.DismissDialog();
setImageDrawable(new BitmapDrawable(getResources(), bm));
if (onRedoUndoListener != null) {
onRedoUndoListener.onRedoUndo(bmstackundo.size(), bmstackredo.size());
}
}
}.execute(x, y);
}
} catch (Exception e) {
//do nothing.
}
}
private boolean isBorderColor(int color) {
if (Color.red(color) < 0x10 && Color.green(color) < 0x10 && Color.blue(color) < 0x10) {
return true;
} else {
return false;
}
}
private void pushUndoStack(Bitmap bm) {
bmstackundo.push(bm);
bmstackredo.clear();
}
/**
* @param pixels 像素数组
* @param w 宽度
* @param h 高度
* @param pixel 当前点的颜色
* @param newColor 填充色
* @param i 横坐标
* @param j 纵坐标
*/
private void fillColor(int[] pixels, int w, int h, int pixel, int newColor, int i, int j) {
int orginalX = i;
int orginalY = j;
mStacks.clear();
mStacks.push(new Point(i, j));
while (!mStacks.isEmpty()) {
if (loaderTask.isCancelled()) {
break;
}
Point seed = mStacks.pop();
//L.e("seed = " + seed.x + " , seed = " + seed.y);
int count = fillLineLeft(pixels, pixel, w, h, newColor, seed.x, seed.y, orginalX, orginalY);
int left = seed.x - count + 1;
count = fillLineRight(pixels, pixel, w, h, newColor, seed.x + 1, seed.y, orginalX, orginalY);
int right = seed.x + count;
if (seed.y - 1 >= 0)
findSeedInNewLine(pixels, pixel, w, h, seed.y - 1, left, right);
if (seed.y + 1 < h)
findSeedInNewLine(pixels, pixel, w, h, seed.y + 1, left, right);
}
}
/**
* @param pixels
* @param pixel
* @param w
* @param h
* @param i
* @param left
* @param right
*/
private void findSeedInNewLine(int[] pixels, int pixel, int w, int h, int i, int left, int right) {
int begin = i * w + left;
int end = i * w + right;
boolean hasSeed = false;
int rx = -1, ry = -1;
ry = i;
while (end >= begin) {
if (needFillPixel(pixels, pixel, end)) {
if (!hasSeed) {
rx = end % w;
mStacks.push(new Point(rx, ry));
hasSeed = true;
}
} else {
hasSeed = false;
}
end--;
}
}
/**
* ???????????????????????
*
* @return
*/
private int fillLineLeft(int[] pixels, int pixel, int w, int h, int newColor, int x, int y, int orginalX, int orginalY) {
int count = 0;
while (x >= 0) {
//?????????
int index = y * w + x;
if (needFillPixel(pixels, pixel, index)) {
if (model == Model.FILLCOLOR) {
pixels[index] = newColor;
} else if (model == Model.FILLGRADUALCOLOR) {
float[] colorHSV = new float[]{0f, 0f, 1f};
Color.colorToHSV(newColor, colorHSV);
float dis = (float) Math.sqrt((x - orginalX) * (x - orginalX) + (y - orginalY) * (y - orginalY));
colorHSV[1] = (colorHSV[1] - dis * 0.006) < 0.2 ? 0.2f : (colorHSV[1] - dis * 0.006f);
pixels[index] = Color.HSVToColor(colorHSV);
}
count++;
x--;
} else {
break;
}
}
return count;
}
private int fillLineRight(int[] pixels, int pixel, int w, int h, int newColor, int x, int y, int orginalX, int orginalY) {
int count = 0;
while (x < w) {
//???????
int index = y * w + x;
if (needFillPixel(pixels, pixel, index)) {
if (model == Model.FILLCOLOR) {
pixels[index] = newColor;
} else if (model == Model.FILLGRADUALCOLOR) {
float[] colorHSV = new float[]{0f, 0f, 1f};
Color.colorToHSV(newColor, colorHSV);
float dis = (float) Math.sqrt((x - orginalX) * (x - orginalX) + (y - orginalY) * (y - orginalY));
colorHSV[1] = (colorHSV[1] - dis * 0.006) < 0.2 ? 0.2f : (colorHSV[1] - dis * 0.006f);
pixels[index] = Color.HSVToColor(colorHSV);
}
count++;
x++;
} else {
break;
}
}
return count;
}
private boolean needFillPixel(int[] pixels, int pixel, int index) {
if (model == Model.FILLGRADUALCOLOR) {
return pixels[index] == pixel;
} else
return pixels[index] == pixel;
}
public void setImageBT(Bitmap bm) {
pushUndoStack(mBitmap.copy(mBitmap.getConfig(), true));
mBitmap = bm.copy(bm.getConfig(), true);
setImageDrawable(new BitmapDrawable(getResources(), mBitmap));
if (onRedoUndoListener != null) {
onRedoUndoListener.onRedoUndo(bmstackundo.size(), bmstackredo.size());
}
}
public void update() {
setMeasuredDimension(getMeasuredWidth(),
getDrawable().getIntrinsicHeight() * getMeasuredWidth() / getDrawable().getIntrinsicWidth());
}
public void setColor(int color) {
mColor = color;
}
/**
* @return true: has element can undo;
*/
public boolean undo() {
try {
if (bmstackundo.peek() != null) {
bmstackredo.push(mBitmap.copy(mBitmap.getConfig(), true));
mBitmap = bmstackundo.pop();
setImageDrawable(new BitmapDrawable(getResources(), mBitmap));
if (onRedoUndoListener != null) {
onRedoUndoListener.onRedoUndo(bmstackundo.size(), bmstackredo.size());
}
if (undopoints != null && !undopoints.empty()) {
redopoints.push(undopoints.pop());
}
return !bmstackundo.empty();
}
} catch (Exception e) {
}
return false;
}
/**
* @return true: has element ,can redo;
*/
public boolean redo() {
try {
if (bmstackredo.peek() != null) {
bmstackundo.push(mBitmap.copy(mBitmap.getConfig(), true));
mBitmap = bmstackredo.pop();
setImageDrawable(new BitmapDrawable(getResources(), mBitmap));
if (onRedoUndoListener != null) {
onRedoUndoListener.onRedoUndo(bmstackundo.size(), bmstackredo.size());
}
if (redopoints != null && !redopoints.empty()) {
undopoints.push(redopoints.pop());
}
return !bmstackredo.empty();
}
} catch (Exception e) {
}
return false;
}
public OnRedoUndoListener getOnRedoUndoListener() {
return onRedoUndoListener;
}
public void setOnRedoUndoListener(OnRedoUndoListener onRedoUndoListener) {
this.onRedoUndoListener = onRedoUndoListener;
}
//clear stack and the current image
public void clearStack() {
bmstackredo.clear();
bmstackundo.clear();
onRedoUndoListener.onRedoUndo(bmstackundo.size(), bmstackredo.size());
mBitmap = null;
}
public Model getModel() {
return model;
}
public void setModel(Model model) {
this.model = model;
}
public void setOnColorPickListener(OnColorPickListener onColorPickListener) {
this.onColorPickListener = onColorPickListener;
}
public interface OnRedoUndoListener {
void onRedoUndo(int undoSize, int redoSize);
}
public Bitmap getmBitmap() {
return mBitmap;
}
private void initStack() {
SharedPreferences sharedPreferences = getContext().getSharedPreferences("Cache", Context.MODE_PRIVATE);
stacksize = sharedPreferences.getInt("stack_max_size", 10);
bmstackundo = new SizedStack<>(stacksize);
bmstackredo = new SizedStack<>(stacksize);
undopoints = new Stack<>();
redopoints = new Stack<>();
}
public interface OnColorPickListener {
void onColorPick(boolean status, int color);
}
/**
* call only for activity destroyed
*/
public void onRecycleBitmaps() {
while (bmstackundo != null && !bmstackundo.empty()) {
bmstackundo.pop().recycle();
bmstackundo.clear();
}
while (bmstackredo != null && !bmstackredo.empty()) {
bmstackredo.pop().recycle();
bmstackredo.clear();
}
if (mBitmap != null) {
mBitmap.recycle();
}
}
public void drawLine(int x, int y) {
if (undopoints != null && !undopoints.empty()) {
drawBlackLine(undopoints.peek().x, undopoints.peek().y, x, y);
undopoints.push(new Point(x, y));
if (onDrawLineListener != null)
onDrawLineListener.OnGivenNextPointListener(x, y);
} else {
undopoints.push(new Point(x, y));
if (onDrawLineListener != null)
onDrawLineListener.OnGivenFirstPointListener(x, y);
}
}
private void drawBlackLine(int startX, int startY, int endX, int endY) {
try {
Log.e("draw", startX + "," + startY + "," + endX + "," + endY);
Bitmap bm = mBitmap;
//format points
startX = startX >= bm.getWidth() ? bm.getWidth() - 1 : startX;
startX = startX < 0 ? 0 : startX;
startY = startY >= bm.getHeight() ? bm.getHeight() - 1 : startY;
startY = startY < 0 ? 0 : startY;
endX = endX >= bm.getWidth() ? bm.getWidth() - 1 : endX;
endX = endX < 0 ? 0 : endX;
endY = endY >= bm.getHeight() ? bm.getHeight() - 1 : endY;
endY = endY < 0 ? 0 : endY;
//test points
bm.getPixel(endX, endY);
bm.getPixel(startX, startY);
pushUndoStack(bm.copy(bm.getConfig(), true));
doingDrawLine(bm, startX, startY, endX, endY);
setImageDrawable(new BitmapDrawable(getResources(), bm));
if (onRedoUndoListener != null) {
onRedoUndoListener.onRedoUndo(bmstackundo.size(), bmstackredo.size());
}
if (onDrawLineListener != null)
onDrawLineListener.OnDrawFinishedListener(true, startX, startY, endX, endY);
} catch (Exception e) {
Log.e("drawline", e.toString());
bmstackundo.pop();
if (onDrawLineListener != null)
onDrawLineListener.OnDrawFinishedListener(false, startX, startY, endX, endY);
}
}
private void doingDrawLine(Bitmap bm, int startX, int startY, int endX, int endY) {
Canvas canvas = new Canvas(bm);
Paint paint = new Paint();
paint.setColor(0xFF000000);
paint.setStrokeWidth(2);
canvas.drawLine(startX, startY, endX, endY, paint);
}
private void doingDrawLineAsyn(Bitmap bm, int startX, int startY, int endX, int endY) {
//if two point same reture
if (startX == endX && startY == endY) {
bm.setPixel(startX, startY, 0xFF000000);
return;
}
//if shuxian
if (startX == endX) {
if (endY > startY) {
for (int i = startY; i < endY; i++) {
bm.setPixel(startX, i, 0xFF000000);
}
} else {
for (int i = endY; i < startY; i++) {
bm.setPixel(startX, i, 0xFF000000);
}
}
return;
}
//if henxian
if (startY == endY) {
if (endX > startX) {
for (int i = startX; i < endX; i++) {
bm.setPixel(i, startY, 0xFF000000);
}
} else {
for (int i = endX; i < startX; i++) {
bm.setPixel(i, startY, 0xFF000000);
}
}
return;
}
//if xiexian
if (Math.abs(endY - startY) > Math.abs(endX - startX)) {
float radio = Math.abs((float) (endY - startY) / (endX - startX));
int offset = 0;
int bushu = radio % 1 == 0 ? 0 : (int) (1 / (radio % 1));
int tempY;
if (endY > startY && endX > startX) {
tempY = startY;
for (int i = startX; i < endX; i++) {
for (int j = 0; j <= radio; j++) {
bm.setPixel(i, tempY + j, 0xFF000000);
}
tempY += (int) radio;
if (bushu != 0) {
if (offset == bushu) {
bm.setPixel(i, tempY++, 0xFF000000);
offset = 0;
} else {
offset++;
}
}
}
} else if (endY < startY && endX > startX) {
tempY = startY;
for (int i = startX; i < endX; i++) {
for (int j = 0; j <= radio; j++) {
bm.setPixel(i, tempY - j, 0xFF000000);
}
tempY -= (int) radio;
if (bushu != 0) {
if (offset == bushu) {
bm.setPixel(i, tempY--, 0xFF000000);
offset = 0;
} else {
offset++;
}
}
}
} else if (endY > startY && endX < startX) {
tempY = endY;
for (int i = endX; i < startX; i++) {
for (int j = 0; j <= radio; j++) {
bm.setPixel(i, tempY - j, 0xFF000000);
}
tempY -= (int) radio;
if (bushu != 0) {
if (offset == bushu) {
bm.setPixel(i, tempY--, 0xFF000000);
offset = 0;
} else {
offset++;
}
}
}
} else if (endY < startY && endX < startX) {
tempY = endY;
for (int i = endX; i < startX; i++) {
for (int j = 0; j <= radio; j++) {
bm.setPixel(i, tempY + j, 0xFF000000);
}
tempY += (int) radio;
if (bushu != 0) {
if (offset == bushu) {
bm.setPixel(i, tempY++, 0xFF000000);
offset = 0;
} else {
offset++;
}
}
}
}
} else {
float radio = Math.abs((float) (endX - startX) / (endY - startY));
int offset = 0;
int bushu = radio % 1 == 0 ? 0 : (int) (1 / (radio % 1));
int tempX;
if (endY > startY && endX > startX) {
tempX = startX; //select small one
for (int i = startY; i < endY; i++) { //loop start at small one end at large one
for (int j = 0; j <= radio; j++) {
bm.setPixel(tempX + j, i, 0xFF000000);
}
tempX += (int) radio;
if (bushu != 0) {
if (offset == bushu) {
bm.setPixel(++tempX, i, 0xFF000000);
offset = 0;
} else {
offset++;
}
}
}
} else if (endY < startY && endX > startX) {
tempX = endX;
for (int i = endY; i < startY; i++) {
for (int j = 0; j <= radio; j++) {
bm.setPixel(tempX - j, i, 0xFF000000);
}
tempX -= (int) radio;
if (bushu != 0) {
if (offset == bushu) {
bm.setPixel(--tempX, i, 0xFF000000);
offset = 0;
} else {
offset++;
}
}
}
} else if (endY > startY && endX < startX) {
tempX = startX;
for (int i = startY; i < endY; i++) {
for (int j = 0; j <= radio; j++) {
bm.setPixel(tempX - j, i, 0xFF000000);
}
tempX -= (int) radio;
if (bushu != 0) {
if (offset == bushu) {
bm.setPixel(--tempX, i, 0xFF000000);
offset = 0;
} else {
offset++;
}
}
}
} else if (endY < startY && endX < startX) {
tempX = endX;
for (int i = endY; i < startY; i++) {
for (int j = 0; j < radio; j++) {
bm.setPixel(tempX + j, i, 0xFF000000);
}
tempX += (int) radio;
if (bushu != 0) {
if (offset == bushu) {
bm.setPixel(++tempX, i, 0xFF000000);
offset = 0;
} else {
offset++;
}
}
}
}
}
}
public void setOnDrawLineListener(OnDrawLineListener onDrawLineListener) {
this.onDrawLineListener = onDrawLineListener;
}
}