Razia Rani Ответов: 0

При масштабировании и переводе холста заполнение цветом не очень хорошо. Я не понимаю, как это решить?


So I have a canvas that I draw a bitmap onto.. When the scale factor of the canvas is 1.0f everything works perfectly and fill color perfectly how I want it to.. However the problem is when I change the scale of the canvas. Either zoomed in or out I run into problems with filling color in bitmap.

I just can't work out the right calculation to get my desired result! Please help!


Что я уже пробовал:

public class ColorGFX extends SurfaceView implements Runnable {
public Thread mThread;
private Handler mHandler;
public boolean isThreadBroken = false;
private int mRunnableCounter = 0;
public int selectedColor = Color.BLACK;
public boolean isFillEnabled = false;
public boolean isFillModeEnabled = true;
public boolean isEraseModeEnabled = false;
private SurfaceHolder mOurHolder;
private Thread mOurThread = null;
public int imageWidth;
public int imageHeight;
public boolean isNextImage = false;
public Bitmap pictureBitmap;
//   public String paintBitmapName;
public Bitmap pictureBitmapBuffer;
public Bitmap bitmap;
public Canvas pathCanvas;
public Canvas fillCanvas;
Matrix matrix;
public Canvas canvas;
private boolean mIsDrawn = false;
public Paint paint;
public Paint bitmapPaint;
public Path mPath;
public boolean isSavedData;
private boolean[][] mFloodfillList;
private boolean[][] mStrokefillList;
private Context mContext;

private static final int INVALID_POINTER_ID = -1;
private int mActivePointerId = INVALID_POINTER_ID;
private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;
private float mLastTouchX;
private float mLastTouchY;
public float mPosX = 10;
public float mPosY = 10;
private int mImageWidthScaled;
private int mImageHeightScaled;


public ColorGFX(Context context, int width, int height) {
    super(context);
    this.mContext = context;
    this.imageWidth = width;
    this.imageHeight = height;
    mPath = new Path();
    bitmapPaint = new Paint(Paint.DITHER_FLAG);
    mOurHolder = getHolder();
    Log.e("imageWidth", "......................." + imageWidth);
    Log.e("imageHeight", "......................" + imageHeight);
    mImageWidthScaled = (int) (imageWidth * mScaleFactor);
    mImageHeightScaled = (int) (imageHeight * mScaleFactor);
    mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}

public ColorGFX(Context context, int width, int height, boolean isSavedData, Bitmap savedPathBitmap) {
    super(context);
    this.mContext = context;
    this.imageWidth = width;
    this.imageHeight = height;
    this.isSavedData = isSavedData;
    this.bitmap = savedPathBitmap;
    mPath = new Path();
    bitmapPaint = new Paint(Paint.DITHER_FLAG);
    mOurHolder = getHolder();
}

public ColorGFX(Context context) {
    super(context);
    this.mContext = context;
    mPath = new Path();
    bitmapPaint = new Paint(Paint.DITHER_FLAG);
    mOurHolder = getHolder();

}

public void pause() {
    if (mThread != null && mThread.isAlive()) {
        isThreadBroken = true;
    }
    mOurThread.interrupt();
}

public void resume() {
    if (bitmap == null && !isSavedData) {
        bitmap = Bitmap.createBitmap(imageWidth, imageHeight, Bitmap.Config.ARGB_8888);
    }
    pathCanvas = new Canvas(bitmap);
    mOurThread = new Thread(this);
    mOurThread.start();
}

public void run() {
    while (!mOurThread.isInterrupted()) {
        if (!mOurHolder.getSurface().isValid()) {
            continue;
        }
        canvas = mOurHolder.lockCanvas();
        canvas.save();
        matrix = new Matrix();
        matrix.postTranslate(mPosX, mPosY);
        canvas.concat(matrix);
        canvas.scale(mScaleFactor, mScaleFactor);
        canvas.drawColor(Color.WHITE);
        if (pictureBitmap != null) {
            canvas.drawBitmap(pictureBitmap, 0, 0, null);
        }
        if (bitmap != null) {
            if (!bitmap.isRecycled()) {
                canvas.drawBitmap(bitmap, 0, 0, bitmapPaint);
            }
        }
        if (mIsDrawn) {
            mPath.reset();
            mIsDrawn = false;
        }
        canvas.drawPath(mPath, paint);

        if (pictureBitmap != null) {

            canvas.drawBitmap(pictureBitmap, 0, 0, null);
        }

        if (isNextImage) {
            loadNewImages();
            isNextImage = false;
        }
        // Once we are done, unlock the canvas and update the display.
        mOurHolder.unlockCanvasAndPost(canvas);
    }

    if (mOurThread.isInterrupted()) {
        mOurThread = null;
    }
}

public void loadNewImages() {
    if (pictureBitmap != null) {
        pictureBitmap.recycle();
    }
    int counter = 0;
    while (true) {
        pictureBitmap = pictureBitmapBuffer.copy(Bitmap.Config.ARGB_8888, true);
        if (pictureBitmap != null) {
            break;
        } else if (counter > 1000) {
            // TODO: throw a timeout exception. Resource is not loading or
            // something is hanging. Right now we'll just break so we don't
            // over consume resources. The error might simply crash the
            // program for the user if this ever happens.
            break;
        }
        counter++;
    }
    pictureBitmapBuffer.recycle();
}

public void clear() {
    pathCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
}
/**
 * Handles fill operations when a fill event occurs.
 */
private void fillHandler(float x, float y, int scaleimageWidth, int scaleimageHeight) {
    if ((scaleimageWidth > imageWidth || scaleimageHeight > imageHeight)) {
        Toast.makeText(mContext, "Image Scaling working", Toast.LENGTH_SHORT).show();
    } else {
        if (isFillModeEnabled) {
            if (isFillEnabled) {
                if (mRunnableCounter >= 1) {
                    // Signal end of op.
                    isFillEnabled = false;
                    return;
                }
                int resPaintId = R.drawable.coloring_book_1_image_1_map;
                // mContext.getResources().getIdentifier(paintBitmapName, "drawable", mContext.getPackageName());
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inJustDecodeBounds = true;
                BitmapFactory.decodeResource(getResources(), resPaintId, options);

                int inSampleSize = 1;

                int fillImageWidth = options.outWidth;
                int fillImageHeight = options.outHeight;
                boolean scaleFailed = false;
                Bitmap paintMap = null;
                float resizeRatioHeight = 1;




                if (fillImageWidth > imageWidth || fillImageHeight > imageHeight) {

                    resizeRatioHeight = (float) fillImageHeight / (float) imageHeight;
                    inSampleSize = (int) resizeRatioHeight;
                    if (inSampleSize <= 1) {
                        scaleFailed = true;
                    }
                }
                options.inSampleSize = inSampleSize;
                options.inJustDecodeBounds = false;

                Bitmap picture = BitmapFactory.decodeResource(getResources(), resPaintId, options);
                if (scaleFailed) {
                    int newWidth = (int) (picture.getWidth() / resizeRatioHeight);
                    int newHeight = (int) (picture.getHeight() / resizeRatioHeight);
                    paintMap = Bitmap.createScaledBitmap(picture, newWidth, newHeight, true);
                    picture.recycle();
                } else {
                    paintMap = picture;
                }
                Bitmap mutablePaintMap = paintMap.copy(Config.ARGB_8888, true);
                paintMap.recycle();
                Log.e("TAG", "..........x............." + x);
                Log.e("TAG", ".........y............." + y);
                if (picture.getHeight() > y && picture.getWidth() > x) {
                    int targetColor = mutablePaintMap.getPixel((int) x, (int) y);
                    if (targetColor == Color.TRANSPARENT) {
                        Point node = new Point((int) x, (int) y);
                        mRunnableCounter++;
                        LoadViewTask floodfillTask = new LoadViewTask();
                        floodfillTask.image = mutablePaintMap;
                        floodfillTask.point = node;
                        floodfillTask.target = targetColor;
                        floodfillTask.replacementColor = selectedColor;

                        mHandler = new Handler();
                        mThread = new Thread(floodfillTask, "FloodFillThread");
                        mThread.start();
                    }
                } else {
                    Log.e(TAG, "Out of bound");
                }
            }
        }
    }
}

public void colorPixels(Bitmap picture, int replacementColor) {
    for (int i = 0; i < mFloodfillList.length; i++) {
        for (int j = 0; j < mFloodfillList[i].length; j++) {
            if (mFloodfillList[i][j] != false) {
                picture.setPixel(i, j, replacementColor);
            }
            if (mStrokefillList[i][j] != false) {
                picture.setPixel(i, j, replacementColor);
            }
        }
    }
}        


public void clearPixelLists() {
    mStrokefillList = null;
    mFloodfillList = null;
}

private class LoadViewTask implements Runnable {

