在画布上绘图 - PorterDuff.Mode.CLEAR绘制黑色!为什么?

时间:2013-08-22 18:08:53

标签: android canvas drawing porter-duff

我正在尝试创建一个简单的自定义视图:有一个由弧路径显示的位图 - 从0deg到360deg。学位正在随着一些FPS而变化。

所以我使用重写的onDraw()方法制作了自定义视图:

@Override
protected void onDraw(Canvas canvas) {

    canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
    arcPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
    canvas.drawArc(arcRectF, -90, currentAngleSweep, true, arcPaint);
    arcPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
    canvas.drawBitmap(bitmap, circleSourceRect, circleDestRect, arcPaint);
}

arcPaint初始化如下:

arcPaint = new Paint();
arcPaint.setAntiAlias(true);
arcPaint.setColor(Color.RED); // Color doesn't matter

现在,一切都很棒,但是...整个视图中的背景是黑色的。

如果我设置canvas.drawColor(..., PorterDuff.Mode.DST)并省略canvas.drawBitmap() - 则会在透明背景上正确绘制圆弧。

我的问题是 - 如何设置PorterDuff模式以使其与透明度一起使用?

当然bitmap是带有alpha通道的32位PNG。

6 个答案:

答案 0 :(得分:28)

PorterDuff.Mode.CLEAR不支持硬件加速。只需设置

view.setLayerType(View.LAYER_TYPE_SOFTWARE, null); 

完美适合我。

答案 1 :(得分:8)

在视图初始化期间使用此语句

setLayerType(LAYER_TYPE_HARDWARE, null);

答案 2 :(得分:2)

除了一件事,你的代码中的一切都还可以:因为你的窗口是不透明的,你会得到黑色背景。要获得透明结果,您应该绘制另一个位图。在你的onDraw方法中,请创建一个新的位图并完成所有工作人员。之后在画布上绘制这个位图。

有关详细信息和示例代码,请阅读this my answer

答案 3 :(得分:1)

