在 canvas 上绘图时,PorterDuff 源和目标指的是什么?
What do PorterDuff source and destination refer to when drawing on canvas?
我整晚都在试图解决这个问题,但在 Google 上找到的答案与 Android 的 canvas 的非常具体的问题有关,但我还没有找到关于这个主题的任何 101 条解释。甚至 Android 文档也使用位图而不是绘图形状。
具体问题:
我需要在 canvas 上画一个椭圆和一条路径。并且根据 documentation 颜色源输出一种颜色,目标输出另一种颜色和重叠区域,源输入或目标输入,第三种颜色。我正试图在屏幕外完成所有这些 canvas。但是上面的一些步骤会出现问题,并且在尝试以任何方式组合它们时会变得更糟。
代码 -
Bitmap bmp = Bitmap.CreateBitmap (720, 720, Bitmap.Config.Argb8888);
Canvas c = new Canvas (bmp);
Paint paint = new Paint ();
paint.SetARGB (255, 255, 0, 0);
c.DrawOval (200, 200, 520, 520, paint); //assumed destination
paint.SetARGB (255, 0, 0, 255);
paint.SetXfermode (new PorterDuffXfermode (PorterDuff.Mode.*)); //replace mode here
paint.SetStyle (Paint.Style.Fill);
Path path = new Path ();
path.MoveTo (c.Width / 2f, c.Height / 2f);
foreach (var m in measurements) {
//calculations
float x = xCalculatedValue
float y = yCalculatedValue
path.LineTo (x, y);
}
path.LineTo (c.Width / 2f, c.Height / 2f);
c.DrawPath (path, paint); //assumed source
源出 -
这反而绘制了 XOR 应该绘制的内容。
- 目的地出来-
这按预期工作。
- 来源 -
这会在上面绘制源代码。
- 目的地在 -
这画的目的地应该是什么。
更一般的问题:
在此上下文中,源和目标指的是什么?直觉上我假设目标是 canvas 位图的当前状态,源是 canvas.Draw* 和 Paint PortedDuff.Mode 添加的矩阵。但好像不是这样。
编辑: 这基本上就是我想要的效果,其中 "star" 是一个动态路径。根据重叠涂上三种不同的颜色。
Crude drawing
编辑 2: York Shen 很好地回答了实际问题。但对于任何想要获得类似效果的人来说,这里是最终代码。
Bitmap DrawGraphBitmapOffscreen ()
{
Bitmap bmp = Bitmap.CreateBitmap (720, 720, Bitmap.Config.Argb8888);
Canvas c = new Canvas (bmp);
// Replace with calculated path
Path path = new Path ();
path.MoveTo (c.Width / 2f, c.Height / 2f);
path.LineTo (263, 288);
path.LineTo (236, 202);
path.LineTo (312, 249);
path.LineTo (331, 162);
path.LineTo (374, 240);
path.LineTo (434, 174);
path.LineTo (431, 263);
path.LineTo (517, 236);
path.LineTo (470, 312);
path.LineTo (557, 331);
path.LineTo (479, 374);
path.LineTo (545, 434);
path.LineTo (456, 431);
path.LineTo (483, 517);
path.LineTo (407, 470);
path.LineTo (388, 557);
path.LineTo (345, 479);
path.LineTo (285, 545);
path.LineTo (288, 456);
path.LineTo (202, 483);
path.LineTo (249, 407);
path.LineTo (162, 388);
path.LineTo (240, 345);
path.LineTo (174, 285);
path.LineTo (263, 288);
path.Close ();
Paint paint = new Paint ();
paint.SetARGB (255, 255, 0, 0);
paint.SetStyle (Paint.Style.Fill);
c.DrawPath (path, paint);
paint.SetARGB (255, 0, 0, 255);
paint.SetXfermode (new PorterDuffXfermode (PorterDuff.Mode.SrcIn));
c.DrawOval (200, 200, 520, 520, paint);
paint.SetARGB (255, 255, 255, 255);
paint.SetXfermode (new PorterDuffXfermode (PorterDuff.Mode.DstOver));
c.DrawOval (200, 200, 520, 520, paint);
return bmp;
}
What do PorterDuff source and destination refer to when drawing on canvas?
经过深入研究,我写了几个demo来深入解释这一点。为了帮助您了解什么是源和目标,请参阅。
首先看下面代码:
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
Paint paint = new Paint();
//Set the background color
canvas.DrawARGB(255, 139, 197, 186);
int canvasWidth = canvas.Width;
int r = canvasWidth / 3;
//Draw a yellow circle
paint.Color = Color.Yellow;
canvas.DrawCircle(r, r, r, paint);
//Draw a blue rectangle
paint.Color = Color.Blue;
canvas.DrawRect(r, r, r * 2.7f, r * 2.7f, paint);
}
我重写OnDraw
方法,设置绿色背景,然后画黄色圆和蓝色矩形,效果:
以上是我们画一个Canvas
的正常流程,我没有用到PorterDuffXfermode
,我们来分析一下它的过程:
首先,我们调用canvas.DrawARGB(255, 139, 197, 186)
方法用单一颜色绘制整个Canvas
,这个canvas中的每个像素都具有相同的ARGB
值:(255, 139, 197, 186)
。由于 ARGB
中的 alpha 值是 255 而不是 0,所以每个像素都是不透明的。
其次,当我们执行canvas.DrawCircle(r, r, r, paint)
方法时,Android会在你定义的位置画一个黄色圆圈。该圆圈中所有ARGB值为(255,139,197,186)
的像素将被替换为黄色像素。
黄色像素是源像素,ARGB值为(255,139,197,186)
的像素是目标像素。稍后解释。
三、执行canvas.DrawRect(r, r, r * 2.7f, r * 2.7f, paint)
方法后,Android会绘制一个蓝色的矩形,这个矩形内的所有像素点都是蓝色的,这些蓝色像素点会替换其他的像素点相同的位置。所以蓝色矩形可以绘制在 Canvas
.
其次,我使用的模式是Xfermode
,PorterDuff.Mode.Clear
:
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
Paint paint = new Paint();
//Set the background color
canvas.DrawARGB(255, 139, 197, 186);
int canvasWidth = canvas.Width;
int r = canvasWidth / 3;
//Draw a yellow circle
paint.Color = Color.Yellow;
canvas.DrawCircle(r, r, r, paint);
//Use Clear as PorterDuffXfermode to draw a blue rectangle
paint.SetXfermode(new PorterDuffXfermode(PorterDuff.Mode.Clear));
paint.Color = Color.Blue;
canvas.DrawRect(r, r, r * 2.7f, r * 2.7f, paint);
paint.SetXfermode(null);
this.SetLayerType(LayerType.Software, null);
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//I found that PorterDuff.Mode.Clear doesn't work with hardware acceleration, so you have add this code
}
效果:
我们来分析一下它的过程:
首先,我们调用canvas.DrawARGB(255, 139, 197, 186)
方法将整个Canvas
画成单色,每个像素都是不透明的。
其次,我们调用canvas.DrawCircle(r, r, r, paint)
方法绘制黄色
在 Canvas
.
圈
三、执行paint.SetXfermode(new PorterDuffXfermode(PorterDuff.Mode.Clear))
,设置paintPorterDuff
模型为Clear
.
第四,调用canvas.DrawRect(r, r, r * 2.7f, r * 2.7f, paint)
绘制蓝色矩形,最后显示白色矩形。
为什么显示白色矩形?通常,当我们调用canvas.DrawXXX()
方法时,我们会传递一个Paint
参数,当Android执行draw方法时,它会检查绘画是否有Xfermode
模式。如果不是,则图形将直接覆盖Canvas
中相同位置的像素。否则,它将根据Xfermode
模式更新Canvas
中的像素。
在我的例子中,当执行canvas.DrawCirlce()
方法时,Paint
没有Xfermode
模型,所以黄色圆圈直接覆盖了Canvas
中的像素。但是当我们调用canvas.DrawRect()
画一个矩形时,Paint
有一个Xfermode
值PorterDuff.Mode.Clear
。 然后Android会在内存中画一个矩形,这个矩形中的像素有一个名字:Source。 内存中的矩形在Canvas
中有对应的矩形,对应的矩形叫做:
目的地 .
源像素的ARGB
的值和目标像素的ARGB
的值是根据Xfermode
定义的规则计算的,它将计算最终的 ARGB 值。然后用最终的ARGB
值更新目标像素的ARGB
值。
在我的例子中,Xfermode
是PorterDuff.Mode.Clear
,它需要目标像素ARGB
变成(0,0,0,0)
,这意味着它是透明的。所以我们用canvas.DrawRect()
的方法在Canvas
中画了一个透明的矩形,由于Activity
本身的底色是白色的,所以会显示一个白色的矩形。
编辑:
为了实现你post图中的功能,我写了一个demo :
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
Paint paint = new Paint();
paint.SetARGB(255, 255, 0, 0);
RectF oval2 = new RectF(60, 100, 300, 200);
canvas.DrawOval(oval2, paint);
paint.SetXfermode(new PorterDuffXfermode(PorterDuff.Mode.*));
Path path = new Path();
paint.SetStyle(Paint.Style.Fill);
paint.SetARGB(255, 0, 0, 255);
path.MoveTo(180, 50);
path.LineTo(95, 240);
path.LineTo(255, 240);
path.Close();
this.SetLayerType(LayerType.Software, null);
canvas.DrawPath(path, paint);
paint.SetXfermode(null);
}
当使用不同的Xfermode
时,它们的效果:
Xor, SrcOut, Screen, Lighten, Darken, Add.
如您所见,您可以使用不同的颜色和不同的 Xfermode
来实现您的效果。
我整晚都在试图解决这个问题,但在 Google 上找到的答案与 Android 的 canvas 的非常具体的问题有关,但我还没有找到关于这个主题的任何 101 条解释。甚至 Android 文档也使用位图而不是绘图形状。
具体问题:
我需要在 canvas 上画一个椭圆和一条路径。并且根据 documentation 颜色源输出一种颜色,目标输出另一种颜色和重叠区域,源输入或目标输入,第三种颜色。我正试图在屏幕外完成所有这些 canvas。但是上面的一些步骤会出现问题,并且在尝试以任何方式组合它们时会变得更糟。
代码 -
Bitmap bmp = Bitmap.CreateBitmap (720, 720, Bitmap.Config.Argb8888); Canvas c = new Canvas (bmp); Paint paint = new Paint (); paint.SetARGB (255, 255, 0, 0); c.DrawOval (200, 200, 520, 520, paint); //assumed destination paint.SetARGB (255, 0, 0, 255); paint.SetXfermode (new PorterDuffXfermode (PorterDuff.Mode.*)); //replace mode here paint.SetStyle (Paint.Style.Fill); Path path = new Path (); path.MoveTo (c.Width / 2f, c.Height / 2f); foreach (var m in measurements) { //calculations float x = xCalculatedValue float y = yCalculatedValue path.LineTo (x, y); } path.LineTo (c.Width / 2f, c.Height / 2f); c.DrawPath (path, paint); //assumed source
源出 -
这反而绘制了 XOR 应该绘制的内容。
- 目的地出来-
这按预期工作。
- 来源 -
这会在上面绘制源代码。
- 目的地在 -
这画的目的地应该是什么。
更一般的问题:
在此上下文中,源和目标指的是什么?直觉上我假设目标是 canvas 位图的当前状态,源是 canvas.Draw* 和 Paint PortedDuff.Mode 添加的矩阵。但好像不是这样。
编辑: 这基本上就是我想要的效果,其中 "star" 是一个动态路径。根据重叠涂上三种不同的颜色。
Crude drawing
编辑 2: York Shen 很好地回答了实际问题。但对于任何想要获得类似效果的人来说,这里是最终代码。
Bitmap DrawGraphBitmapOffscreen ()
{
Bitmap bmp = Bitmap.CreateBitmap (720, 720, Bitmap.Config.Argb8888);
Canvas c = new Canvas (bmp);
// Replace with calculated path
Path path = new Path ();
path.MoveTo (c.Width / 2f, c.Height / 2f);
path.LineTo (263, 288);
path.LineTo (236, 202);
path.LineTo (312, 249);
path.LineTo (331, 162);
path.LineTo (374, 240);
path.LineTo (434, 174);
path.LineTo (431, 263);
path.LineTo (517, 236);
path.LineTo (470, 312);
path.LineTo (557, 331);
path.LineTo (479, 374);
path.LineTo (545, 434);
path.LineTo (456, 431);
path.LineTo (483, 517);
path.LineTo (407, 470);
path.LineTo (388, 557);
path.LineTo (345, 479);
path.LineTo (285, 545);
path.LineTo (288, 456);
path.LineTo (202, 483);
path.LineTo (249, 407);
path.LineTo (162, 388);
path.LineTo (240, 345);
path.LineTo (174, 285);
path.LineTo (263, 288);
path.Close ();
Paint paint = new Paint ();
paint.SetARGB (255, 255, 0, 0);
paint.SetStyle (Paint.Style.Fill);
c.DrawPath (path, paint);
paint.SetARGB (255, 0, 0, 255);
paint.SetXfermode (new PorterDuffXfermode (PorterDuff.Mode.SrcIn));
c.DrawOval (200, 200, 520, 520, paint);
paint.SetARGB (255, 255, 255, 255);
paint.SetXfermode (new PorterDuffXfermode (PorterDuff.Mode.DstOver));
c.DrawOval (200, 200, 520, 520, paint);
return bmp;
}
What do PorterDuff source and destination refer to when drawing on canvas?
经过深入研究,我写了几个demo来深入解释这一点。为了帮助您了解什么是源和目标,请参阅。
首先看下面代码:
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
Paint paint = new Paint();
//Set the background color
canvas.DrawARGB(255, 139, 197, 186);
int canvasWidth = canvas.Width;
int r = canvasWidth / 3;
//Draw a yellow circle
paint.Color = Color.Yellow;
canvas.DrawCircle(r, r, r, paint);
//Draw a blue rectangle
paint.Color = Color.Blue;
canvas.DrawRect(r, r, r * 2.7f, r * 2.7f, paint);
}
我重写OnDraw
方法,设置绿色背景,然后画黄色圆和蓝色矩形,效果:
以上是我们画一个Canvas
的正常流程,我没有用到PorterDuffXfermode
,我们来分析一下它的过程:
首先,我们调用
canvas.DrawARGB(255, 139, 197, 186)
方法用单一颜色绘制整个Canvas
,这个canvas中的每个像素都具有相同的ARGB
值:(255, 139, 197, 186)
。由于ARGB
中的 alpha 值是 255 而不是 0,所以每个像素都是不透明的。其次,当我们执行
canvas.DrawCircle(r, r, r, paint)
方法时,Android会在你定义的位置画一个黄色圆圈。该圆圈中所有ARGB值为(255,139,197,186)
的像素将被替换为黄色像素。 黄色像素是源像素,ARGB值为(255,139,197,186)
的像素是目标像素。稍后解释。三、执行
canvas.DrawRect(r, r, r * 2.7f, r * 2.7f, paint)
方法后,Android会绘制一个蓝色的矩形,这个矩形内的所有像素点都是蓝色的,这些蓝色像素点会替换其他的像素点相同的位置。所以蓝色矩形可以绘制在Canvas
.
其次,我使用的模式是Xfermode
,PorterDuff.Mode.Clear
:
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
Paint paint = new Paint();
//Set the background color
canvas.DrawARGB(255, 139, 197, 186);
int canvasWidth = canvas.Width;
int r = canvasWidth / 3;
//Draw a yellow circle
paint.Color = Color.Yellow;
canvas.DrawCircle(r, r, r, paint);
//Use Clear as PorterDuffXfermode to draw a blue rectangle
paint.SetXfermode(new PorterDuffXfermode(PorterDuff.Mode.Clear));
paint.Color = Color.Blue;
canvas.DrawRect(r, r, r * 2.7f, r * 2.7f, paint);
paint.SetXfermode(null);
this.SetLayerType(LayerType.Software, null);
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//I found that PorterDuff.Mode.Clear doesn't work with hardware acceleration, so you have add this code
}
效果:
我们来分析一下它的过程:
首先,我们调用
canvas.DrawARGB(255, 139, 197, 186)
方法将整个Canvas
画成单色,每个像素都是不透明的。其次,我们调用
canvas.DrawCircle(r, r, r, paint)
方法绘制黄色 在Canvas
. 圈
三、执行
paint.SetXfermode(new PorterDuffXfermode(PorterDuff.Mode.Clear))
,设置paintPorterDuff
模型为Clear
.第四,调用
canvas.DrawRect(r, r, r * 2.7f, r * 2.7f, paint)
绘制蓝色矩形,最后显示白色矩形。
为什么显示白色矩形?通常,当我们调用canvas.DrawXXX()
方法时,我们会传递一个Paint
参数,当Android执行draw方法时,它会检查绘画是否有Xfermode
模式。如果不是,则图形将直接覆盖Canvas
中相同位置的像素。否则,它将根据Xfermode
模式更新Canvas
中的像素。
在我的例子中,当执行canvas.DrawCirlce()
方法时,Paint
没有Xfermode
模型,所以黄色圆圈直接覆盖了Canvas
中的像素。但是当我们调用canvas.DrawRect()
画一个矩形时,Paint
有一个Xfermode
值PorterDuff.Mode.Clear
。 然后Android会在内存中画一个矩形,这个矩形中的像素有一个名字:Source。 内存中的矩形在Canvas
中有对应的矩形,对应的矩形叫做:
目的地 .
源像素的ARGB
的值和目标像素的ARGB
的值是根据Xfermode
定义的规则计算的,它将计算最终的 ARGB 值。然后用最终的ARGB
值更新目标像素的ARGB
值。
在我的例子中,Xfermode
是PorterDuff.Mode.Clear
,它需要目标像素ARGB
变成(0,0,0,0)
,这意味着它是透明的。所以我们用canvas.DrawRect()
的方法在Canvas
中画了一个透明的矩形,由于Activity
本身的底色是白色的,所以会显示一个白色的矩形。
编辑:
为了实现你post图中的功能,我写了一个demo :
protected override void OnDraw(Canvas canvas)
{
base.OnDraw(canvas);
Paint paint = new Paint();
paint.SetARGB(255, 255, 0, 0);
RectF oval2 = new RectF(60, 100, 300, 200);
canvas.DrawOval(oval2, paint);
paint.SetXfermode(new PorterDuffXfermode(PorterDuff.Mode.*));
Path path = new Path();
paint.SetStyle(Paint.Style.Fill);
paint.SetARGB(255, 0, 0, 255);
path.MoveTo(180, 50);
path.LineTo(95, 240);
path.LineTo(255, 240);
path.Close();
this.SetLayerType(LayerType.Software, null);
canvas.DrawPath(path, paint);
paint.SetXfermode(null);
}
当使用不同的Xfermode
时,它们的效果:
Xor, SrcOut, Screen, Lighten, Darken, Add.
如您所见,您可以使用不同的颜色和不同的 Xfermode
来实现您的效果。