如何使用Alpha通道绘图?

22

我想要在画布上绘制图形,使得颜色呈现加法混合。例如,我想要生成这样的效果:
Proper additive colors

但是,实际上我得到了这个:
My results

请注意,半白半黑的背景是有意为之的,只是为了观察alpha与两种不同背景的交互作用。我希望这段代码能够适用于任何一个背景。以下是我的代码:

public class VennColorsActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        class VennView extends View {
            public VennView(Context context) {
                super(context);
            }

            @Override
            protected void onDraw(Canvas canvas) {
                super.onDraw(canvas);
                int alpha = 60, val = 255;
                int ar = Color.argb(alpha, val, 0, 0);
                int ag = Color.argb(alpha, 0, val, 0);
                int ab = Color.argb(alpha, 0, 0, val);
                float w = canvas.getWidth();
                float h = canvas.getHeight();
                float cx = w / 2f;
                float cy = h / 2;
                float r = w / 5;
                float tx = (float) (r * Math.cos(30 * Math.PI / 180));
                float ty = (float) (r * Math.sin(30 * Math.PI / 180));
                float expand = 1.5f;
                Paint paint = new Paint();
                paint.setColor(Color.WHITE);
                canvas.drawRect(new Rect(0, 0, (int) w, (int) (h / 2)), paint);
                PorterDuff.Mode mode = android.graphics.PorterDuff.Mode.ADD;
                paint = new Paint(Paint.ANTI_ALIAS_FLAG);
                paint.setColorFilter(new PorterDuffColorFilter(ar, mode));
                paint.setColor(ar);
                canvas.drawCircle(cx, cy - r, expand * r, paint);
                paint.setColorFilter(new PorterDuffColorFilter(ag, mode));
                paint.setColor(ag);
                canvas.drawCircle(cx - tx, cy + ty, expand * r, paint);
                paint.setColorFilter(new PorterDuffColorFilter(ab, mode));
                paint.setColor(ab);
                canvas.drawCircle(cx + tx, cy + ty, expand * r, paint);
            }
        }
        this.setContentView(new VennView(this));
    }
}

请问有人能帮助我理解在Android图形中如何使用加色法绘画吗?

2个回答

33

你已经朝着正确的方向努力了。在你的代码中有3个主要问题:

  • 你需要设置xfer模式iso color filter
  • 使用临时位图来渲染图像
  • Alpha应该是0xFF,以获得你想要的结果

这是我使用xfer模式得到的结果。我的做法是把所有东西都画到临时位图上,然后将整个位图呈现到主画布上。

xfer mode rules

你问为什么需要临时位图?好问题!如果你在主画布上绘制所有内容,那么你的颜色将与主画布背景颜色混合,导致所有颜色混乱。透明的临时位图可以帮助保持您的颜色与UI的其他部分分开。

请确保在onDraw()中没有分配任何内容-通过这种方式很快就会耗尽内存..还要确保你在不再需要它时已回收了临时位图。

package com.example.stack2;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.os.Bundle;
import android.view.View;

public class YouAreWelcome extends Activity {

    Bitmap tempBmp = Bitmap.createBitmap(1, 1, Config.ARGB_8888);
    Canvas c = new Canvas();
    Paint paint = new Paint();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        class VennView extends View {
            public VennView(Context context) {
                super(context);

            }

            protected void onDraw(Canvas canvas) {
                super.onDraw(canvas);
                if(tempBmp.isRecycled() || tempBmp.getWidth()!=canvas.getWidth() || tempBmp.getHeight()!=canvas.getHeight())
                {
                    tempBmp.recycle();
                    tempBmp = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Config.ARGB_8888);
                    c.setBitmap(tempBmp);
                }

                    //clear previous drawings
                c.drawColor(Color.TRANSPARENT, Mode.CLEAR);

                int alpha = 255, val = 255;
                int ar = Color.argb(alpha, val, 0, 0);
                int ag = Color.argb(alpha, 0, val, 0);
                int ab = Color.argb(alpha, 0, 0, val);
                float w = canvas.getWidth();
                float h = canvas.getHeight();
                float cx = w / 2f;
                float cy = h / 2;
                float r = w / 5;
                float tx = (float) (r * Math.cos(30 * Math.PI / 180));
                float ty = (float) (r * Math.sin(30 * Math.PI / 180));
                float expand = 1.5f;
                paint.setAntiAlias(true);
                paint.setXfermode(new PorterDuffXfermode(Mode.ADD));
                paint.setColor(ar);
                c.drawCircle(cx, cy - r, expand * r, paint);
                paint.setColor(ag);
                c.drawCircle(cx - tx, cy + ty, expand * r, paint);
                paint.setColor(ab);
                c.drawCircle(cx + tx, cy + ty, expand * r, paint);
                canvas.drawBitmap(tempBmp, 0, 0, null);
            }
        }
        this.setContentView(new VennView(this));
    }
}

