diff --git a/.gitignore b/.gitignore
index 322456c..0635683 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
*.iml
+/.vscode
target/
settings.xml
pom.xml.tag
diff --git a/README.md b/README.md
index b57f512..7123d5c 100644
--- a/README.md
+++ b/README.md
@@ -11,12 +11,53 @@ If you are using Maven, add the following to your `pom.xml` file:
org.hsluv
hsluv
- 0.2
+ 1.0
-
-# Documentation
-Javadocs: http://www.javadoc.io/doc/org.hsluv/hsluv
+# Usage
+
+The API is designed to avoid heap allocation. The `HSLuv` class defines the following public fields:
+
+- RGB: `hex:String`, `rgb_r` [0;1], `rgb_g` [0;1], `rgb_r` [0;1]
+- CIE XYZ: `xyz_x`, `xyz_y`, `xyz_z`
+- CIE LUV: `luv_l`, `luv_u`, `luv_v`
+- CIE LUV LCh: `lch_l`, `lch_c`, `lch_h`
+- HSLuv: `hsluv_h` [0;360], `hsluv_s` [0;100], `hsluv_l` [0;100]
+- HPLuv: `hpluv_h` [0;360], `hpluv_p` [0;100], `hpluv_l` [0;100]
+
+To convert between color spaces, simply set the properties of the source color space, run the
+conversion methods, then read the properties of the target color space.
+
+Use the following methods to convert to and from RGB:
+
+- HSLuv: `hsluvToRgb()`, `hsluvToHex()`, `rgbToHsluv()`, `hexToHsluv()`
+- HPLuv: `hpluvToRgb()`, `hpluvToHex()`, `rgbToHpluv()`, `hexToHpluv()`
+
+Use the following methods to do step-by-step conversion:
+
+- Forward: `hsluvToLch()` (or `hpluvToLch()`), `lchToLuv()`, `luvToXyz()`, `xyzToRgb()`, `rgbToHex()`
+- Backward: `hexToRgb()`, `rgbToXyz()`, `xyzToLuv()`, `luvToLch()`, `lchToHsluv()` (or `lchToHpluv()`)
+
+For advanced usage, we also export the [bounding lines](https://www.hsluv.org/math/) in slope-intercept
+format, two for each RGB channel representing the limit of the gamut.
+
+- R < 0: `r0s`, `r0i`
+- R > 1: `r1s`, `r1i`
+- G < 0: `g0s`, `g0i`
+- G > 1: `g1s`, `g1i`
+- B < 0: `b0s`, `b0i`
+- B > 1: `b1s`, `b1i`
+
+Example:
+
+```java
+HsluvColorConverter conv = new HsluvColorConverter();
+conv.hsluv_h = 10;
+conv.hsluv_s = 75;
+conv.hsluv_l = 65;
+conv.hsluvToHex();
+System.out.println(conv.hex); // Will print "#ec7d82"
+```
# Testing
@@ -24,7 +65,8 @@ Javadocs: http://www.javadoc.io/doc/org.hsluv/hsluv
# Deployment
-Docs:
+Docs:
+
- https://central.sonatype.org/publish/publish-maven/
- https://central.sonatype.org/publish/requirements/gpg/
@@ -59,4 +101,3 @@ Then run:
mvn versions:set -DnewVersion=0.3 # bump version
mvn clean deploy -P release
```
-
diff --git a/pom.xml b/pom.xml
index 2fd412f..f1c00da 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,11 +1,12 @@
-
+
4.0.0
org.hsluv
hsluv
jar
- 0.3
+ 1.0
hsluv
Human-friendly HSL
@@ -48,12 +49,6 @@
4.13.2
test
-
- javax
- javaee-api
- 8.0.1
- test
-
org.glassfish
jakarta.json
@@ -121,8 +116,8 @@
sign
- 0xD54740FB
- 0xD54740FB
+ EBFD22439E5C59D664D0CFC750617B51F61187C2
+ EBFD22439E5C59D664D0CFC750617B51F61187C2
--pinentry-mode
loopback
@@ -133,4 +128,4 @@
-
+
\ No newline at end of file
diff --git a/src/main/java/org/hsluv/HUSLColorConverter.java b/src/main/java/org/hsluv/HUSLColorConverter.java
deleted file mode 100644
index faca3a6..0000000
--- a/src/main/java/org/hsluv/HUSLColorConverter.java
+++ /dev/null
@@ -1,415 +0,0 @@
-package org.hsluv;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class HUSLColorConverter {
- private static double[][] m = new double[][]
- {
- new double[]{3.240969941904521, -1.537383177570093, -0.498610760293},
- new double[]{-0.96924363628087, 1.87596750150772, 0.041555057407175},
- new double[]{0.055630079696993, -0.20397695888897, 1.056971514242878},
- };
-
- private static double[][] minv = new double[][]
- {
- new double[]{0.41239079926595, 0.35758433938387, 0.18048078840183},
- new double[]{0.21263900587151, 0.71516867876775, 0.072192315360733},
- new double[]{0.019330818715591, 0.11919477979462, 0.95053215224966},
- };
-
- private static double refY = 1.0;
-
- private static double refU = 0.19783000664283;
- private static double refV = 0.46831999493879;
-
- private static double kappa = 903.2962962;
- private static double epsilon = 0.0088564516;
-
- private static List getBounds(double L) {
- ArrayList result = new ArrayList();
-
- double sub1 = Math.pow(L + 16, 3) / 1560896;
- double sub2 = sub1 > epsilon ? sub1 : L / kappa;
-
- for (int c = 0; c < 3; ++c) {
- double m1 = m[c][0];
- double m2 = m[c][1];
- double m3 = m[c][2];
-
- for (int t = 0; t < 2; ++t) {
- double top1 = (284517 * m1 - 94839 * m3) * sub2;
- double top2 = (838422 * m3 + 769860 * m2 + 731718 * m1) * L * sub2 - 769860 * t * L;
- double bottom = (632260 * m3 - 126452 * m2) * sub2 + 126452 * t;
-
- result.add(new double[]{top1 / bottom, top2 / bottom});
- }
- }
-
- return result;
- }
-
- private static double intersectLineLine(double[] lineA, double[] lineB) {
- return (lineA[1] - lineB[1]) / (lineB[0] - lineA[0]);
- }
-
- private static double distanceFromPole(double[] point) {
- return Math.sqrt(Math.pow(point[0], 2) + Math.pow(point[1], 2));
- }
-
- private static Length lengthOfRayUntilIntersect(double theta, double[] line) {
- double length = line[1] / (Math.sin(theta) - line[0] * Math.cos(theta));
-
- return new Length(length);
- }
-
- private static class Length {
- final boolean greaterEqualZero;
- final double length;
-
-
- private Length(double length) {
- this.greaterEqualZero = length >= 0;
- this.length = length;
- }
- }
-
- private static double maxSafeChromaForL(double L) {
- List bounds = getBounds(L);
- double min = Double.MAX_VALUE;
-
- for (int i = 0; i < 2; ++i) {
- double m1 = bounds.get(i)[0];
- double b1 = bounds.get(i)[1];
- double[] line = new double[]{m1, b1};
-
- double x = intersectLineLine(line, new double[]{-1 / m1, 0});
- double length = distanceFromPole(new double[]{x, b1 + x * m1});
-
- min = Math.min(min, length);
- }
-
- return min;
- }
-
- private static double maxChromaForLH(double L, double H) {
- double hrad = H / 360 * Math.PI * 2;
-
- List bounds = getBounds(L);
- double min = Double.MAX_VALUE;
-
- for (double[] bound : bounds) {
- Length length = lengthOfRayUntilIntersect(hrad, bound);
- if (length.greaterEqualZero) {
- min = Math.min(min, length.length);
- }
- }
-
- return min;
- }
-
- private static double dotProduct(double[] a, double[] b) {
- double sum = 0;
-
- for (int i = 0; i < a.length; ++i) {
- sum += a[i] * b[i];
- }
-
- return sum;
- }
-
- private static double round(double value, int places) {
- double n = Math.pow(10, places);
-
- return Math.round(value * n) / n;
- }
-
- private static double fromLinear(double c) {
- if (c <= 0.0031308) {
- return 12.92 * c;
- } else {
- return 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
- }
- }
-
- private static double toLinear(double c) {
- if (c > 0.04045) {
- return Math.pow((c + 0.055) / (1 + 0.055), 2.4);
- } else {
- return c / 12.92;
- }
- }
-
- private static int[] rgbPrepare(double[] tuple) {
-
- int[] results = new int[tuple.length];
-
- for (int i = 0; i < tuple.length; ++i) {
- double chan = tuple[i];
- double rounded = round(chan, 3);
-
- if (rounded < -0.0001 || rounded > 1.0001) {
- throw new IllegalArgumentException("Illegal rgb value: " + rounded);
- }
-
- results[i] = (int) Math.round(rounded * 255);
- }
-
- return results;
- }
-
- public static double[] xyzToRgb(double[] tuple) {
- return new double[]
- {
- fromLinear(dotProduct(m[0], tuple)),
- fromLinear(dotProduct(m[1], tuple)),
- fromLinear(dotProduct(m[2], tuple)),
- };
- }
-
- public static double[] rgbToXyz(double[] tuple) {
- double[] rgbl = new double[]
- {
- toLinear(tuple[0]),
- toLinear(tuple[1]),
- toLinear(tuple[2]),
- };
-
- return new double[]
- {
- dotProduct(minv[0], rgbl),
- dotProduct(minv[1], rgbl),
- dotProduct(minv[2], rgbl),
- };
- }
-
- private static double yToL(double Y) {
- if (Y <= epsilon) {
- return (Y / refY) * kappa;
- } else {
- return 116 * Math.pow(Y / refY, 1.0 / 3.0) - 16;
- }
- }
-
- private static double lToY(double L) {
- if (L <= 8) {
- return refY * L / kappa;
- } else {
- return refY * Math.pow((L + 16) / 116, 3);
- }
- }
-
- public static double[] xyzToLuv(double[] tuple) {
- double X = tuple[0];
- double Y = tuple[1];
- double Z = tuple[2];
-
- double varU = (4 * X) / (X + (15 * Y) + (3 * Z));
- double varV = (9 * Y) / (X + (15 * Y) + (3 * Z));
-
- double L = yToL(Y);
-
- if (L == 0) {
- return new double[]{0, 0, 0};
- }
-
- double U = 13 * L * (varU - refU);
- double V = 13 * L * (varV - refV);
-
- return new double[]{L, U, V};
- }
-
- public static double[] luvToXyz(double[] tuple) {
- double L = tuple[0];
- double U = tuple[1];
- double V = tuple[2];
-
- if (L == 0) {
- return new double[]{0, 0, 0};
- }
-
- double varU = U / (13 * L) + refU;
- double varV = V / (13 * L) + refV;
-
- double Y = lToY(L);
- double X = 0 - (9 * Y * varU) / ((varU - 4) * varV - varU * varV);
- double Z = (9 * Y - (15 * varV * Y) - (varV * X)) / (3 * varV);
-
- return new double[]{X, Y, Z};
- }
-
- public static double[] luvToLch(double[] tuple) {
- double L = tuple[0];
- double U = tuple[1];
- double V = tuple[2];
-
- double C = Math.sqrt(U * U + V * V);
- double H;
-
- if (C < 0.00000001) {
- H = 0;
- } else {
- double Hrad = Math.atan2(V, U);
-
- // pi to more digits than they provide it in the stdlib
- H = (Hrad * 180.0) / 3.1415926535897932;
-
- if (H < 0) {
- H = 360 + H;
- }
- }
-
- return new double[]{L, C, H};
- }
-
- public static double[] lchToLuv(double[] tuple) {
- double L = tuple[0];
- double C = tuple[1];
- double H = tuple[2];
-
- double Hrad = H / 360.0 * 2 * Math.PI;
- double U = Math.cos(Hrad) * C;
- double V = Math.sin(Hrad) * C;
-
- return new double[]{L, U, V};
- }
-
- public static double[] hsluvToLch(double[] tuple) {
- double H = tuple[0];
- double S = tuple[1];
- double L = tuple[2];
-
- if (L > 99.9999999) {
- return new double[]{100d, 0, H};
- }
-
- if (L < 0.00000001) {
- return new double[]{0, 0, H};
- }
-
- double max = maxChromaForLH(L, H);
- double C = max / 100 * S;
-
- return new double[]{L, C, H};
- }
-
- public static double[] lchToHsluv(double[] tuple) {
- double L = tuple[0];
- double C = tuple[1];
- double H = tuple[2];
-
- if (L > 99.9999999) {
- return new double[]{H, 0, 100};
- }
-
- if (L < 0.00000001) {
- return new double[]{H, 0, 0};
- }
-
- double max = maxChromaForLH(L, H);
- double S = C / max * 100;
-
- return new double[]{H, S, L};
- }
-
- public static double[] hpluvToLch(double[] tuple) {
- double H = tuple[0];
- double S = tuple[1];
- double L = tuple[2];
-
- if (L > 99.9999999) {
- return new double[]{100, 0, H};
- }
-
- if (L < 0.00000001) {
- return new double[]{0, 0, H};
- }
-
- double max = maxSafeChromaForL(L);
- double C = max / 100 * S;
-
- return new double[]{L, C, H};
- }
-
- public static double[] lchToHpluv(double[] tuple) {
- double L = tuple[0];
- double C = tuple[1];
- double H = tuple[2];
-
- if (L > 99.9999999) {
- return new double[]{H, 0, 100};
- }
-
- if (L < 0.00000001) {
- return new double[]{H, 0, 0};
- }
-
- double max = maxSafeChromaForL(L);
- double S = C / max * 100;
-
- return new double[]{H, S, L};
- }
-
- public static String rgbToHex(double[] tuple) {
- int[] prepared = rgbPrepare(tuple);
-
- return String.format("#%02x%02x%02x",
- prepared[0],
- prepared[1],
- prepared[2]);
- }
-
- public static double[] hexToRgb(String hex) {
- return new double[]
- {
- Integer.parseInt(hex.substring(1, 3), 16) / 255.0,
- Integer.parseInt(hex.substring(3, 5), 16) / 255.0,
- Integer.parseInt(hex.substring(5, 7), 16) / 255.0,
- };
- }
-
- public static double[] lchToRgb(double[] tuple) {
- return xyzToRgb(luvToXyz(lchToLuv(tuple)));
- }
-
- public static double[] rgbToLch(double[] tuple) {
- return luvToLch(xyzToLuv(rgbToXyz(tuple)));
- }
-
- // RGB <--> HUSL(p)
-
- public static double[] hsluvToRgb(double[] tuple) {
- return lchToRgb(hsluvToLch(tuple));
- }
-
- public static double[] rgbToHsluv(double[] tuple) {
- return lchToHsluv(rgbToLch(tuple));
- }
-
- public static double[] hpluvToRgb(double[] tuple) {
- return lchToRgb(hpluvToLch(tuple));
- }
-
- public static double[] rgbToHpluv(double[] tuple) {
- return lchToHpluv(rgbToLch(tuple));
- }
-
- // Hex
-
- public static String hsluvToHex(double[] tuple) {
- return rgbToHex(hsluvToRgb(tuple));
- }
-
- public static String hpluvToHex(double[] tuple) {
- return rgbToHex(hpluvToRgb(tuple));
- }
-
- public static double[] hexToHsluv(String s) {
- return rgbToHsluv(hexToRgb(s));
- }
-
- public static double[] hexToHpluv(String s) {
- return rgbToHpluv(hexToRgb(s));
- }
-
-}
diff --git a/src/main/java/org/hsluv/HsluvColorConverter.java b/src/main/java/org/hsluv/HsluvColorConverter.java
new file mode 100644
index 0000000..76b0146
--- /dev/null
+++ b/src/main/java/org/hsluv/HsluvColorConverter.java
@@ -0,0 +1,368 @@
+package org.hsluv;
+
+/**
+ * README: https://github.com/hsluv/hsluv-java
+ *
+ */
+public class HsluvColorConverter {
+ private static double refY = 1.0;
+ private static double refU = 0.19783000664283;
+ private static double refV = 0.46831999493879;
+ private static double kappa = 903.2962962;
+ private static double epsilon = 0.0088564516;
+ private static double m_r0 = 3.240969941904521;
+ private static double m_r1 = -1.537383177570093;
+ private static double m_r2 = -0.498610760293;
+ private static double m_g0 = -0.96924363628087;
+ private static double m_g1 = 1.87596750150772;
+ private static double m_g2 = 0.041555057407175;
+ private static double m_b0 = 0.055630079696993;
+ private static double m_b1 = -0.20397695888897;
+ private static double m_b2 = 1.056971514242878;
+
+ // RGB
+ public String hex = "#000000";
+ public double rgb_r = 0;
+ public double rgb_g = 0;
+ public double rgb_b = 0;
+
+ // CIE XYZ
+ public double xyz_x = 0;
+ public double xyz_y = 0;
+ public double xyz_z = 0;
+
+ // CIE LUV
+ public double luv_l = 0;
+ public double luv_u = 0;
+ public double luv_v = 0;
+
+ // CIE LUV LCh
+ public double lch_l = 0;
+ public double lch_c = 0;
+ public double lch_h = 0;
+
+ // HSLuv
+ public double hsluv_h = 0;
+ public double hsluv_s = 0;
+ public double hsluv_l = 0;
+
+ // HPLuv
+ public double hpluv_h = 0;
+ public double hpluv_p = 0;
+ public double hpluv_l = 0;
+
+ // 6 lines in slope-intercept format: R < 0, R > 1, G < 0, G > 1, B < 0, B > 1
+ public double r0s = 0;
+ public double r0i = 0;
+ public double r1s = 0;
+ public double r1i = 0;
+
+ public double g0s = 0;
+ public double g0i = 0;
+ public double g1s = 0;
+ public double g1i = 0;
+
+ public double b0s = 0;
+ public double b0i = 0;
+ public double b1s = 0;
+ public double b1i = 0;
+
+ private static double fromLinear(double c) {
+ if (c <= 0.0031308) {
+ return 12.92 * c;
+ } else {
+ return 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
+ }
+ }
+
+ private static double toLinear(double c) {
+ if (c > 0.04045) {
+ return Math.pow((c + 0.055) / 1.055, 2.4);
+ } else {
+ return c / 12.92;
+ }
+ }
+
+ private static double yToL(double Y) {
+ if (Y <= epsilon) {
+ return Y / refY * kappa;
+ } else {
+ return 116 * Math.pow(Y / refY, 1.0 / 3) - 16;
+ }
+ }
+
+ private static double lToY(double L) {
+ if (L <= 8) {
+ return refY * L / kappa;
+ } else {
+ return refY * Math.pow((L + 16) / 116, 3);
+ }
+ }
+
+ private static double hexToRgbChannel(String hex, int offset) {
+ return Integer.parseInt(hex.substring(offset, offset + 2), 16) / 255.0;
+ }
+
+ private static double distanceFromOriginAngle(double slope, double intercept, double angle) {
+ double d = intercept / (Math.sin(angle) - slope * Math.cos(angle));
+ if (d < 0) {
+ return Double.POSITIVE_INFINITY;
+ } else {
+ return d;
+ }
+ }
+
+ private static double distanceFromOrigin(double slope, double intercept) {
+ return Math.abs(intercept) / Math.sqrt(Math.pow(slope, 2) + 1);
+ }
+
+ private static double min6(double f1, double f2, double f3, double f4, double f5, double f6) {
+ return Math.min(f1, Math.min(f2, Math.min(f3, Math.min(f4, Math.min(f5, f6)))));
+ }
+
+ public void rgbToHex() {
+ long r = Math.round(this.rgb_r * 255);
+ long g = Math.round(this.rgb_g * 255);
+ long b = Math.round(this.rgb_b * 255);
+ this.hex = "#" + String.format("%06x", r * 256 * 256 + g * 256 + b);
+ }
+
+ public void hexToRgb() {
+ this.hex = this.hex.toLowerCase();
+ this.rgb_r = hexToRgbChannel(this.hex, 1);
+ this.rgb_g = hexToRgbChannel(this.hex, 3);
+ this.rgb_b = hexToRgbChannel(this.hex, 5);
+ }
+
+ public void xyzToRgb() {
+ this.rgb_r = fromLinear(m_r0 * this.xyz_x + m_r1 * this.xyz_y + m_r2 * this.xyz_z);
+ this.rgb_g = fromLinear(m_g0 * this.xyz_x + m_g1 * this.xyz_y + m_g2 * this.xyz_z);
+ this.rgb_b = fromLinear(m_b0 * this.xyz_x + m_b1 * this.xyz_y + m_b2 * this.xyz_z);
+ }
+
+ public void rgbToXyz() {
+ double lr = toLinear(this.rgb_r);
+ double lg = toLinear(this.rgb_g);
+ double lb = toLinear(this.rgb_b);
+ this.xyz_x = 0.41239079926595 * lr + 0.35758433938387 * lg + 0.18048078840183 * lb;
+ this.xyz_y = 0.21263900587151 * lr + 0.71516867876775 * lg + 0.072192315360733 * lb;
+ this.xyz_z = 0.019330818715591 * lr + 0.11919477979462 * lg + 0.95053215224966 * lb;
+ }
+
+ public void xyzToLuv() {
+ double divider = this.xyz_x + 15 * this.xyz_y + 3 * this.xyz_z;
+ double varU = 4 * this.xyz_x;
+ double varV = 9 * this.xyz_y;
+ if (divider != 0) {
+ varU /= divider;
+ varV /= divider;
+ } else {
+ varU = Double.NaN;
+ varV = Double.NaN;
+ }
+ this.luv_l = yToL(this.xyz_y);
+ if (this.luv_l == 0) {
+ this.luv_u = 0;
+ this.luv_v = 0;
+ } else {
+ this.luv_u = 13 * this.luv_l * (varU - refU);
+ this.luv_v = 13 * this.luv_l * (varV - refV);
+ }
+ }
+
+ public void luvToXyz() {
+ if (this.luv_l == 0) {
+ this.xyz_x = 0;
+ this.xyz_y = 0;
+ this.xyz_z = 0;
+ return;
+ }
+ double varU = this.luv_u / (13 * this.luv_l) + refU;
+ double varV = this.luv_v / (13 * this.luv_l) + refV;
+ this.xyz_y = lToY(this.luv_l);
+ this.xyz_x = 0 - 9 * this.xyz_y * varU / ((varU - 4) * varV - varU * varV);
+ this.xyz_z = (9 * this.xyz_y - 15 * varV * this.xyz_y - varV * this.xyz_x) / (3 * varV);
+ }
+
+ public void luvToLch() {
+ this.lch_l = this.luv_l;
+ this.lch_c = Math.sqrt(this.luv_u * this.luv_u + this.luv_v * this.luv_v);
+ if (this.lch_c < 0.00000001) {
+ this.lch_h = 0;
+ } else {
+ double hrad = Math.atan2(this.luv_v, this.luv_u);
+ this.lch_h = hrad * 180.0 / Math.PI;
+ if (this.lch_h < 0) {
+ this.lch_h = 360 + this.lch_h;
+ }
+ }
+ }
+
+ public void lchToLuv() {
+ double hrad = this.lch_h / 180.0 * Math.PI;
+ this.luv_l = this.lch_l;
+ this.luv_u = Math.cos(hrad) * this.lch_c;
+ this.luv_v = Math.sin(hrad) * this.lch_c;
+ }
+
+ public void calculateBoundingLines(double l) {
+ double sub1 = Math.pow(l + 16, 3) / 1560896;
+ double sub2 = sub1 > epsilon ? sub1 : l / kappa;
+ double s1r = sub2 * (284517 * m_r0 - 94839 * m_r2);
+ double s2r = sub2 * (838422 * m_r2 + 769860 * m_r1 + 731718 * m_r0);
+ double s3r = sub2 * (632260 * m_r2 - 126452 * m_r1);
+ double s1g = sub2 * (284517 * m_g0 - 94839 * m_g2);
+ double s2g = sub2 * (838422 * m_g2 + 769860 * m_g1 + 731718 * m_g0);
+ double s3g = sub2 * (632260 * m_g2 - 126452 * m_g1);
+ double s1b = sub2 * (284517 * m_b0 - 94839 * m_b2);
+ double s2b = sub2 * (838422 * m_b2 + 769860 * m_b1 + 731718 * m_b0);
+ double s3b = sub2 * (632260 * m_b2 - 126452 * m_b1);
+ this.r0s = s1r / s3r;
+ this.r0i = s2r * l / s3r;
+ this.r1s = s1r / (s3r + 126452);
+ this.r1i = (s2r - 769860) * l / (s3r + 126452);
+ this.g0s = s1g / s3g;
+ this.g0i = s2g * l / s3g;
+ this.g1s = s1g / (s3g + 126452);
+ this.g1i = (s2g - 769860) * l / (s3g + 126452);
+ this.b0s = s1b / s3b;
+ this.b0i = s2b * l / s3b;
+ this.b1s = s1b / (s3b + 126452);
+ this.b1i = (s2b - 769860) * l / (s3b + 126452);
+ }
+
+ public double calcMaxChromaHpluv() {
+ double r0 = distanceFromOrigin(this.r0s, this.r0i);
+ double r1 = distanceFromOrigin(this.r1s, this.r1i);
+ double g0 = distanceFromOrigin(this.g0s, this.g0i);
+ double g1 = distanceFromOrigin(this.g1s, this.g1i);
+ double b0 = distanceFromOrigin(this.b0s, this.b0i);
+ double b1 = distanceFromOrigin(this.b1s, this.b1i);
+ return min6(r0, r1, g0, g1, b0, b1);
+ }
+
+ public double calcMaxChromaHsluv(double h) {
+ double hueRad = h / 360 * Math.PI * 2;
+ double r0 = distanceFromOriginAngle(this.r0s, this.r0i, hueRad);
+ double r1 = distanceFromOriginAngle(this.r1s, this.r1i, hueRad);
+ double g0 = distanceFromOriginAngle(this.g0s, this.g0i, hueRad);
+ double g1 = distanceFromOriginAngle(this.g1s, this.g1i, hueRad);
+ double b0 = distanceFromOriginAngle(this.b0s, this.b0i, hueRad);
+ double b1 = distanceFromOriginAngle(this.b1s, this.b1i, hueRad);
+ return min6(r0, r1, g0, g1, b0, b1);
+ }
+
+ public void hsluvToLch() {
+ if (this.hsluv_l > 99.9999999) {
+ this.lch_l = 100;
+ this.lch_c = 0;
+ } else if (this.hsluv_l < 0.00000001) {
+ this.lch_l = 0;
+ this.lch_c = 0;
+ } else {
+ this.lch_l = this.hsluv_l;
+ this.calculateBoundingLines(this.hsluv_l);
+ double max = this.calcMaxChromaHsluv(this.hsluv_h);
+ this.lch_c = max / 100 * this.hsluv_s;
+ }
+ this.lch_h = this.hsluv_h;
+ }
+
+ public void lchToHsluv() {
+ if (this.lch_l > 99.9999999) {
+ this.hsluv_s = 0;
+ this.hsluv_l = 100;
+ } else if (this.lch_l < 0.00000001) {
+ this.hsluv_s = 0;
+ this.hsluv_l = 0;
+ } else {
+ this.calculateBoundingLines(this.lch_l);
+ double max = this.calcMaxChromaHsluv(this.lch_h);
+ this.hsluv_s = this.lch_c / max * 100;
+ this.hsluv_l = this.lch_l;
+ }
+ this.hsluv_h = this.lch_h;
+ }
+
+ public void hpluvToLch() {
+ if (this.hpluv_l > 99.9999999) {
+ this.lch_l = 100;
+ this.lch_c = 0;
+ } else if (this.hpluv_l < 0.00000001) {
+ this.lch_l = 0;
+ this.lch_c = 0;
+ } else {
+ this.lch_l = this.hpluv_l;
+ this.calculateBoundingLines(this.hpluv_l);
+ double max = this.calcMaxChromaHpluv();
+ this.lch_c = max / 100 * this.hpluv_p;
+ }
+ this.lch_h = this.hpluv_h;
+ }
+
+ public void lchToHpluv() {
+ if (this.lch_l > 99.9999999) {
+ this.hpluv_p = 0;
+ this.hpluv_l = 100;
+ } else if (this.lch_l < 0.00000001) {
+ this.hpluv_p = 0;
+ this.hpluv_l = 0;
+ } else {
+ this.calculateBoundingLines(this.lch_l);
+ double max = this.calcMaxChromaHpluv();
+ this.hpluv_p = this.lch_c / max * 100;
+ this.hpluv_l = this.lch_l;
+ }
+ this.hpluv_h = this.lch_h;
+ }
+
+ public void hsluvToRgb() {
+ this.hsluvToLch();
+ this.lchToLuv();
+ this.luvToXyz();
+ this.xyzToRgb();
+ }
+
+ public void hpluvToRgb() {
+ this.hpluvToLch();
+ this.lchToLuv();
+ this.luvToXyz();
+ this.xyzToRgb();
+ }
+
+ public void hsluvToHex() {
+ this.hsluvToRgb();
+ this.rgbToHex();
+ }
+
+ public void hpluvToHex() {
+ this.hpluvToRgb();
+ this.rgbToHex();
+ }
+
+ public void rgbToHsluv() {
+ this.rgbToXyz();
+ this.xyzToLuv();
+ this.luvToLch();
+ this.lchToHpluv();
+ this.lchToHsluv();
+ }
+
+ public void rgbToHpluv() {
+ this.rgbToXyz();
+ this.xyzToLuv();
+ this.luvToLch();
+ this.lchToHpluv();
+ this.lchToHpluv();
+ }
+
+ public void hexToHsluv() {
+ this.hexToRgb();
+ this.rgbToHsluv();
+ }
+
+ public void hexToHpluv() {
+ this.hexToRgb();
+ this.rgbToHpluv();
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/hsluv/ColorConverterTest.java b/src/test/java/org/hsluv/ColorConverterTest.java
index 0565a9c..973a879 100644
--- a/src/test/java/org/hsluv/ColorConverterTest.java
+++ b/src/test/java/org/hsluv/ColorConverterTest.java
@@ -1,130 +1,94 @@
package org.hsluv;
import jakarta.json.*;
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
+import static org.junit.Assert.assertEquals;
import java.io.IOException;
import java.io.InputStream;
+import org.junit.Test;
-public class ColorConverterTest extends TestCase {
-
- private static final double MAXDIFF = 0.0000000001;
- private static final double MAXRELDIFF = 0.000000001;
-
- /**
- * modified from
- * https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
- */
- private boolean assertAlmostEqualRelativeAndAbs(double a, double b) {
- // Check if the numbers are really close -- needed
- // when comparing numbers near zero.
- double diff = Math.abs(a - b);
- if (diff <= MAXDIFF) {
- return true;
- }
-
- a = Math.abs(a);
- b = Math.abs(b);
- double largest = (b > a) ? b : a;
-
- return diff <= largest * MAXRELDIFF;
- }
-
- private void assertTuplesClose(String label, double[] expected, double[] actual) {
- boolean mismatch = false;
- double[] deltas = new double[expected.length];
-
- for (int i = 0; i < expected.length; ++i) {
- deltas[i] = Math.abs(expected[i] - actual[i]);
- if (!assertAlmostEqualRelativeAndAbs(expected[i], actual[i])) {
- mismatch = true;
- }
- }
-
- if (mismatch) {
- System.out.printf("MISMATCH %s\n", label);
- System.out.printf(" expected: %.10f,%.10f,%.10f\n", expected[0], expected[1], expected[2]);
- System.out.printf(" actual: %.10f,%.10f,%.10f\n", actual[0], actual[1], actual[2]);
- System.out.printf(" deltas: %.10f,%.10f,%.10f\n", deltas[0], deltas[1], deltas[2]);
+public class ColorConverterTest {
+ static void assertFloatClose(double expected, double actual) {
+ if (Math.abs(expected - actual) > 1e-10) {
+ System.out.println(expected);
+ System.out.println(actual);
+ throw new RuntimeException("Not equals");
}
-
- assertFalse(mismatch);
}
-
- public static Test suite() {
- return new TestSuite(ColorConverterTest.class);
+ static void assertClose(HsluvColorConverter expected, HsluvColorConverter actual) {
+ assertEquals(expected.hex, actual.hex);
+ assertFloatClose(expected.rgb_r, actual.rgb_r);
+ assertFloatClose(expected.rgb_g, actual.rgb_g);
+ assertFloatClose(expected.rgb_b, actual.rgb_b);
+ assertFloatClose(expected.xyz_x, actual.xyz_x);
+ assertFloatClose(expected.xyz_y, actual.xyz_y);
+ assertFloatClose(expected.xyz_z, actual.xyz_z);
+ assertFloatClose(expected.luv_l, actual.luv_l);
+ assertFloatClose(expected.luv_u, actual.luv_u);
+ assertFloatClose(expected.luv_v, actual.luv_v);
+ assertFloatClose(expected.lch_l, actual.lch_l);
+ assertFloatClose(expected.lch_c, actual.lch_c);
+ assertFloatClose(expected.lch_h, actual.lch_h);
+ assertFloatClose(expected.hsluv_h, actual.hsluv_h);
+ assertFloatClose(expected.hsluv_s, actual.hsluv_s);
+ assertFloatClose(expected.hsluv_l, actual.hsluv_l);
+ assertFloatClose(expected.hpluv_h, actual.hpluv_h);
+ assertFloatClose(expected.hpluv_p, actual.hpluv_p);
+ assertFloatClose(expected.hpluv_l, actual.hpluv_l);
}
- private double[] tupleFromJsonArray(JsonArray arr) {
- return new double[]{
- arr.getJsonNumber(0).doubleValue(),
- arr.getJsonNumber(1).doubleValue(),
- arr.getJsonNumber(2).doubleValue()
- };
+ static double getSample(JsonObject s, String cs, int index) {
+ return s.getJsonArray(cs).getJsonNumber(index).doubleValue();
}
+ @Test
public void testHsluv() throws IOException {
System.out.println("Running test");
InputStream snapshotStream = ColorConverterTest.class.getResourceAsStream("/snapshot-rev4.json");
JsonReader reader = Json.createReader(snapshotStream);
JsonObject tests = reader.readObject();
+ HsluvColorConverter conv = new HsluvColorConverter();
for (String hex : tests.keySet()) {
- JsonObject expected = tests.getJsonObject(hex);
- double[] rgb = tupleFromJsonArray(expected.getJsonArray("rgb"));
- double[] xyz = tupleFromJsonArray(expected.getJsonArray("xyz"));
- double[] luv = tupleFromJsonArray(expected.getJsonArray("luv"));
- double[] lch = tupleFromJsonArray(expected.getJsonArray("lch"));
- double[] hsluv = tupleFromJsonArray(expected.getJsonArray("hsluv"));
- double[] hpluv = tupleFromJsonArray(expected.getJsonArray("hpluv"));
-
- System.out.println("testing " + hex);
-
- // forward functions
-
- double[] rgbFromHex = HUSLColorConverter.hexToRgb(hex);
- double[] xyzFromRgb = HUSLColorConverter.rgbToXyz(rgbFromHex);
- double[] luvFromXyz = HUSLColorConverter.xyzToLuv(xyzFromRgb);
- double[] lchFromLuv = HUSLColorConverter.luvToLch(luvFromXyz);
- double[] hsluvFromLch = HUSLColorConverter.lchToHsluv(lchFromLuv);
- double[] hpluvFromLch = HUSLColorConverter.lchToHpluv(lchFromLuv);
- double[] hsluvFromHex = HUSLColorConverter.hexToHsluv(hex);
- double[] hpluvFromHex = HUSLColorConverter.hexToHpluv(hex);
-
- assertTuplesClose("hexToRgb", rgb, rgbFromHex);
- assertTuplesClose("rgbToXyz", xyz, xyzFromRgb);
- assertTuplesClose("xyzToLuv", luv, luvFromXyz);
- assertTuplesClose("luvToLch", lch, lchFromLuv);
- assertTuplesClose("lchToHsluv", hsluv, hsluvFromLch);
- assertTuplesClose("lchToHpluv", hpluv, hpluvFromLch);
- assertTuplesClose("hexToHsluv", hsluv, hsluvFromHex);
- assertTuplesClose("hexToHpluv", hpluv, hpluvFromHex);
-
- // backward functions
-
- double[] lchFromHsluv = HUSLColorConverter.hsluvToLch(hsluv);
- double[] lchFromHpluv = HUSLColorConverter.hpluvToLch(hpluv);
- double[] luvFromLch = HUSLColorConverter.lchToLuv(lch);
- double[] xyzFromLuv = HUSLColorConverter.luvToXyz(luv);
- double[] rgbFromXyz = HUSLColorConverter.xyzToRgb(xyz);
- String hexFromRgb = HUSLColorConverter.rgbToHex(rgb);
- String hexFromHsluv = HUSLColorConverter.hsluvToHex(hsluv);
- String hexFromHpluv = HUSLColorConverter.hpluvToHex(hpluv);
-
- assertTuplesClose("hsluvToLch", lch, lchFromHsluv);
- assertTuplesClose("hpluvToLch", lch, lchFromHpluv);
- assertTuplesClose("lchToLuv", luv, luvFromLch);
- assertTuplesClose("luvToXyz", xyz, xyzFromLuv);
- assertTuplesClose("xyzToRgb", rgb, rgbFromXyz);
- assertEquals(hex, hexFromRgb);
- assertEquals(hex, hexFromHsluv);
- assertEquals(hex, hexFromHpluv);
-
+ JsonObject s = tests.getJsonObject(hex);
+ HsluvColorConverter sample = new HsluvColorConverter();
+ sample.hex = hex;
+ sample.rgb_r = getSample(s, "rgb", 0);
+ sample.rgb_g = getSample(s, "rgb", 1);
+ sample.rgb_b = getSample(s, "rgb", 2);
+ sample.xyz_x = getSample(s, "xyz", 0);
+ sample.xyz_y = getSample(s, "xyz", 1);
+ sample.xyz_z = getSample(s, "xyz", 2);
+ sample.luv_l = getSample(s, "luv", 0);
+ sample.luv_u = getSample(s, "luv", 1);
+ sample.luv_v = getSample(s, "luv", 2);
+ sample.lch_l = getSample(s, "lch", 0);
+ sample.lch_c = getSample(s, "lch", 1);
+ sample.lch_h = getSample(s, "lch", 2);
+ sample.hsluv_h = getSample(s, "hsluv", 0);
+ sample.hsluv_s = getSample(s, "hsluv", 1);
+ sample.hsluv_l = getSample(s, "hsluv", 2);
+ sample.hpluv_h = getSample(s, "hpluv", 0);
+ sample.hpluv_p = getSample(s, "hpluv", 1);
+ sample.hpluv_l = getSample(s, "hpluv", 2);
+ conv.hex = hex;
+ conv.hexToHsluv();
+ assertClose(conv, sample);
+ conv.hexToHpluv();
+ assertClose(conv, sample);
+ conv.hsluv_h = sample.hsluv_h;
+ conv.hsluv_s = sample.hsluv_s;
+ conv.hsluv_l = sample.hsluv_l;
+ conv.hsluvToHex();
+ assertClose(conv, sample);
+ conv.hpluv_h = sample.hpluv_h;
+ conv.hpluv_p = sample.hpluv_p;
+ conv.hpluv_l = sample.hpluv_l;
+ conv.hpluvToHex();
+ assertClose(conv, sample);
}
}
}