How to calculate color from RadialGradient

0

A while back I found this great color picker from Piotr Adams which I can not find on Git anymore but it's still on this page: https://www.programcreek.com/java-api-examples/index.php?source_dir=Random-Penis-master/app/src/main/java/com/osacky/penis/picker/ColorPicker.java

The main reason I use this color picker in my app is because I want to be able to place a pointer on the RadialGradient based on a color. This library calculates the position for a certain color, this means placing a picker on the correct location is very fast and easy.

The problem is I don't quite understand how it works. I now want to generate a RadialGradient with different colors. But the logic it uses does not work when I generate a RadialGradient with different colors.

Here is the code that generates the 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;
}

The code for listening to changes of the picker, so this calculates the color based on a position:

@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);
}

Finally the code that calculates the position based on a color:

 // 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); 

So my question is how can I for example change the RadialGradient to only include 2 colors, without breaking the calculations of getting the color? Even an explanation on how this works would be great!

android
math
calculation
radial-gradients
asked on Stack Overflow Jun 28, 2018 by Beuz

1 Answer

0

There is great tutorial here: http://tekeye.uk/android/examples/ui/android-color-picker-tutorial (not mine). I don't know much about the theory behind it either but you can use this code to calculate color based on position.

// 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);
}

You can call the interpreting function like this for example.

@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;
    }
}

I already tried it in my project and it works like a charm. So hope it helps you too. :)

EDIT:

Oh so my colors are set up like this.

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

So you can add/remove as many colors as you want and since the interpret functions calculates based on size of this array it should work.

answered on Stack Overflow Aug 2, 2018 by David Sucharda

User contributions licensed under CC BY-SA 3.0