    public Point point;
    public int target;
    public int replacementColor;
    public Bitmap image;

    public boolean[][] list;
    public boolean[][] strokeList;

    @Override
    public void run() {
        synchronized (mThread) {

            floodFill(point, target, replacementColor, image);
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                }
            });
        }

        mHandler.post(new Runnable() {
            @Override
            public void run() {
                if (!isThreadBroken) {
                    image.recycle();
                    mFloodfillList = list;
                    mStrokefillList = strokeList;

                    Bitmap fillPicture = Bitmap.createBitmap(imageWidth, imageHeight, Bitmap.Config.ARGB_8888);
                    colorPixels(fillPicture, replacementColor);
                    Paint addFilter = new Paint();
                    addFilter.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
                    pathCanvas.drawBitmap(fillPicture, 0, 0, addFilter);
                }
                clearPixelLists();
                mRunnableCounter--;
                if (mRunnableCounter == 0) {
                    isThreadBroken = false;
                }
            }
        });
        synchronized (mThread) {
            mThread.interrupt();
        }
    }

    public void floodFill(Point node, int targetColor, int replacementColor, Bitmap picture) {
        int width = picture.getWidth();
        int height = picture.getHeight();
        list = new boolean[width][height];
        strokeList = new boolean[width][height];

        int target = targetColor;
        int replacement = replacementColor;
        if (target != replacement) {
            Queue<Point> queue = new LinkedList<Point>();
            do {
                if (isThreadBroken) {
                    break;
                }
                int x = node.x;
                int y = node.y;
                while (x > 0 && picture.getPixel(x - 1, y) == target) {
                    // Continuously decrement x (AKA bring x as far to the
                    // west as possible given the color constraints).
                    x--;
                }
                // Set directional booleans.
                boolean spanUp = false;
                boolean spanDown = false;
                // While x has not reached as far East as it can in the
                // bitmap (AKA hasn't hit the end of the image and hasn't
                // reached a color different than the replacement color)...
                while (x < width && picture.getPixel(x, y) == target) {

                    // Replace the current pixel color.
                    picture.setPixel(x, y, replacement);
                    // Add the pixel to the flood fill list.
                    list[x][y] = true;

                    // If we don't take the stroke paths into consideration
                    // and color them where necessary, we WILL have reduced
                    // aliasing, but not perfect anti-aliasing. By coloring
                    // the paths, we have virtually ZERO aliasing.

                    // TOP

                    if (y + 1 < height - 1 && picture.getPixel(x, y + 1) != target) {
                        strokeList[x][y + 1] = true;
                    }

                    // RIGHT
                    if (x + 1 < width - 1 && picture.getPixel(x + 1, y) != target) {
                        strokeList[x + 1][y] = true;
                    }

                    // LEFT
                    if (x - 1 > 0 && picture.getPixel(x - 1, y) != target) {
                        strokeList[x - 1][y] = true;
                    }

                    // BOTTOM
                    if (y - 1 > 0 && picture.getPixel(x, y - 1) != target) {
                        strokeList[x][y - 1] = true;
                    }

                    // Add one SOUTH point to the queue if it is replaceable
                    // (this will be the next relative point to check from)
                    // and we have not previously moved down.
                    if (!spanUp && y > 0 && picture.getPixel(x, y - 1) == target) {
                        queue.add(new Point(x, y - 1));
                        spanUp = true;
                    }
                    // If the SOUTH point is unreplaceable or we have
                    // previously moved up set the boolean to false.
                    else if (spanUp && y > 0 && picture.getPixel(x, y - 1) != target) {
                        spanUp = false;
                    }

                    // Add one NORTH point to the queue if it is replaceable
                    // (this will be the next relative point to check from)
                    // and we have not previously moved up.
                    if (!spanDown && y < height - 1
                            && picture.getPixel(x, y + 1) == target) {
                        queue.add(new Point(x, y + 1));
                        spanDown = true;
                    }
                    // If the NORTH point is unreplaceable or we have
                    // previously moved up set the boolean to false.
                    else if (spanDown && y < height - 1 && picture.getPixel(x, y + 1) != target) {
                        spanDown = false;
                    }

                    // Increment the x-position, 1 to the east.
                    x++;
                }
            }
            // Remove the head of this queue. Keep looping until no pixels
            // remain.
            while ((node = queue.poll()) != null);
        }

        // Once the Flood Fill Algorithm has completed, turn the action flag
        // off. We're done.
        isFillEnabled = false;
    }
}


