在画布上绘制时,PorterDuff的源和目标是指什么?

5
我一整晚都在试图解决这个问题,但是在谷歌上找到的答案都和Android的画布(canvas)有关,而且没有找到任何关于这个主题的101级别的解释。即使是Android文档也使用位图(bitmap)而不是绘制形状。
具体问题:
我需要在画布(canvas)上绘制一个椭圆形和路径。根据文档,使用一个颜色作为源颜色,另一个颜色作为目标颜色,并将重叠区域中的源颜色或目标颜色设为第三种颜色。我尝试在离屏画布(offscreen canvas)上完成所有这些操作,但在执行以上某些步骤时会出现问题,而且在任何组合方式下问题都会变得更加严重。
  • Code -

        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
    
  • Source out -

这个代码绘制了XOR的预期效果。

  • 目标图形 -

该功能按预期工作。

  • 源图形 -

这绘制了源图形应该出现的内容。

  • 目标图形 -

这绘制了目标图形应该出现的内容。

更一般的问题:

在这个上下文中,源和目标是什么意思?直觉上,我会认为目标是画布位图的当前状态,源是由canvas.Draw*和Paint PortedDuff.Mode添加的矩阵。但事实似乎并非如此。

编辑:这基本上就是我想要的效果,其中“星”是动态路径。根据重叠情况涂上三种不同的颜色。

粗略的绘图

编辑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;
}

你想要什么效果,在画布上绘制一个椭圆和路径?为什么要使用 PorterDuffXfermode?如果只需要绘制一个椭圆和路径,就不需要使用 PorterDuffX - York Shen
我添加了一张图片来解释我想做的事情。我不确定 alpha compositing 是最好的解决方案,但它是我想到的唯一一个。除了实际解决问题之外,我更想了解在画布上依次绘制东西的背景和目标是什么意思。 - JaanTohver
所以你想要画一个椭圆和一个多边形,然后使用 PorterDuffXfermode 来实现你发布的图片中的效果? - York Shen
这就是问题所在。正如我所解释的,模式并没有按照文档所说的那样表现。我认为问题在于我不理解源和目标指的是什么。你介意给一个快速解释吗?或者提供一段代码以实现图片中的结果? - JaanTohver
1个回答

12

当在画布上绘制时,PorterDuff源和目标是什么意思?

经过深入研究后,我编写了一些演示来深入解释这个问题。为了帮助您理解源和目标的含义。

首先,请看以下代码:

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方法,设置了绿色背景,然后绘制了一个黄色圆和一个蓝色矩形,效果如下:

enter image description here

上述是我们绘制Canvas的正常步骤,我没有使用任何PorterDuffXfermode,让我们分析其过程:
  • 首先,我们调用canvas.DrawARGB(255, 139, 197, 186)方法,用单一颜色绘制整个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
  }

效果:

enter image description here

让我们分析其过程:
  • 首先,我们调用canvas.DrawARGB(255, 139, 197, 186)方法将整个画布绘制为单色,每个像素都是不透明的。

  • 其次,我们调用canvas.DrawCircle(r, r, r, paint)方法在Canvas中绘制一个黄色圆形。

  • 第三步,执行paint.SetXfermode(new PorterDuffXfermode(PorterDuff.Mode.Clear)),将画笔的PorterDuff模式设置为Clear

  • 第四步,调用canvas.DrawRect(r, r, r * 2.7f, r * 2.7f, paint)绘制一个蓝色矩形,最后显示一个白色矩形。

为什么会显示一个白色矩形?通常,当我们调用canvas.DrawXXX()方法时,会传递一个Paint参数,当Android执行绘制方法时,它会检查画笔是否具有Xfermode模式。如果没有,则图形将直接覆盖在Canvas中相同位置的像素上。否则,它将根据Xfermode模式更新Canvas中的像素。

在我的例子中,当执行canvas.DrawCircle()方法时,Paint没有Xfermode模式,所以黄色圆直接覆盖了Canvas中的像素。但是当我们调用canvas.DrawRect()来绘制一个矩形时,Paint具有XfermodePorterDuff.Mode.Clear那么Android会在内存中绘制一个矩形,这个矩形中的像素有一个名称:源。内存中的矩形与Canvas中的对应矩形称为:目标。

根据Xfermode定义的规则计算源像素的ARGB值和目标像素的ARGB值,它将计算出最终的ARGB值。然后使用最终的ARGB值更新目标像素的ARGB值。

在我的例子中,XfermodePorterDuff.Mode.Clear,它要求目标像素的ARGB变为(0,0,0,0),也就是透明的。因此,我们使用canvas.DrawRect()方法在Canvas中绘制一个透明的矩形,由于Activity本身具有白色背景颜色,因此它将显示一个白色矩形。

编辑:

为了实现您在图片中发布的功能,我写了一个演示:

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时,它们的效果如下:

异或, 源图像去除交集, 屏幕, 变亮, 变暗, 添加

正如您所见,您可以使用不同的颜色和不同的Xfermode来实现您想要的效果。


3
太棒了,非常感谢。"然后Android会在内存中绘制一个矩形,这个矩形内的像素有一个名字叫做:源(Source)。内存中的矩形在画布(Canvas)上有一个对应的矩形,这个对应的矩形被称为目标(destination)"。这正是我想知道的。我原以为目标是整个画布。 - JaanTohver
@York Shen,如果可能的话,你能分享一下你的邮箱吗? - Pramod Waghmare

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接