javaandroidcanvaspaintandroid-photoview

Android Manipulate Images on Canvas - working with touch, move, zoom - in/out, scale


I am working on Photography app like this : https://play.google.com/store/apps/details?id=com.photo.editor.collage.maker.photoblender&hl=en

I have to implement functionality like this : enter image description here

I have 2 functionalities with this view...

1) with splash background

enter image description here enter image description here enter image description here

2) with blurred background

enter image description here enter image description here enter image description here

Both are using 2 shapes.... Now as you are using path to draw shape....i wants to draw image bitmap shape like above.

I have more shapes like below (All having 2 images as above):

3)

enter image description here enter image description here

I wants to replace this portion of code you are using :

private void createPath() {
            path.reset();
            path.moveTo(114, 156);
            float[] points = {68, 138, 19, 136, 21, 87, 8, 39, 56, 26, 97, -2, 123, 40, 163, 71, 131, 109};
            for (int i = 0; i < points.length; i += 2) {
                path.lineTo(points[i], points[i + 1]);
            }
            path.close();
            Matrix m = new Matrix();
            m.setRectToRect(new RectF(0, 0, 160, 160), clip, Matrix.ScaleToFit.CENTER);
            path.transform(m);
            transformedPath.set(path);
        }

i did above functionality by adding shape as path, but when i am trying to use shapes as bitmap i didn't get complete result.... here is my code for view.

class MotionImageView extends View implements MatrixGestureDetector.OnMatrixChangeListener {
    private final Bitmap shapeMaskBitmap;
    private final Bitmap shapeShadowBitmap;
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    Paint monoPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    Paint borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    Bitmap bitmap, blur;
    Matrix matrix = new Matrix();
    Matrix pathMatrix = new Matrix();
//    Path path = new Path();
//    Path transformedPath = new Path();
    MatrixGestureDetector detector;
    RectF clip = new RectF();

    public MotionImageView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        Log.e("~~~~", "1111");
        bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.model);
        blur = blur(context, bitmap, 8);
        ColorMatrix cm = new ColorMatrix();
        cm.setSaturation(0.25f);
        monoPaint.setColorFilter(new ColorMatrixColorFilter(cm));
        borderPaint.setStyle(Paint.Style.STROKE);
        borderPaint.setColor(0xccffffff);
        borderPaint.setStrokeWidth(4);
        borderPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        detector = new MatrixGestureDetector(pathMatrix, this);

        shapeMaskBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.shape_mask);
        shapeShadowBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.shape_shadow);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        Shader shader = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
        RectF src = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight());
        RectF dst = new RectF(0, 0, w, h);
        matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);
        shader.setLocalMatrix(matrix);
        paint.setShader(shader);
        matrix.mapRect(clip, src);
//        createPath();
    }

    /*private void createPath() {
        path.reset();
        path.moveTo(114, 156);
        float[] points = {68, 138, 19, 136, 21, 87, 8, 39, 56, 26, 97, -2, 123, 40, 163, 71, 131, 109};
        for (int i = 0; i < points.length; i += 2) {
            path.lineTo(points[i], points[i + 1]);
        }
        path.close();
        Matrix m = new Matrix();
        m.setRectToRect(new RectF(0, 0, 160, 160), clip, Matrix.ScaleToFit.CENTER);
        path.transform(m);
        transformedPath.set(path);
    }*/

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawBitmap(blur, matrix, monoPaint);
        canvas.save();
        canvas.clipRect(clip);
//        canvas.drawPath(transformedPath, paint);
        canvas.drawBitmap(shapeMaskBitmap, pathMatrix, paint);
        canvas.restore();
//        canvas.drawPath(transformedPath, borderPaint);
        canvas.drawBitmap(shapeShadowBitmap, pathMatrix, borderPaint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        detector.onTouchEvent(event);
        return true;
    }

    @Override
    public void onChange(Matrix matrix) {
//        path.transform(matrix, transformedPath);
        pathMatrix.set(matrix);
        invalidate();
    }

    Bitmap blur(Context ctx, Bitmap src, float radius) {
        Bitmap bitmap = src.copy(src.getConfig(), true);

        RenderScript renderScript = RenderScript.create(ctx);
        Allocation blurInput = Allocation.createFromBitmap(renderScript, src);
        Allocation blurOutput = Allocation.createFromBitmap(renderScript, bitmap);

        ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));
        blur.setInput(blurInput);
        blur.setRadius(radius);
        blur.forEach(blurOutput);

        blurOutput.copyTo(bitmap);
        renderScript.destroy();
        return bitmap;
    }
}

