合并两个 RGB 渐变

Merge two RGB gradients

我有两个要合并的 LinearGradients:

我的代码如下所示:

Shader horizontal = new LinearGradient(0, 0, width, 0, new float[]{Color.rgb(0, 0, 0), Color.rgb(0, 255, 0)}, null, Shader.TileMode.CLAMP);
Shader vertical = new LinearGradient(0, 0, 0, height, new float[]{Color.rgb(0, 0, 0), Color.rgb(0, 0, 255)}, null, Shader.TileMode.CLAMP);
ComposeShader shader = new ComposeShader(horizontal, vertical, mode);
paint.setShader(shader);

红色值可能会改变,但其他两个是不变的。我想在颜色选择器中使用生成的渐变。它必须看起来像这样:(您也可以在 here 上看到它,您必须单击颜色选择器右侧窗格中的 R 字母)

我尝试了几种 PorterDuff 模式,其中一些接近,但 none 符合我的需要。 SCREEN 近乎完美,但有时太轻了。 ADD 将小于 128 的红色值显示为 0。MULTIPLY 用一种纯色填充正方形,仅此而已。我还尝试将渐变的颜色设置为 alpha 128。这使得 ADD 太暗,XORSCREEN 太苍白。

如何正确制作这个渐变?我应该使用哪种 PorterDuff 模式?


我将光标绘制为与所选颜色相同的颜色,以测试渐变是否正确绘制。 (选中的颜色是用坐标计算的)对于除value以外的所有pivot值,光标很难see/invisible.

hsv

看起来白色渐变变透明的速度太快了。为了做到这一点,我使用了两个线性梯度,然后将它们与 ComposeShaderSRC_OVER PorterDuff 模式合并。然后我绘制一个黑色矩形,其透明度对应于值(亮度)值。如果你需要,我可以 post 编码。

编辑:

我要做一些假设。根据您引用的 link,我假设您想要做一些类似的事情,您可以使用滑块控件(如右侧的垂直滑块)实时更改“枢轴”颜色。此外,我假设您想在 red/green/blue 作为主色之间切换。

提高绩效的方法如下:

  • 为颜色分配一个 int 数组一次并重复使用该数组。
  • 分配一次位图并重新使用位图。
  • 始终将位图设为 256 x 256,并在绘制时将位图缩放到合适的大小。这样每次计算都很重要;没有重复的像素。

考虑到所有这些事情,这里是对例程的重写:

    private void changeColor(int w, int h, int[] pixels, char pivotColor, int pivotColorValue, boolean initial) {

        if (pivotColorValue < 0 || pivotColorValue > 255) {
            throw new IllegalArgumentException("color value must be between 0 and 255, was " + pivotColorValue);
        }

        if (initial) {

            // set all the bits of the color

            int alpha = 0xFF000000;

            for (int y = 0; y < h; y++) {
                for (int x = 0; x < w; x++) {
                    int r = 0, b = 0, g = 0;
                    switch (pivotColor) {
                        case 'R':
                        case 'r':
                            r = pivotColorValue << 16;
                            g = (256 * x / w) << 8;
                            b = 256 * y / h;
                            break;
                        case 'G':
                        case 'g':
                            r = (256 * x / w) << 16;
                            g = pivotColorValue << 8;
                            b = 256 * y / h;
                            break;
                        case 'B':
                        case 'b':
                            r = (256 * x / w) << 16;
                            g = (256 * y / h) << 8;
                            b = pivotColorValue;
                            break;
                    }
                    int index = y * w + x;
                    pixels[index] = alpha | r | g | b;
                }
            }
        } else {

            // only set the bits of the color that is changing

            int colorBits = 0;
            switch (pivotColor) {
                case 'R':
                case 'r':
                    colorBits = pivotColorValue << 16;
                    break;
                case 'G':
                case 'g':
                    colorBits = pivotColorValue << 8;
                    break;
                case 'B':
                case 'b':
                    colorBits = pivotColorValue;
                    break;
            }

            for (int i = 0; i < pixels.length; i++) {
                switch (pivotColor) {
                    case 'R':
                    case 'r':
                        pixels[i] = (pixels[i] & 0xFF00FFFF) | colorBits;
                        break;
                    case 'G':
                    case 'g':
                        pixels[i] = (pixels[i] & 0xFFFF00FF) | colorBits;
                        break;
                    case 'B':
                    case 'b':
                        pixels[i] = (pixels[i] & 0xFFFFFF00) | colorBits;
                        break;
                }
            }
        }

我是这样测试的:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    private ImageView mImageView;

    private Bitmap mBitmap;

    private int[] mPixels;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setTitle("Demo");

        mPixels = new int[256 * 256];
        mBitmap = Bitmap.createBitmap(256, 256, Bitmap.Config.ARGB_8888);
        mImageView = (ImageView) findViewById(R.id.imageview);

        long start = SystemClock.elapsedRealtime();

        changeColor(256, 256, mPixels, 'r', 0, true);
        mBitmap.setPixels(mPixels, 0, 256, 0, 0, 256, 256);
        mImageView.setImageBitmap(mBitmap);

        long elapsed = SystemClock.elapsedRealtime() - start;
        Log.d(TAG, "initial elapsed time: " + elapsed + " ms");

        SeekBar seekBar = (SeekBar) findViewById(R.id.seekbar);
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {

                long start = SystemClock.elapsedRealtime();

                changeColor(256, 256, mPixels, 'r', progress, false);
                mBitmap.setPixels(mPixels, 0, 256, 0, 0, 256, 256);
                mImageView.setImageBitmap(mBitmap);

                long elapsed = SystemClock.elapsedRealtime() - start;
                Log.d(TAG, "elapsed time: " + elapsed + " ms");
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) { }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) { }
        });

    }

    // changeColor method goes here
}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">

    <ImageView
        android:id="@+id/imageview"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:scaleType="fitCenter"/>

    <SeekBar
        android:id="@+id/seekbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="24dp"
        android:max="255"/>

