如何使自定义画笔的背景透明? [英] How can I make the background of my custom brush transparent?

查看:83
本文介绍了如何使自定义画笔的背景透明?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想用我的自定义画笔在下面的代码上绘制画布,但是正如您在图片中看到的那样,我的画笔的背景是黑色的,尽管没有颜色.

I wanted to draw on the canvas with the code below with my custom brush, but as you can see in the picture, the background of my brush is black, albeit without color.

尽管我将笔刷颜色指定为Color.TRANSPARENT或Color.parseColor(#00000000&"),但笔刷背景仍然变为黑色.

Although I specified the brush color as Color.TRANSPARENT or Color.parseColor ("# 00000000"), the brush background still turns black.

如何使笔刷的背景颜色透明?

How can I make the background color of my brush transparent?

单击以查看图片

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import androidx.annotation.ColorInt;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import android.util.AttributeSet;
import android.util.Pair;
import android.view.MotionEvent;
import android.view.View;

import java.util.Stack;

public class BrushDrawingView extends View {

    static final float DEFAULT_BRUSH_SIZE = 50.0f;
    static final float DEFAULT_ERASER_SIZE = 50.0f;
    static final int DEFAULT_OPACITY = 255;

    private float mBrushSize = DEFAULT_BRUSH_SIZE;
    private float mBrushEraserSize = DEFAULT_ERASER_SIZE;
    private int mOpacity = DEFAULT_OPACITY;

    private final Stack<BrushLinePath> mDrawnPaths = new Stack<>();
    private final Stack<BrushLinePath> mRedoPaths = new Stack<>();
    private final Paint mDrawPaint = new Paint();

    private Canvas mDrawCanvas;
    private boolean mBrushDrawMode;
    private Bitmap brushBitmap;

    private Path mPath;
    private float mTouchX, mTouchY;
    private static final float TOUCH_TOLERANCE = 4;

    private BrushViewChangeListener mBrushViewChangeListener;

    public BrushDrawingView(Context context) {
        this(context, null);
    }

    public BrushDrawingView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BrushDrawingView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setupBrushDrawing();
    }

    private void setupBrushDrawing() {
        //Caution: This line is to disable hardware acceleration to make eraser feature work properly
        setupPathAndPaint();
        setVisibility(View.GONE);
    }

    private void setupPathAndPaint() {
        mPath = new Path();
        mDrawPaint.setAntiAlias(true);
        mDrawPaint.setStyle(Paint.Style.STROKE);
        mDrawPaint.setStrokeJoin(Paint.Join.ROUND);
        mDrawPaint.setStrokeCap(Paint.Cap.ROUND);
        mDrawPaint.setStrokeWidth(mBrushSize);
        mDrawPaint.setAlpha(mOpacity);
    }

    private void refreshBrushDrawing() {
        mBrushDrawMode = true;
        setupPathAndPaint();
    }

    void brushEraser() {
        mBrushDrawMode = true;
        mDrawPaint.setStrokeWidth(mBrushEraserSize);
        mDrawPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
    }

    public void setBrushDrawingMode(boolean brushDrawMode) {
        this.mBrushDrawMode = brushDrawMode;
        if (brushDrawMode) {
            this.setVisibility(View.VISIBLE);
            refreshBrushDrawing();
        }
    }

    public Bitmap getBrushBitmap() {
        return brushBitmap;
    }

    public void setBrushBitmap(Bitmap brushBitmap) {
        this.brushBitmap = brushBitmap;
    }

    public void setOpacity(@IntRange(from = 0, to = 255) int opacity) {
        this.mOpacity = (int) (opacity * 2.55f);
        setBrushDrawingMode(true);
    }

    public int getOpacity() {
        return mOpacity;
    }

    boolean getBrushDrawingMode() {
        return mBrushDrawMode;
    }

    public void setBrushSize(float size) {
        mBrushSize = 5 + (int) (size);
        setBrushDrawingMode(true);
    }

    void setBrushColor(@ColorInt int color) {
        mDrawPaint.setColor(color);
        setBrushDrawingMode(true);
    }

    void setBrushEraserSize(float brushEraserSize) {
        this.mBrushEraserSize = brushEraserSize;
        setBrushDrawingMode(true);
    }

    void setBrushEraserColor(@ColorInt int color) {
        mDrawPaint.setColor(color);
        setBrushDrawingMode(true);
    }

    float getEraserSize() {
        return mBrushEraserSize;
    }

    public float getBrushSize() {
        return mBrushSize;
    }

    int getBrushColor() {
        return mDrawPaint.getColor();
    }

    public void clearAll() {
        mDrawnPaths.clear();
        mRedoPaths.clear();
        if (mDrawCanvas != null) {
            mDrawCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
        }
        invalidate();
    }

    void setBrushViewChangeListener(BrushViewChangeListener brushViewChangeListener) {
        mBrushViewChangeListener = brushViewChangeListener;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        Bitmap canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        mDrawCanvas = new Canvas(canvasBitmap);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        for (BrushLinePath linePath : mDrawnPaths) {
            canvas.drawPath(linePath.getDrawPath(), linePath.getDrawPaint());
        }
        canvas.drawPath(mPath, mDrawPaint);
        /////
        final Bitmap scaledBitmap = getScaledBitmap();

        final float centerX = scaledBitmap.getWidth() / 2;
        final float centerY = scaledBitmap.getHeight() / 2;

        final PathMeasure pathMeasure = new PathMeasure(mPath, false);

        float distance = scaledBitmap.getWidth() / 2;

        float[] position = new float[2];
        float[] slope = new float[2];

        float slopeDegree;

        while (distance < pathMeasure.getLength())
        {
            pathMeasure.getPosTan(distance, position, slope);
            slopeDegree = (float)((Math.atan2(slope[1], slope[0]) * 180f) / Math.PI);
            canvas.save();
            canvas.translate(position[0] - centerX, position[1] - centerY);
            canvas.rotate(slopeDegree, centerX, centerY);
            canvas.drawBitmap(scaledBitmap, 0, 0, mDrawPaint);
            canvas.restore();
            distance += scaledBitmap.getWidth() + 10;
        }

    }

    /////

    private Bitmap getScaledBitmap()
    {
        // width / height of the bitmap[
        float width = brushBitmap.getWidth();
        float height = brushBitmap.getHeight();

        // ratio of the bitmap
        float ratio = width / height;

        // set the height of the bitmap to the width of the path (from the paint object).
        float scaledHeight = mDrawPaint.getStrokeWidth();

        // to maintain aspect ratio of the bitmap, use the height * ratio for the width.
        float scaledWidth = scaledHeight * ratio;

        // return the generated bitmap, scaled to the correct size.
        return Bitmap.createScaledBitmap(brushBitmap, (int)scaledWidth, (int)scaledHeight, true);
    }

    /**
     * Handle touch event to draw paint on canvas i.e brush drawing
     *
     * @param event points having touch info
     * @return true if handling touch events
     */
    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(@NonNull MotionEvent event) {
        if (mBrushDrawMode) {
            float touchX = event.getX();
            float touchY = event.getY();
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    touchStart(touchX, touchY);
                    break;
                case MotionEvent.ACTION_MOVE:
                    touchMove(touchX, touchY);
                    break;
                case MotionEvent.ACTION_UP:
                    touchUp();
                    break;
            }
            invalidate();
            return true;
        } else {
            return false;
        }
    }

    boolean undo() {
        if (!mDrawnPaths.empty()) {
            mRedoPaths.push(mDrawnPaths.pop());
            invalidate();
        }
        if (mBrushViewChangeListener != null) {
            mBrushViewChangeListener.onViewRemoved(this);
        }
        return !mDrawnPaths.empty();
    }

    boolean redo() {
        if (!mRedoPaths.empty()) {
            mDrawnPaths.push(mRedoPaths.pop());
            invalidate();
        }

        if (mBrushViewChangeListener != null) {
            mBrushViewChangeListener.onViewAdd(this);
        }
        return !mRedoPaths.empty();
    }


    private void touchStart(float x, float y) {
        mRedoPaths.clear();
        mPath.reset();
        mPath.moveTo(x, y);
        mTouchX = x;
        mTouchY = y;
        if (mBrushViewChangeListener != null) {
            mBrushViewChangeListener.onStartDrawing();
        }
    }

    private void touchMove(float x, float y) {
        float dx = Math.abs(x - mTouchX);
        float dy = Math.abs(y - mTouchY);
        if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
            mPath.quadTo(mTouchX, mTouchY, (x + mTouchX) / 2, (y + mTouchY) / 2);
            mTouchX = x;
            mTouchY = y;
        }
    }

    private void touchUp() {
        mPath.lineTo(mTouchX, mTouchY);
        // Commit the path to our offscreen
        mDrawCanvas.drawPath(mPath, mDrawPaint);
        // kill this so we don't double draw

        mDrawnPaths.push(new BrushLinePath(mPath, mDrawPaint));

        /////

        final Bitmap scaledBitmap = getScaledBitmap();

        final float centerX = scaledBitmap.getWidth() / 2;
        final float centerY = scaledBitmap.getHeight() / 2;

        final PathMeasure pathMeasure = new PathMeasure(mPath, false);

        float distance = scaledBitmap.getWidth() / 2;

        float[] position = new float[2];
        float[] slope = new float[2];

        float slopeDegree;

        while (distance < pathMeasure.getLength())
        {
            pathMeasure.getPosTan(distance, position, slope);
            slopeDegree = (float)((Math.atan2(slope[1], slope[0]) * 180f) / Math.PI);
            mDrawCanvas.save();
            mDrawCanvas.translate(position[0] - centerX, position[1] - centerY);
            mDrawCanvas.rotate(slopeDegree, centerX, centerY);
            mDrawCanvas.drawBitmap(scaledBitmap, 0, 0, mDrawPaint);
            mDrawCanvas.restore();
            distance += scaledBitmap.getWidth() + 10;
        }

        /////

        mPath = new Path();
        if (mBrushViewChangeListener != null) {
            mBrushViewChangeListener.onStopDrawing();
            mBrushViewChangeListener.onViewAdd(this);
        }
    }

    @VisibleForTesting
    Paint getDrawingPaint() {
        return mDrawPaint;
    }

    @VisibleForTesting
    Pair<Stack<BrushLinePath>, Stack<BrushLinePath>> getDrawingPath() {
        return new Pair<>(mDrawnPaths, mRedoPaths);
    }
}

public interface BrushViewChangeListener {
    void onViewAdd(BrushDrawingView brushDrawingView);

    void onViewRemoved(BrushDrawingView brushDrawingView);

    void onStartDrawing();

    void onStopDrawing();
}

class BrushLinePath {
    private final Paint mDrawPaint;
    private final Path mDrawPath;

    BrushLinePath(final Path drawPath, final Paint drawPaints) {
        mDrawPaint = new Paint(drawPaints);
        mDrawPath = new Path(drawPath);
    }

    Paint getDrawPaint() {
        return mDrawPaint;
    }

    Path getDrawPath() {
        return mDrawPath;
    }
}

推荐答案

发生这种情况的原因是默认情况下Paint没有设置Alpha合成模式.因此,当您尝试在画布上绘制位图时,它将用画笔像素替换目标像素,在本例中为#00000000.这将导致像素显示为黑色.请查看以下文档: https://developer.android.com/reference/android/graphics/PorterDuff.Mode

The reason it happens is because Paint doesn't have an alpha composing mode set by default. Thus, when you're trying to paint a bitmap over your canvas it will replace the destination pixels with your brush pixels, which in your case is #00000000. And that will result in pixel being displayed as black. Have a look into this documentation: https://developer.android.com/reference/android/graphics/PorterDuff.Mode

乍一看,您似乎正在寻找的是 PorterDuff.Mode.SRC_OVER PorterDuff.Mode.SRC_ATOP -这样,源图像中的透明像素(您的笔刷)不会从目标(画布)上过度绘制像素.如果您的背景始终是不透明的,则 SRC_OVER SRC_ATOP 之间不会有任何区别,但是如果不是-选择适合您需要的背景.然后,您可以通过在此行的末尾添加以下行来修改 setupPathAndPaint 方法:

By the first glance it seems you're looking for PorterDuff.Mode.SRC_OVER or PorterDuff.Mode.SRC_ATOP - this way transparent pixels from your source image (your brush) will not over-draw the pixels from your destination (canvas). In case your background is always non-transparent, you will see no difference between SRC_OVER and SRC_ATOP, but if it isn't - choose the one which fits your needs. Then you can modify setupPathAndPaint method by adding this line to its end:

    mDrawPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));

这篇关于如何使自定义画笔的背景透明?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