mirror of
https://github.com/peterosterlund2/droidfish.git
synced 2024-11-27 06:10:28 +01:00
More accurate conversion between HSV and RGB color spaces
This is needed to avoid problems when dragging one of the R/G/B sliders. With the default Android conversion code, the other R/G/B sliders sometimes move during dragging.
This commit is contained in:
parent
4dcc24cee7
commit
3969a9e841
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright (C) 2020 Peter Österlund
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.margaritov.preference.colorpicker;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
public class AHSVColorTest {
|
||||
@BeforeClass
|
||||
public static void setUpClass() {
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDownClass() {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void alpha() {
|
||||
AHSVColor color = new AHSVColor();
|
||||
for (int i = 0; i < 255; i++) {
|
||||
color.setAlpha(i);
|
||||
assertEquals(i, color.getAlpha());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hsv() {
|
||||
AHSVColor color = new AHSVColor();
|
||||
double[] hsv = new double[3];
|
||||
for (int i = 0; i < 360; i++) {
|
||||
hsv[0] = i;
|
||||
hsv[1] = (i % 17) / 17;
|
||||
hsv[2] = (i % 11) / 11;
|
||||
color.setHSV(hsv);
|
||||
double[] ret = color.getHSV();
|
||||
assertEquals(hsv[0], ret[0], 1e-10);
|
||||
assertEquals(hsv[1], ret[1], 1e-10);
|
||||
assertEquals(hsv[2], ret[2], 1e-10);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rgb() {
|
||||
AHSVColor color = new AHSVColor();
|
||||
for (int i = 0; i < 255; i++) {
|
||||
int r = (i * 3413) % 255;
|
||||
int g = (i * 113) % 255;
|
||||
int b = (i * 1847) % 255;
|
||||
int c = 0xff000000 + (r << 16) + (g << 8) + b;
|
||||
color.setARGB(c);
|
||||
int c2 = color.getARGB();
|
||||
int r2 = (c2 & 0x00ff0000) >>> 16;
|
||||
int g2 = (c2 & 0x0000ff00) >>> 8;
|
||||
int b2 = (c2 & 0x000000ff);
|
||||
assertEquals(r, r2);
|
||||
assertEquals(g, g2);
|
||||
assertEquals(b, b2);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,15 +18,15 @@ package net.margaritov.preference.colorpicker;
|
|||
|
||||
import android.graphics.Color;
|
||||
|
||||
/** Represents a color in HSV float format and an alpha value. */
|
||||
/** Represents a color in HSV double format and an integer alpha value (0-255). */
|
||||
class AHSVColor {
|
||||
private int alpha = 0xff;
|
||||
private float[] hsv = new float[]{360f, 0f, 0f};
|
||||
private double[] hsv = new double[]{360, 0, 0};
|
||||
|
||||
AHSVColor() { }
|
||||
|
||||
/** Set hue,sat,val values. Preserve alpha. */
|
||||
void setHSV(float[] hsv) {
|
||||
void setHSV(double[] hsv) {
|
||||
this.hsv[0] = hsv[0];
|
||||
this.hsv[1] = hsv[1];
|
||||
this.hsv[2] = hsv[2];
|
||||
|
@ -43,8 +43,8 @@ class AHSVColor {
|
|||
int r = Color.red(color);
|
||||
int g = Color.green(color);
|
||||
int b = Color.blue(color);
|
||||
float oldHue = hsv[0];
|
||||
Color.RGBToHSV(r, g, b, hsv);
|
||||
double oldHue = hsv[0];
|
||||
ARGBToHSV(Color.rgb(r, g, b), hsv);
|
||||
if (hsv[1] <= 0f)
|
||||
hsv[0] = oldHue;
|
||||
}
|
||||
|
@ -67,8 +67,8 @@ class AHSVColor {
|
|||
}
|
||||
|
||||
/** Get hue,sat,val values. */
|
||||
float[] getHSV() {
|
||||
return new float[]{hsv[0], hsv[1], hsv[2]};
|
||||
double[] getHSV() {
|
||||
return new double[]{hsv[0], hsv[1], hsv[2]};
|
||||
}
|
||||
|
||||
/** Get alpha value. */
|
||||
|
@ -78,7 +78,7 @@ class AHSVColor {
|
|||
|
||||
/** Get ARGB color value. */
|
||||
int getARGB() {
|
||||
return Color.HSVToColor(alpha, hsv);
|
||||
return HSVToARGB(alpha, hsv);
|
||||
}
|
||||
|
||||
/** Get red (0), green (1), or blue (2) color component. */
|
||||
|
@ -91,4 +91,72 @@ class AHSVColor {
|
|||
default: throw new RuntimeException("Internal error");
|
||||
}
|
||||
}
|
||||
|
||||
private static int HSVToARGB(int alpha, double[] hsv) {
|
||||
double h = hsv[0] % 360;
|
||||
double s = hsv[1];
|
||||
double v = hsv[2];
|
||||
double c = v * s;
|
||||
double m = v - c;
|
||||
double x = c * (1 - Math.abs(((h / 60.0) % 2) - 1));
|
||||
|
||||
double r = 0, g = 0, b = 0;
|
||||
switch ((int)Math.floor(h / 60.0)) {
|
||||
case 0: r = c; g = x; break;
|
||||
case 1: r = x; g = c; break;
|
||||
case 2: g = c; b = x; break;
|
||||
case 3: g = x; b = c; break;
|
||||
case 4: r = x; b = c; break;
|
||||
case 5: r = c; b = x; break;
|
||||
}
|
||||
|
||||
int red = Math.min(Math.max((int)Math.round((r + m) * 255), 0), 255);
|
||||
int green = Math.min(Math.max((int)Math.round((g + m) * 255), 0), 255);
|
||||
int blue = Math.min(Math.max((int)Math.round((b + m) * 255), 0), 255);
|
||||
|
||||
return Color.argb(alpha, red, green, blue);
|
||||
}
|
||||
|
||||
private static void ARGBToHSV(int color, double[] hsv) {
|
||||
int red = Color.red(color);
|
||||
int green = Color.green(color);
|
||||
int blue = Color.blue(color);
|
||||
|
||||
double r = red / 255.0;
|
||||
double g = green / 255.0;
|
||||
double b = blue / 255.0;
|
||||
|
||||
int maxI = 0;
|
||||
double cMax = r;
|
||||
if (cMax < g) {
|
||||
cMax = g;
|
||||
maxI = 1;
|
||||
}
|
||||
if (cMax < b) {
|
||||
cMax = b;
|
||||
maxI = 2;
|
||||
}
|
||||
|
||||
double cMin = Math.min(Math.min(r, g), b);
|
||||
double d = cMax - cMin;
|
||||
|
||||
double v = cMax;
|
||||
double s = (cMax > 0) ? (d / cMax) : 0.0;
|
||||
double h;
|
||||
if (d <= 0) {
|
||||
h = 0;
|
||||
} else if (maxI == 0) {
|
||||
h = 60 * (g - b) / d;
|
||||
if (h < 0)
|
||||
h += 360;
|
||||
} else if (maxI == 1) {
|
||||
h = 60 * ((b - r) / d + 2);
|
||||
} else {
|
||||
h = 60 * ((r - g) / d + 4);
|
||||
}
|
||||
|
||||
hsv[0] = h;
|
||||
hsv[1] = s;
|
||||
hsv[2] = v;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,19 +56,19 @@ class AlphaGradientPanel extends GradientPanel {
|
|||
|
||||
@Override
|
||||
void updateColor(Point point) {
|
||||
int alpha = pointToAlpha(point.x);
|
||||
int alpha = pointToAlpha(point);
|
||||
color.setAlpha(alpha);
|
||||
}
|
||||
|
||||
private Point alphaToPoint(int alpha) {
|
||||
float width = rect.width();
|
||||
return new Point((int)(width - (alpha * width / 0xff) + rect.left),
|
||||
(int)rect.top);
|
||||
double width = rect.width();
|
||||
return new Point((int)Math.round(width - (alpha * width / 0xff) + rect.left),
|
||||
Math.round(rect.top));
|
||||
}
|
||||
|
||||
private int pointToAlpha(int x) {
|
||||
private int pointToAlpha(Point p) {
|
||||
int width = (int)rect.width();
|
||||
x = Math.min(Math.max(x - (int)rect.left, 0), width);
|
||||
int x = Math.min(Math.max(p.x - (int)rect.left, 0), width);
|
||||
return 0xff - (x * 0xff / width);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,20 +51,20 @@ public class HueGradientPanel extends GradientPanel {
|
|||
|
||||
@Override
|
||||
void updateColor(Point point) {
|
||||
float[] hsv = color.getHSV();
|
||||
hsv[0] = pointToHue(point.y);
|
||||
double[] hsv = color.getHSV();
|
||||
hsv[0] = pointToHue(point);
|
||||
color.setHSV(hsv);
|
||||
}
|
||||
|
||||
private Point hueToPoint(float hue) {
|
||||
float height = rect.height();
|
||||
return new Point((int)rect.left,
|
||||
(int)(height - (hue * height / 360f) + rect.top));
|
||||
private Point hueToPoint(double hue) {
|
||||
double height = rect.height();
|
||||
return new Point(Math.round(rect.left),
|
||||
(int)Math.round((height - (hue * height / 360) + rect.top)));
|
||||
}
|
||||
|
||||
private float pointToHue(float y) {
|
||||
float height = rect.height();
|
||||
y = Math.min(Math.max(y - rect.top, 0f), height);
|
||||
return 360f - (y * 360f / height);
|
||||
private double pointToHue(Point p) {
|
||||
double height = rect.height();
|
||||
double y = Math.min(Math.max(p.y - rect.top, 0f), height);
|
||||
return 360 - (y * 360 / height);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package net.margaritov.preference.colorpicker;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.LinearGradient;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.RectF;
|
||||
|
@ -56,13 +55,13 @@ public class RGBGradientPanel extends GradientPanel {
|
|||
|
||||
private Point rgbComponentToPoint(int val) {
|
||||
if (horizontal) {
|
||||
float width = rect.width();
|
||||
return new Point((int)((val * width / 0xff) + rect.left),
|
||||
(int)rect.top);
|
||||
double width = rect.width();
|
||||
return new Point((int)Math.round((val * width / 0xff) + rect.left),
|
||||
Math.round(rect.top));
|
||||
} else {
|
||||
float height = rect.height();
|
||||
return new Point((int)rect.left,
|
||||
(int)(rect.bottom - (val * height / 0xff)));
|
||||
double height = rect.height();
|
||||
return new Point(Math.round(rect.left),
|
||||
(int)Math.round(rect.bottom - (val * height / 0xff)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ package net.margaritov.preference.colorpicker;
|
|||
import android.graphics.Canvas;
|
||||
import android.graphics.ComposeShader;
|
||||
import android.graphics.LinearGradient;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.RectF;
|
||||
|
@ -42,9 +41,9 @@ public class SatValGradientPanel extends GradientPanel {
|
|||
|
||||
@Override
|
||||
protected void setGradientPaint() {
|
||||
float[] hsv = color.getHSV();
|
||||
hsv[1] = 1f;
|
||||
hsv[2] = 1f;
|
||||
double[] hsv = color.getHSV();
|
||||
hsv[1] = 1;
|
||||
hsv[2] = 1;
|
||||
AHSVColor hue = new AHSVColor();
|
||||
hue.setHSV(hsv);
|
||||
Shader satShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top,
|
||||
|
@ -55,7 +54,7 @@ public class SatValGradientPanel extends GradientPanel {
|
|||
|
||||
@Override
|
||||
protected void drawTracker(Canvas canvas) {
|
||||
float[] hsv = color.getHSV();
|
||||
double[] hsv = color.getHSV();
|
||||
Point p = satValToPoint(hsv[1], hsv[2]);
|
||||
|
||||
float r = PALETTE_CIRCLE_TRACKER_RADIUS;
|
||||
|
@ -67,28 +66,28 @@ public class SatValGradientPanel extends GradientPanel {
|
|||
|
||||
@Override
|
||||
void updateColor(Point point) {
|
||||
float[] hsv = color.getHSV();
|
||||
float[] result = pointToSatVal(point.x, point.y);
|
||||
double[] hsv = color.getHSV();
|
||||
double[] result = pointToSatVal(point);
|
||||
hsv[1] = result[0];
|
||||
hsv[2] = result[1];
|
||||
color.setHSV(hsv);
|
||||
}
|
||||
|
||||
private Point satValToPoint(float sat, float val) {
|
||||
final float width = rect.width();
|
||||
final float height = rect.height();
|
||||
private Point satValToPoint(double sat, double val) {
|
||||
double width = rect.width();
|
||||
double height = rect.height();
|
||||
|
||||
return new Point((int)(sat * width + rect.left),
|
||||
(int)((1f - val) * height + rect.top));
|
||||
return new Point((int)Math.round(sat * width + rect.left),
|
||||
(int)Math.round((1 - val) * height + rect.top));
|
||||
}
|
||||
|
||||
private float[] pointToSatVal(float x, float y) {
|
||||
float width = rect.width();
|
||||
float height = rect.height();
|
||||
private double[] pointToSatVal(Point p) {
|
||||
double width = rect.width();
|
||||
double height = rect.height();
|
||||
|
||||
x = Math.min(Math.max(x - rect.left, 0f), width);
|
||||
y = Math.min(Math.max(y - rect.top, 0f), height);
|
||||
double x = Math.min(Math.max(p.x - rect.left, 0), width);
|
||||
double y = Math.min(Math.max(p.y - rect.top, 0), height);
|
||||
|
||||
return new float[]{ x / width, 1f - y / height };
|
||||
return new double[]{ x / width, 1 - y / height };
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user