</LinearLayout>

尝试一下,看看它的性能是否适合您。我觉得挺有道理的。


我认为底层的 Skia 库有一个 Porter-Duff 模式可以做到这一点,但它在 android.graphics.PorterDuff.Mode 中不可用。

好吧好吧,我想我们只能自己动手了:

    private Bitmap makeColorPicker(int w, int h, int r) {

        if (r < 0 || r > 255) {
            throw new IllegalArgumentException("red value must be between 0 and 255, was " + r);
        }

        // need to manage memory, OutOfMemoryError could happen here
        int[] pixels = new int[w * h];

        int baseColor = 0xFF000000 | (r << 16);  // alpha and red value

        for (int y = 0; y < h; y++) {
            for (int x = 0; x < w; x++) {
                int g = (256 * x / w) << 8;
                int b = 256 * y / h;
                int index = y * w + x;
                pixels[index] = baseColor | g | b;
            }
        }

        return Bitmap.createBitmap(pixels, w, h, Bitmap.Config.ARGB_8888);
    }

关于单纯疱疹病毒:

一旦您切换到 HSV 颜色 space,就会为您打开一些不同的选项。现在像您最初考虑的那样合成两个图像是有道理的。我只是给你千字版本的图像。请不要让我打开 PhotoShop。

  • 色调轴:

    我正在描绘可以在开发时呈现的双向渐变图像。此渐变在右上角的 alpha 为零,在底部边缘为全黑,在左上角为全白。当您移动色调角度时,您只需在该图像下方绘制一个纯色矩形。在完全饱和度和亮度下,颜色将是所需的色调,因此您会在右上角看到这种颜色。

  • 饱和度枢轴:

    这里我画了两个渐变图像,都可以在开发时渲染。第一个是完全饱和,你会看到顶部的水平彩虹在底部混合成黑色。第二种是零饱和度,顶部为白色,底部为黑色。您在底部绘制彩虹渐变,然后在顶部绘制 white/black 渐变。将顶部图像的 alpha 从零更改为完全将显示从完全饱和度到零饱和度的变化。

  • 亮度轴(值)

    为此,我正在描绘一个黑色矩形底座和另一个图像,该图像也是水平彩虹渐变,而不是垂直补间到底部的白色(全亮度)。现在,您可以通过将彩虹图像从全 alpha 更改为零 alpha 来调整亮度,从而显示下面的黑色矩形。

我必须做一些数学运算以确保这些 alpha 合成代表实际颜色 space,但我认为我已经非常接近了。