1
非常感谢,Pavel;而且速度也很快! - Melinda Green
3
顺便问一下,你知道有没有 PorterDuff.Mode.ADD 的替代方案,可以兼容 SDK 版本 8 吗?文档上说 ADD 是在版本 8 中添加的,但这是错误的。它实际上是在版本 11 中引入的,并且会在一些不应该安装我的小部件的旧设备上崩溃。 - Melinda Green
1
太棒了!你能将这种技术应用于重叠的位图上吗? - mal
1
@bot_bot 我希望如此,因为我可能会以那种方式重新实现我的应用程序。 - Melinda Green

3
再次感谢Pavel。如果没有你的帮助,我很难自己解决这个问题。我回答自己的问题以更好地深入细节,但是我已经接受了你的答案作为最佳答案。
你说得对,我宁愿不创建和管理屏幕外位图(和画布)。这基本上就是为什么我提到黑色或白色背景可以使其正常工作的原因。
在我看到某些东西工作之前,我从来不担心性能,但是在那之后我会分享你的警惕。编辑您的版本以修复并删除这些成员,得到以下实现。
这是否健壮?请注意两个调用Canvas.drawColor(),我认为它们也可以合并为一个。
package com.superliminal.android.test.venn;

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.os.Bundle;
import android.view.View;

public class VennColorsActivity extends Activity {
    private Paint paint = new Paint();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        class VennView extends View {
            public VennView(Context context) {
                super(context);
            }

            @Override
            protected void onDraw(Canvas canvas) {
                super.onDraw(canvas);
                canvas.drawColor(Color.BLACK);
                canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
                int alpha = 255, val = 255;
                int ar = Color.argb(alpha, val, 0, 0);
                int ag = Color.argb(alpha, 0, val, 0);
                int ab = Color.argb(alpha, 0, 0, val);
                float w = canvas.getWidth();
                float h = canvas.getHeight();
                float cx = w / 2f;
                float cy = h / 2;
                float r = w / 5;
                float tx = (float) (r * Math.cos(30 * Math.PI / 180));
                float ty = (float) (r * Math.sin(30 * Math.PI / 180));
                float expand = 1.5f;
                paint.setAntiAlias(true);
                paint.setXfermode(new PorterDuffXfermode(Mode.ADD));
                paint.setColor(ar);
                canvas.drawCircle(cx, cy - r, expand * r, paint);
                paint.setColor(ag);
                canvas.drawCircle(cx - tx, cy + ty, expand * r, paint);
                paint.setColor(ab);
                canvas.drawCircle(cx + tx, cy + ty, expand * r, paint);
            }
        }
        setContentView(new VennView(this));
    }
}

1
这个解决方案也是正确的。我建议使用单独的位图,因为通常你只想混合你想要的颜色,而不会弄乱其他UI。对于你的代码唯一的评论是canvas.drawColor(Color.BLACK);调用是无用的,因为你通过调用canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);清除了整个区域。所以理论上你可以删除这行 - Pavel Dudka
@Melinda Green,我该如何在每次按钮点击时更改活动中画布的绘制颜色,以便它可以提供不同的背景颜色? - Erum
@Erum Hannan 只需在按钮的 onClick 方法中存储一个颜色,并在 onDraw 中使用 canvas.drawColor 将其设置为背景即可。 - Melinda Green
@MelindaGreen 怎样从主活动调用 onDraw 方法?我需要在 onDraw() 方法中传递颜色参数吗? - Erum
@Erum Hannan,你不需要调用onDraw方法,但这里不是讨论的地方。请参考Activity的文档。 - Melinda Green
@Erum,你不应该调用View.onDraw(Canvas),正如@Melinda Green所指出的那样。相反,你需要调用View.invalidate()来请求UI使该视图无效。无效化过程将涉及到View.onDraw(Canvas)方法,从而实现你所要求的功能。 - Davide Cannizzo

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