解决不受欢迎的PorterDuff效果
首先使用最简单的方法,就像OP的问题一样,Path.arcTo(*, *, *, *, false)就够了 - 注意它是arcTo,而不是addArc,而{{1}在添加弧之前表示false - 不需要no forceMoveTo

PorterDuff

如果你真的需要PorterDuff,主要用于复杂的颜色变形,比如混合渐变,不要将PorterDuff过滤效果的颜色或形状或位图直接绘制到Path arcPath = new Path(); @Override protected void onDraw(Canvas canvas) { arcPath.rewind(); arcPath.moveTo(arcRectF.centerX, arcRectF.centerY); arcPath.arcTo(arcRectF, -90, currentAngleSweep, false); arcPath.close(); canvas.clipPath(arcPath, Region.Op.DIFFERENCE); canvas.drawBitmap(bitmap, circleSourceRect, circleDestRect, arcPaint); } 中提供的默认画布,请使用一些缓冲/ dest位图[s]与alpha通道 - 和onDraw(Canvas) - 存储来自PorterDuff过滤的结果,最后将位图绘制到默认画布,而不应用除矩阵更改之外的任何过滤。
这是创建边框模糊圆形图像的工作示例:

setHasAlpha(true)

一些说明: 1,此视图扩展import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.RadialGradient; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.widget.ImageView; /** * Created by zdave on 6/22/17. */ public class BlurredCircleImageViewShader extends ImageView { private Canvas mCanvas; private Paint mPaint; private Matrix matrix; private static final float GRADIENT_RADIUS = 600f; //any value you like, but should be big enough for better resolution. private Shader gradientShader; private Bitmap bitmapGradient; private Bitmap bitmapDest; private Canvas canvasDest; public BlurredCircleImageViewShader(Context context) { this(context, null); } public BlurredCircleImageViewShader(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public BlurredCircleImageViewShader(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); matrix = new Matrix(); int[] colors = new int[]{Color.BLACK, Color.BLACK, Color.TRANSPARENT}; float[] colorStops = new float[]{0f, 0.5f, 1f}; gradientShader = new RadialGradient(GRADIENT_RADIUS, GRADIENT_RADIUS, GRADIENT_RADIUS, colors, colorStops, Shader.TileMode.CLAMP); mPaint.setShader(gradientShader); bitmapGradient = Bitmap.createBitmap((int)(GRADIENT_RADIUS * 2), (int)(GRADIENT_RADIUS * 2), Bitmap.Config.ARGB_8888); bitmapDest = bitmapGradient.copy(Bitmap.Config.ARGB_8888, true); Canvas canvas = new Canvas(bitmapGradient); canvas.drawRect(0, 0, GRADIENT_RADIUS * 2, GRADIENT_RADIUS * 2, mPaint); canvasDest = new Canvas(bitmapDest); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = getMeasuredWidth(); setMeasuredDimension(width, width); } @Override protected void onDraw(Canvas canvas){ /*uncomment each of them to show the effect, the first and the third one worked, the second show the same problem as OP's*/ //drawWithLayers(canvas); //unrecommended. //drawWithBitmap(canvas); //this shows transparent as black drawWithBitmapS(canvas); //recommended. } @SuppressLint("WrongCall") private void drawWithLayers(Canvas canvas){ mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); float width = canvas.getWidth(); float hWidth = width / 2; //both saveLayerAlpha saveLayer worked here, and if without either of them, //the transparent area will be black. //int count = canvas.saveLayerAlpha(0, 0, getWidth(), getHeight(), 255, Canvas.ALL_SAVE_FLAG); int count = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG); super.onDraw(canvas); float scale = hWidth/GRADIENT_RADIUS; matrix.setTranslate(hWidth - GRADIENT_RADIUS, hWidth - GRADIENT_RADIUS); matrix.postScale(scale, scale, hWidth, hWidth); gradientShader.setLocalMatrix(matrix); canvas.drawRect(0, 0, width, width, mPaint); canvas.restoreToCount(count); } @SuppressLint("WrongCall") private void drawWithBitmap(Canvas canvas){ super.onDraw(canvas); float scale = canvas.getWidth() / (GRADIENT_RADIUS * 2); matrix.setScale(scale, scale); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); canvas.drawBitmap(bitmapGradient, matrix, mPaint); //transparent area is still black. } @SuppressLint("WrongCall") private void drawWithBitmapS(Canvas canvas){ float scale = canvas.getWidth() / (GRADIENT_RADIUS * 2); int count = canvasDest.save(); canvasDest.scale(1/scale, 1/scale); //tell super to draw in 1/scale. super.onDraw(canvasDest); canvasDest.restoreToCount(count); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); canvasDest.drawBitmap(bitmapGradient, 0, 0, mPaint); matrix.setScale(scale, scale); //to scale bitmapDest to canvas. canvas.drawBitmap(bitmapDest, matrix, null); } } 而不是ImageView,存在一些差异 2,为什么View - drawWithLayerssaveLayer - 未被推荐:a,它们不确定,有时无法正常工作(显示透明为黑色),尤其是{ {1}}谁saveLayerAlpha为空,而View使用onDraw(Canvas)绘制一些人; b,它们价格昂贵,它们分配ImageView.onDraw(Canvas)来存储临时绘图结果,并且没有明确的任何资源回收机制的线索。
3,使用自己的位图[s],更适合自定义资源回收。

有些人说,在没有分配位图[s]的情况下使用PorterDuff是不可能的,因为在绘制/布局/测量之前不能确定位图的宽度,高度。登记/> 你可以使用缓冲位图[s]进行PorterDuff绘图:
首先,分配一些足够大的位图[s] 然后,用一些矩阵绘制位图[s] 然后,将位图[s]绘制成带有一些矩阵的dest位图 最后,用一些矩阵将dest位图绘制到画布中
有些人推荐使用setLayerType(View.LAYER_TYPE_SOFTWARE,null),这对我来说不是一个选项,因为它会导致在循环中调用onDraw(Canvas)。

result image   source image

答案 4 :(得分:0)

如果你有纯色背景,你需要做的就是将Paint颜色设置为背景颜色。例如,如果您有白色背景,则可以执行以下操作:

paint.setColor(Color.WHITE);

但是,如果您需要擦除具有透明背景的线条,请尝试以下操作: 要使用透明颜色进行绘制,必须使用Paint setXfermode,这只有在将位图设置到画布时才有效。如果您按照以下步骤操作,则应获得所需的结果。

创建画布并设置其位图。

mCanvas = new Canvas();
mBitmap= Bitmap.createBitmap(scrw, scrh, Config.ARGB_8888);
mCanvas.setBitmap(mBitmap);
When you want to erase something you just need to use setXfermode.

public void onClickEraser() 
{ 
   if (isEraserOn)
      mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
   else
      mPaint.setXfermode(null);
}

现在您应该可以使用以下方法绘制透明色:

mCanvas.drawPath(path, mPaint);

答案 5 :(得分:0)

只需保存画布,然后还原

canvas.saveLayer(clipContainer, null, Canvas.ALL_SAVE_FLAG);
canvas.drawRoundRect(rectTopGrey, roundCorners, roundCorners, greyPaint);
canvas.drawRoundRect(rectTopGreyClip, roundCorners, roundCorners, clipPaint);
canvas.restore();

在这种情况下,第一个矩形将作为https://developer.android.com/reference/android/graphics/PorterDuff.Mode的目标,第二个矩形将作为源