-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
252 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,23 @@ | ||
package co.lors; | ||
|
||
public sealed interface Color permits RgbColor { | ||
public sealed interface Color permits RgbColor, OklabColor { | ||
|
||
static Color parse(String value) { | ||
if (value.startsWith("#")) { | ||
return RgbColor.fromHex(value); | ||
} else if (value.startsWith("rgb")) { | ||
return RgbColor.fromCss(value); | ||
} else if (value.startsWith("oklab")) { | ||
return OklabColor.fromCss(value); | ||
} | ||
// todo: handle more colorspaces | ||
|
||
throw new UnsupportedOperationException("Colorspace of " + value + " is unsupported!"); | ||
} | ||
|
||
String toCss(); | ||
|
||
RgbColor toRgb(); | ||
|
||
OklabColor toOklab(); | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package co.lors; | ||
|
||
/** | ||
* A type representing an Oklab color with optional transparency. | ||
* | ||
* It uses the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklab">Oklab</a> colorspace, | ||
* which uses the D65 white point. | ||
* Compared to sRGB, it can express a wider range of colors. | ||
*/ | ||
public record OklabColor(float lightness, float aAxis, float bAxis, float alpha) implements Color { | ||
|
||
public OklabColor { | ||
checkUnitRange("lightness", lightness); | ||
checkAxisRange("aAxis", aAxis); | ||
checkAxisRange("bAxis", bAxis); | ||
checkUnitRange("alpha", alpha); | ||
} | ||
|
||
public static OklabColor of(float lightness, float aAxis, float bAxis, float alpha) { | ||
return new OklabColor(lightness, aAxis, bAxis, alpha); | ||
} | ||
|
||
public static OklabColor of(float lightness, float aAxis, float bAxis) { | ||
return new OklabColor(lightness, aAxis, bAxis, 1.0f); | ||
} | ||
|
||
public static OklabColor fromCss(String value) { | ||
String[] colors = value.substring(7, value.length() - 1).split(" / |, |,| "); | ||
float lightness = Float.parseFloat(colors[0].trim()); | ||
float aAxis = Float.parseFloat(colors[1].trim()); | ||
float bAxis = Float.parseFloat(colors[2].trim()); | ||
float alpha = 1.0f; | ||
if (colors.length > 3) { | ||
alpha = Float.parseFloat(colors[3].trim()); | ||
} | ||
return OklabColor.of(lightness, aAxis, bAxis, alpha); | ||
} | ||
|
||
@Override | ||
public String toCss() { | ||
String alphaString = alpha == 0.0f ? "" : " / " + alpha; | ||
return "oklab(" + lightness + " " + aAxis + " " + bAxis + alphaString + ")"; | ||
} | ||
|
||
@Override | ||
public RgbColor toRgb() { | ||
double l = lightness + 0.3963377774 * aAxis + 0.2158037573 * bAxis; | ||
double m = lightness - 0.1055613458 * aAxis - 0.0638541728 * bAxis; | ||
double s = lightness - 0.0894841775 * aAxis - 1.2914855480 * bAxis; | ||
|
||
double l3 = l * l * l; | ||
double m3 = m * m * m; | ||
double s3 = s * s * s; | ||
|
||
double red = +4.0767416621 * l3 - 3.3077115913 * m3 + 0.2309699292 * s3; | ||
double green = -1.2684380046 * l3 + 2.6097574011 * m3 - 0.3413193965 * s3; | ||
double blue = -0.0041960863 * l3 - 0.7034186147 * m3 + 1.7076147010 * s3; | ||
return RgbColor.of(scaleToByte(red), scaleToByte(green), scaleToByte(blue), (int) (alpha * 255)); | ||
} | ||
|
||
@Override | ||
public OklabColor toOklab() { | ||
return this; | ||
} | ||
|
||
private static double gamma(double value) { | ||
return value >= 0.0031308 | ||
? 1.055 * Math.pow(value, 1 / 2.4) - 0.055 | ||
: 12.92 * value; | ||
} | ||
|
||
private static int scaleToByte(double value) { | ||
return Math.max(0, Math.min(255, (int) (Math.round(gamma(value) * 255)))); | ||
} | ||
|
||
private static void checkUnitRange(String name, float value) { | ||
if (value < 0) { | ||
throw new IllegalArgumentException(name + " cannot be less than 0, but was " + value); | ||
} | ||
if (value > 1) { | ||
throw new IllegalArgumentException(name + " cannot be greater than 1, but was " + value); | ||
} | ||
} | ||
|
||
private static void checkAxisRange(String name, float value) { | ||
if (value < -0.4f) { | ||
throw new IllegalArgumentException(name + " cannot be less than -0.4, but was " + value); | ||
} | ||
if (value > 0.4f) { | ||
throw new IllegalArgumentException(name + " cannot be greater than 0.4, but was " + value); | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package co.lors; | ||
|
||
import org.junit.jupiter.api.Test; | ||
|
||
import static org.junit.jupiter.api.Assertions.*; | ||
|
||
class ColorTest { | ||
|
||
@Test | ||
void parse() { | ||
assertEquals(RgbColor.of(255, 204, 153), Color.parse("#FFCC99")); | ||
assertEquals(RgbColor.of(255, 204, 153, 34), Color.parse("#FFCC9922")); | ||
|
||
assertEquals(RgbColor.of(255, 204, 153), Color.parse("rgb(255, 204, 153)")); | ||
assertEquals(RgbColor.of(255, 204, 153, 34), Color.parse("rgb(255, 204, 153 / 0.133)")); | ||
|
||
assertEquals(OklabColor.of(0.700f, 0.200f, 0.000f), Color.parse("oklab(0.7 0.2 0.0)")); | ||
assertEquals(OklabColor.of(0.700f, 0.200f, 0.000f, 1.0f), Color.parse("oklab(0.7 0.2 0.0 / 1.0)")); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package co.lors; | ||
|
||
import org.junit.jupiter.api.Test; | ||
|
||
import static org.junit.jupiter.api.Assertions.*; | ||
|
||
class OklabColorTest { | ||
|
||
public static final OklabColor COLOR1 = OklabColor.of(0.700f, 0.200f, 0.000f, 1.0f); | ||
public static final OklabColor COLOR2 = OklabColor.of(0.500f, 0.100f, 0.100f, 1.0f); | ||
|
||
@Test | ||
void fromCss() { | ||
assertEquals(COLOR1, OklabColor.fromCss("oklab(0.7 0.2 0.0 / 1.0)")); | ||
assertEquals(COLOR2, OklabColor.fromCss("oklab(0.5 0.1 0.1 / 1.0)")); | ||
} | ||
|
||
@Test | ||
void toCss() { | ||
assertEquals(COLOR1.toCss(), "oklab(0.7 0.2 0.0 / 1.0)"); | ||
assertEquals(COLOR2.toCss(), "oklab(0.5 0.1 0.1 / 1.0)"); | ||
} | ||
|
||
@Test | ||
void toRgb() { | ||
assertEquals(COLOR1.toRgb(), RgbColor.of(251, 92, 153, 255)); | ||
assertEquals(COLOR2.toRgb(), RgbColor.of(161, 66, 3, 255)); | ||
} | ||
|
||
@Test | ||
void lightness() { | ||
assertEquals(COLOR1.lightness(), 0.7f); | ||
assertEquals(COLOR2.lightness(), 0.5f); | ||
} | ||
|
||
@Test | ||
void aAxis() { | ||
assertEquals(COLOR1.aAxis(), 0.2f); | ||
assertEquals(COLOR2.aAxis(), 0.1f); | ||
} | ||
|
||
@Test | ||
void bAxis() { | ||
assertEquals(COLOR1.bAxis(), 0.0f); | ||
assertEquals(COLOR2.bAxis(), 0.1f); | ||
} | ||
|
||
@Test | ||
void alpha() { | ||
assertEquals(COLOR1.alpha(), 1.0f); | ||
assertEquals(COLOR2.alpha(), 1.0f); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters