【问题标题】:How to calculate color from RadialGradient如何从 RadialGradient 计算颜色
【发布时间】:2018-06-28 10:40:15
【问题描述】:

不久前,我从 Piotr Adams 那里找到了这个很棒的颜色选择器,我在 Git 上找不到了,但它仍然在这个页面上:https://www.programcreek.com/java-api-examples/index.php?source_dir=Random-Penis-master/app/src/main/java/com/osacky/penis/picker/ColorPicker.java

我在我的应用程序中使用这个颜色选择器的主要原因是因为我希望能够根据颜色在 RadialGradient 上放置一个指针。这个库计算某种颜色的位置,这意味着将选择器放置在正确的位置非常快速和容易。

问题是我不太明白它是如何工作的。我现在想生成具有不同颜色的 RadialGradient。但是当我生成具有不同颜色的 RadialGradient 时,它使用的逻辑不起作用。

这是生成 RadialGradient 的代码:

private Bitmap createColorWheelBitmap(int width, int height) {
    Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);

    int colorCount = 12;
    int colorAngleStep = 360 / 12;
    int colors[] = new int[colorCount + 1];
    float hsv[] = new float[]{0f, 1f, 1f};
    for (int i = 0; i < colors.length; i++) {
        hsv[0] = (i * colorAngleStep + 180) % 360;
        colors[i] = Color.HSVToColor(hsv);
    }
    colors[colorCount] = colors[0];

    SweepGradient sweepGradient = new SweepGradient(width / 2, height / 2, colors, null);
    RadialGradient radialGradient = new RadialGradient(width / 2, height / 2, colorWheelRadius, 0xFFFFFFFF, 0x00FFFFFF, TileMode.CLAMP);
    ComposeShader composeShader = new ComposeShader(sweepGradient, radialGradient, PorterDuff.Mode.SRC_OVER);

    colorWheelPaint.setShader(composeShader);

    Canvas canvas = new Canvas(bitmap);
    canvas.drawCircle(width / 2, height / 2, colorWheelRadius, colorWheelPaint);

    return bitmap;
}

监听picker变化的代码,所以这里根据位置计算颜色:

@Override
public boolean onTouchEvent(MotionEvent event) {
    int action = event.getAction();
    switch (action) {
        case MotionEvent.ACTION_DOWN:
        case MotionEvent.ACTION_MOVE:
            int x = (int) event.getX();
            int y = (int) event.getY();
            int cx = x - getWidth() / 2;
            int cy = y - getHeight() / 2;
            double d = Math.sqrt(cx * cx + cy * cy);

            if (d <= colorWheelRadius) {
                colorHSV[0] = (float) (Math.toDegrees(Math.atan2(cy, cx)) + 180f);
                colorHSV[1] = Math.max(0f, Math.min(1f, (float) (d / colorWheelRadius)));
                selectedPointer.setColor(Color.HSVToColor(colorHSV));
                notifyListeners();
                invalidate();
            }

            return true;
        case MotionEvent.ACTION_BUTTON_PRESS:

    }

    return super.onTouchEvent(event);
}

最后是根据颜色计算位置的代码:

 // drawing color wheel pointer 
    float hueAngle = (float) Math.toRadians(colorHSV[0]); 
    int colorPointX = (int) (-Math.cos(hueAngle) * colorHSV[1] * colorWheelRadius) + centerX; 
    int colorPointY = (int) (-Math.sin(hueAngle) * colorHSV[1] * colorWheelRadius) + centerY; 

    float pointerRadius = 0.075f * colorWheelRadius; 
    int pointerX = (int) (colorPointX - pointerRadius / 2); 
    int pointerY = (int) (colorPointY - pointerRadius / 2); 

    colorPointerCoords.set(pointerX, pointerY, pointerX + pointerRadius, pointerY + pointerRadius); 
    canvas.drawOval(colorPointerCoords, colorPointerPaint); 

所以我的问题是,例如,我怎样才能将 RadialGradient 更改为仅包含 2 种颜色,而不会破坏获取颜色的计算?即使是关于它如何工作的解释也会很棒!

【问题讨论】:

    标签: android math calculation radial-gradients


    【解决方案1】:

    这里有很棒的教程:http://tekeye.uk/android/examples/ui/android-color-picker-tutorial(不是我的)。我也不太了解它背后的理论,但您可以使用此代码根据位置计算颜色。

    // Calculate channel based on 2 surrounding colors and p angle.
    private int ave(int s, int d, float p) {
        return s + java.lang.Math.round(p * (d - s));
    }
    
    // Calculate color based on drawn colors and angle based on x and y position.
    private int interpColor(int colors[], float unit) {
        if (unit <= 0) {
            return colors[0];
        }
        if (unit >= 1) {
            return colors[colors.length - 1];
        }
    
        // Adjust the angle (unit) based on how many colors there are in the list.
        float p = unit * (colors.length - 1);
        // Get starting color position in the array.
        int i = (int)p;
        p -= i;
    
        // Now p is just the fractional part [0...1) and i is the index.
        // Get two composite colors for calculations.
        int c0 = colors[i];
        int c1 = colors[i+1];
        // Calculate color channels.
        int a = ave(Color.alpha(c0), Color.alpha(c1), p);
        int r = ave(Color.red(c0), Color.red(c1), p);
        int g = ave(Color.green(c0), Color.green(c1), p);
        int b = ave(Color.blue(c0), Color.blue(c1), p);
    
        // And finally create the color from the channels.
        return Color.argb(a, r, g, b);
    }
    

    例如,您可以像这样调用解释函数。

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX() - CENTER_X;
        float y = event.getY() - CENTER_Y; 
    
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:   
                // Calculate the angle based on x and y positions clicked.      
                float angle = (float)java.lang.Math.atan2(y, x);
                // need to turn angle [-PI ... PI] into unit [0....1]
                float unit = angle/(2*PI);
                if (unit < 0) {
                    unit += 1;
                }
                // mColors is your list with colors so int[].
                int color = interpColor(mColors, unit);
            break;
        }
    }
    

    我已经在我的项目中尝试过它,它就像一个魅力。所以希望它也能帮助你。 :)

    编辑:

    哦,所以我的颜色是这样设置的。

    mColors = intArrayOf(-0x10000, -0xff01, -0xffff01, -0xff0001, -0xff0100, -0x100, -0x10000)
    

    因此,您可以根据需要添加/删除任意数量的颜色,并且由于解释函数根据此数组的大小进行计算,因此它应该可以工作。

    【讨论】:

    • 感谢您的回答!但恐怕我的问题并不清楚。我的目标是根据颜色获得位置,而不是相反。
    • 我正在处理同样的问题,我最终在第一个显示器上显示静态位置,在用户选择特定颜色后,我保存颜色 + 图标位置。下一个颜色选择器显示将更改颜色并将图标定位到保存的位置。
    • 当颜色不会从外部更改时,这是一个很好的解决方案,这确实发生在我的应用程序中。不过感谢您的回答!