@Override
public boolean onTouchEvent(MotionEvent ev) {
    // Let the ScaleGestureDetector inspect all events.
    mScaleDetector.onTouchEvent(ev);

    final int action = ev.getAction();
    switch (action & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_DOWN: {
            Log.e("MotionEvent", "==========ACTION_DOWN===========");
            final float x = ev.getX();
            final float y = ev.getY();

            mLastTouchX = x;
            mLastTouchY = y;
            mActivePointerId = ev.getPointerId(0);
            break;
        }

        case MotionEvent.ACTION_POINTER_DOWN:
            isFillModeEnabled = false;
            break;

        case MotionEvent.ACTION_MOVE: {

            Log.e("MotionEvent", "==========ACTION_MOVE===========");
            final int pointerIndex = ev.findPointerIndex(mActivePointerId);
            final float x = ev.getX(pointerIndex);
            final float y = ev.getY(pointerIndex);

            // Only move if the ScaleGestureDetector isn't processing a gesture.
            if (!mScaleDetector.isInProgress()) {
                isFillModeEnabled = true;
                final float dx = x - mLastTouchX;
                final float dy = y - mLastTouchY;

                mPosX += dx;
                mPosY += dy;

                invalidate();
            } else {
                isFillModeEnabled = false;
            }

            mLastTouchX = x;
            mLastTouchY = y;

            break;
        }
        case MotionEvent.ACTION_UP: {
            Log.e("MotionEvent", "==========ACTION_UP===========");
            setFocusable(false);
            Log.e("ACTION_UP", "===============imageWidth==========" + imageWidth);
            Log.e("ACTION_UP", "============imageHeight===========" + imageHeight);

            mImageWidthScaled = (int) (imageWidth * mScaleFactor);
            mImageHeightScaled = (int) (imageHeight * mScaleFactor);

            mActivePointerId = INVALID_POINTER_ID;

            mActivePointerId = INVALID_POINTER_ID;
            float mx = ev.getX();
            float my = ev.getY();
            this.isFillEnabled = true;
            fillHandler(mx, my, mImageWidthScaled, mImageHeightScaled);
            invalidate();

            break;
        }
        case MotionEvent.ACTION_CANCEL: {
            Log.e("MotionEvent", "==========ACTION_CANCEL===========");
            mActivePointerId = INVALID_POINTER_ID;
            break;
        }

        case MotionEvent.ACTION_POINTER_UP: {

            isFillModeEnabled = false;
            Log.e("MotionEvent", "==========ACTION_POINTER_UP===========");
            final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
            final int pointerId = ev.getPointerId(pointerIndex);
            if (pointerId == mActivePointerId) {
                // This was our active pointer going up. Choose a new
                // active pointer and adjust accordingly.
                final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
                mLastTouchX = ev.getX(newPointerIndex);
                mLastTouchY = ev.getY(newPointerIndex);
                mActivePointerId = ev.getPointerId(newPointerIndex);
            }
            break;
        }
    }
    return true;
}

private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        mScaleFactor *= detector.getScaleFactor();
        mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 10.0f));
        invalidate();
        return true;
    }
}

0 Ответов