My output was like this :

enter image description here


Solution

  • what you are trying to do with PorterDuff.Mode.CLEAR and BlurMaskFilter will not work, if you want effect like this:

    enter image description here

    or this:

    enter image description here

    you have to use BitmapShader and ScriptIntrinsicBlur, see this sample custom View:

    class V extends View implements MatrixGestureDetector.OnMatrixChangeListener {
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        Paint monoPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        Paint borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        Bitmap bitmap, blur;
        Matrix matrix = new Matrix();
        Matrix pathMatrix = new Matrix();
        Path path = new Path();
        Path transformedPath = new Path();
        MatrixGestureDetector detector;
        RectF clip = new RectF();
    
        public V(Context context) {
            super(context);
            bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.forest);
            blur = blur(context, bitmap, 8);
            ColorMatrix cm = new ColorMatrix();
            cm.setSaturation(0.25f);
            monoPaint.setColorFilter(new ColorMatrixColorFilter(cm));
            borderPaint.setStyle(Paint.Style.STROKE);
            borderPaint.setColor(0xccffffff);
            borderPaint.setStrokeWidth(4);
            detector = new MatrixGestureDetector(pathMatrix, this);
        }
    
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            Shader shader = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
            RectF src = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight());
            RectF dst = new RectF(0, 0, w, h);
            matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);
            shader.setLocalMatrix(matrix);
            paint.setShader(shader);
            matrix.mapRect(clip, src);
            createPath();
        }
    
        private void createPath() {
            path.reset();
            path.moveTo(114, 156);
            float[] points = {68, 138, 19, 136, 21, 87, 8, 39, 56, 26, 97, -2, 123, 40, 163, 71, 131, 109};
            for (int i = 0; i < points.length; i += 2) {
                path.lineTo(points[i], points[i + 1]);
            }
            path.close();
            Matrix m = new Matrix();
            m.setRectToRect(new RectF(0, 0, 160, 160), clip, Matrix.ScaleToFit.CENTER);
            path.transform(m);
            transformedPath.set(path);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            canvas.drawBitmap(blur, matrix, monoPaint);
            canvas.save();
            canvas.clipRect(clip);
            canvas.drawPath(transformedPath, paint);
            canvas.restore();
            canvas.drawPath(transformedPath, borderPaint);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            detector.onTouchEvent(event);
            return true;
        }
    
        @Override
        public void onChange(Matrix matrix) {
            path.transform(matrix, transformedPath);
            invalidate();
        }
    
        Bitmap blur(Context ctx, Bitmap src, float radius) {
            Bitmap bitmap = src.copy(src.getConfig(), true);
    
            RenderScript renderScript = RenderScript.create(ctx);
            Allocation blurInput = Allocation.createFromBitmap(renderScript, src);
            Allocation blurOutput = Allocation.createFromBitmap(renderScript, bitmap);
    
            ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));
            blur.setInput(blurInput);
            blur.setRadius(radius);
            blur.forEach(blurOutput);
    
            blurOutput.copyTo(bitmap);
            renderScript.destroy();
            return bitmap;
        }
    }
    
    class MatrixGestureDetector {
        private static final String TAG = "MatrixGestureDetector";
    
        interface OnMatrixChangeListener {
            void onChange(Matrix matrix);
        }
    
        private int ptpIdx = 0;
        private Matrix mTempMatrix = new Matrix();
        private Matrix mMatrix;
        private OnMatrixChangeListener mListener;
        private float[] mSrc = new float[4];
        private float[] mDst = new float[4];
        private int mCount;
    
        public MatrixGestureDetector(Matrix matrix, MatrixGestureDetector.OnMatrixChangeListener listener) {
            if (matrix == null) throw new RuntimeException("Matrix cannot be null");
            if (listener == null) throw new RuntimeException("OnMatrixChangeListener cannot be null");
            mMatrix = matrix;
            mListener = listener;
        }
    
        public void onTouchEvent(MotionEvent event) {
            if (event.getPointerCount() > 2) {
                return;
            }
    
            int action = event.getActionMasked();
            int index = event.getActionIndex();
    
            switch (action) {
                case MotionEvent.ACTION_DOWN:
                case MotionEvent.ACTION_POINTER_DOWN:
                    int idx = index * 2;
                    mSrc[idx] = event.getX(index);
                    mSrc[idx + 1] = event.getY(index);
                    mCount++;
                    ptpIdx = 0;
                    break;
    
                case MotionEvent.ACTION_MOVE:
                    for (int i = 0; i < mCount; i++) {
                        idx = ptpIdx + i * 2;
                        mDst[idx] = event.getX(i);
                        mDst[idx + 1] = event.getY(i);
                    }
                    mTempMatrix.setPolyToPoly(mSrc, ptpIdx, mDst, ptpIdx, mCount);
                    mMatrix.postConcat(mTempMatrix);
                    mListener.onChange(mMatrix);
                    System.arraycopy(mDst, 0, mSrc, 0, mDst.length);
                    break;
    
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_POINTER_UP:
                    if (event.getPointerId(index) == 0) ptpIdx = 2;
                    mCount--;
                    break;
            }
        }
    }
    

    EDIT: when using Bitmaps instead of Paths the code is shorter by couple of lines:

    class V extends View implements MatrixGestureDetector.OnMatrixChangeListener {
        Paint monoPaint = new Paint();
        Paint srcInPaint = new Paint();
        Bitmap mask, maskShadow, bitmap, blur;
        Matrix matrix = new Matrix();
        Matrix maskMatrix = new Matrix();
        MatrixGestureDetector detector;
        RectF clip = new RectF();
    
        public V(Context context) {
            super(context);
            mask = BitmapFactory.decodeResource(context.getResources(), R.drawable.mask).extractAlpha();
            maskShadow = BitmapFactory.decodeResource(context.getResources(), R.drawable.mask_shadow);
            bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.forest);
            blur = blur(context, bitmap, 8);
            ColorMatrix cm = new ColorMatrix();
            cm.setSaturation(0.25f);
            monoPaint.setColorFilter(new ColorMatrixColorFilter(cm));
            srcInPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
            detector = new MatrixGestureDetector(maskMatrix, this);
        }
    
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            RectF src = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight());
            RectF dst = new RectF(0, 0, w, h);
            matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);
            matrix.mapRect(dst, src);
    
            src.set(0, 0, mask.getWidth(), mask.getHeight());
            maskMatrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);
            setupClip();
        }
    
        private void setupClip() {
            clip.set(0, 0, mask.getWidth(), mask.getHeight());
            maskMatrix.mapRect(clip);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            canvas.drawBitmap(blur, matrix, monoPaint);
            drawMask(canvas);
        }
    
        private void drawMask(Canvas canvas) {
            canvas.clipRect(clip);
            canvas.saveLayer(clip, null, 0);
            canvas.drawBitmap(mask, maskMatrix, null);
            canvas.drawBitmap(bitmap, matrix, srcInPaint);
            canvas.drawBitmap(maskShadow, maskMatrix, null);
            canvas.restore();
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            detector.onTouchEvent(event);
            return true;
        }
    
        @Override
        public void onChange(Matrix matrix) {
            setupClip();
            invalidate();
        }
    
        Bitmap blur(Context ctx, Bitmap src, float radius) {
            Bitmap bitmap = src.copy(src.getConfig(), true);
    
            RenderScript renderScript = RenderScript.create(ctx);
            Allocation blurInput = Allocation.createFromBitmap(renderScript, src);
            Allocation blurOutput = Allocation.createFromBitmap(renderScript, bitmap);
    
            ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));
            blur.setInput(blurInput);
            blur.setRadius(radius);
            blur.forEach(blurOutput);
    
            blurOutput.copyTo(bitmap);
            renderScript.destroy();
            return bitmap;
        }
    }
    

    and the result is like this:

    enter image description here