diff --git a/examples/lib/colorspace.html b/examples/lib/colorspace.html index a2b84da..446cbfa 100644 --- a/examples/lib/colorspace.html +++ b/examples/lib/colorspace.html @@ -5,47 +5,105 @@ Color Space Converter +

Color Space Converter

-
- +
+
-
- +
+
-
- +
+
-
- +
+
-
- +
+
-
- +
+
+
+ + + + +
+
+ + + + +
+
+ + + + +
+ diff --git a/examples/lib/colorspace.js b/examples/lib/colorspace.js index c45eead..970b6c2 100644 --- a/examples/lib/colorspace.js +++ b/examples/lib/colorspace.js @@ -1,11 +1,11 @@ -(function() { - let wasm_promise = fetch("colorspace.wasm"); - var wasm_exports = null; - const text_decoder = new TextDecoder(); +(function () { + let wasmPromise = fetch("colorspace.wasm"); + var wasmExports = null; + const textDecoder = new TextDecoder(); function decodeString(ptr, len) { if (len === 0) return ""; - return text_decoder.decode(new Uint8Array(wasm_exports.memory.buffer, ptr, len)); + return textDecoder.decode(new Uint8Array(wasmExports.memory.buffer, ptr, len)); } function hex2rgb(hex) { @@ -28,18 +28,18 @@ return "#" + toHex(r) + toHex(g) + toHex(b); } - WebAssembly.instantiateStreaming(wasm_promise, { + WebAssembly.instantiateStreaming(wasmPromise, { js: { - log: function(ptr, len) { + log: function (ptr, len) { const msg = decodeString(ptr, len); console.log(msg); }, - now: function() { + now: function () { return performance.now(); }, }, - }).then(function(obj) { - wasm_exports = obj.instance.exports; + }).then(function (obj) { + wasmExports = obj.instance.exports; window.wasm = obj; // Event listeners for HEX document.getElementById("hex-#").addEventListener("input", updateFromHex); @@ -64,19 +64,31 @@ document.getElementById("lab-l").addEventListener("input", updateFromLab); document.getElementById("lab-a").addEventListener("input", updateFromLab); document.getElementById("lab-b").addEventListener("input", updateFromLab); + // Event listeners for LMS + document.getElementById("lms-l").addEventListener("input", updateFromLms); + document.getElementById("lms-m").addEventListener("input", updateFromLms); + document.getElementById("lms-s").addEventListener("input", updateFromLms); + // Event listeners for Oklab + document.getElementById("oklab-l").addEventListener("input", updateFromOklab); + document.getElementById("oklab-a").addEventListener("input", updateFromOklab); + document.getElementById("oklab-b").addEventListener("input", updateFromOklab); + // Event listeners for XYB + document.getElementById("xyb-x").addEventListener("input", updateFromXyb); + document.getElementById("xyb-y").addEventListener("input", updateFromXyb); + document.getElementById("xyb-b").addEventListener("input", updateFromXyb); function updateFromHex() { const hex = document.getElementById("hex-#").value; const rgb = hex2rgb(hex); if (rgb) { - document.getElementById('validation-message').textContent = ''; + document.getElementById("validation-message").textContent = ""; document.getElementById("rgb-r").value = rgb.r; document.getElementById("rgb-g").value = rgb.g; document.getElementById("rgb-b").value = rgb.b; updateColor(); updateFromRgb(); } else { - document.getElementById('validation-message').textContent = '⚠️ Invalid HEX value.'; + document.getElementById("validation-message").textContent = "⚠️ Invalid HEX value."; } } @@ -101,39 +113,66 @@ // --- RGB --- function rgb2hsl(r, g, b) { - const hslPtr = wasm_exports.alloc(3 * 4); - const hsl = new Float64Array(wasm_exports.memory.buffer, hslPtr, 3); - wasm_exports.rgb2hsl(r, g, b, hslPtr); - document.getElementById("hsl-h").value = hsl[0].toFixed(3); - document.getElementById("hsl-s").value = hsl[1].toFixed(3); - document.getElementById("hsl-l").value = hsl[2].toFixed(3); + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.rgb2hsl(r, g, b, outPtr); + document.getElementById("hsl-h").value = out[0].toFixed(6); + document.getElementById("hsl-s").value = out[1].toFixed(6); + document.getElementById("hsl-l").value = out[2].toFixed(6); } function rgb2hsv(r, g, b) { - const hsvPtr = wasm_exports.alloc(3 * 4); - const hsv = new Float64Array(wasm_exports.memory.buffer, hsvPtr, 3); - wasm_exports.rgb2hsv(r, g, b, hsvPtr); - document.getElementById("hsv-h").value = hsv[0].toFixed(3); - document.getElementById("hsv-s").value = hsv[1].toFixed(3); - document.getElementById("hsv-v").value = hsv[2].toFixed(3); + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.rgb2hsv(r, g, b, outPtr); + document.getElementById("hsv-h").value = out[0].toFixed(6); + document.getElementById("hsv-s").value = out[1].toFixed(6); + document.getElementById("hsv-v").value = out[2].toFixed(6); } function rgb2xyz(r, g, b) { - const xyzPtr = wasm_exports.alloc(3 * 4); - const xyz = new Float64Array(wasm_exports.memory.buffer, xyzPtr, 3); - wasm_exports.rgb2xyz(r, g, b, xyzPtr); - document.getElementById("xyz-x").value = xyz[0].toFixed(3); - document.getElementById("xyz-y").value = xyz[1].toFixed(3); - document.getElementById("xyz-z").value = xyz[2].toFixed(3); + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.rgb2xyz(r, g, b, outPtr); + document.getElementById("xyz-x").value = out[0].toFixed(6); + document.getElementById("xyz-y").value = out[1].toFixed(6); + document.getElementById("xyz-z").value = out[2].toFixed(6); } function rgb2lab(r, g, b) { - const labPtr = wasm_exports.alloc(3 * 4); - const lab = new Float64Array(wasm_exports.memory.buffer, labPtr, 3); - wasm_exports.rgb2lab(r, g, b, labPtr); - document.getElementById("lab-l").value = lab[0].toFixed(3); - document.getElementById("lab-a").value = lab[1].toFixed(3); - document.getElementById("lab-b").value = lab[2].toFixed(3); + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.rgb2lab(r, g, b, outPtr); + document.getElementById("lab-l").value = out[0].toFixed(6); + document.getElementById("lab-a").value = out[1].toFixed(6); + document.getElementById("lab-b").value = out[2].toFixed(6); + } + + function rgb2lms(r, g, b) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.rgb2lms(r, g, b, outPtr); + document.getElementById("lms-l").value = out[0].toFixed(6); + document.getElementById("lms-m").value = out[1].toFixed(6); + document.getElementById("lms-s").value = out[2].toFixed(6); + } + + function rgb2oklab(r, g, b) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.rgb2oklab(r, g, b, outPtr); + document.getElementById("oklab-l").value = out[0].toFixed(6); + document.getElementById("oklab-a").value = out[1].toFixed(6); + document.getElementById("oklab-b").value = out[2].toFixed(6); + } + + function rgb2xyb(r, g, b) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.rgb2xyb(r, g, b, outPtr); + document.getElementById("xyb-x").value = out[0].toFixed(6); + document.getElementById("xyb-y").value = out[1].toFixed(6); + document.getElementById("xyb-b").value = out[2].toFixed(6); } function updateFromRgb() { @@ -150,45 +189,74 @@ rgb2hsv(r, g, b); rgb2xyz(r, g, b); rgb2lab(r, g, b); + rgb2lms(r, g, b); + rgb2oklab(r, g, b); + rgb2xyb(r, g, b); updateHex(); } // --- HSL --- function hsl2rgb(h, s, l) { - const rgbPtr = wasm_exports.alloc(3); - const rgb = new Uint8Array(wasm_exports.memory.buffer, rgbPtr, 3); - wasm_exports.hsl2rgb(h, s, l, rgbPtr); - document.getElementById("rgb-r").value = rgb[0]; - document.getElementById("rgb-g").value = rgb[1]; - document.getElementById("rgb-b").value = rgb[2]; + const outPtr = wasmExports.alloc(3); + const out = new Uint8Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.hsl2rgb(h, s, l, outPtr); + document.getElementById("rgb-r").value = out[0]; + document.getElementById("rgb-g").value = out[1]; + document.getElementById("rgb-b").value = out[2]; } function hsl2hsv(h, s, l) { - const hsvPtr = wasm_exports.alloc(3 * 4); - const hsv = new Float64Array(wasm_exports.memory.buffer, hsvPtr, 3); - wasm_exports.hsl2hsv(h, s, l, hsvPtr); - document.getElementById("hsv-h").value = hsv[0].toFixed(3); - document.getElementById("hsv-s").value = hsv[1].toFixed(3); - document.getElementById("hsv-v").value = hsv[2].toFixed(3); + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.hsl2hsv(h, s, l, outPtr); + document.getElementById("hsv-h").value = out[0].toFixed(6); + document.getElementById("hsv-s").value = out[1].toFixed(6); + document.getElementById("hsv-v").value = out[2].toFixed(6); } function hsl2xyz(h, s, l) { - const xyzPtr = wasm_exports.alloc(3 * 4); - const xyz = new Float64Array(wasm_exports.memory.buffer, xyzPtr, 3); - wasm_exports.hsl2xyz(h, s, l, xyzPtr); - document.getElementById("xyz-x").value = xyz[0].toFixed(3); - document.getElementById("xyz-y").value = xyz[1].toFixed(3); - document.getElementById("xyz-z").value = xyz[2].toFixed(3); + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.hsl2xyz(h, s, l, outPtr); + document.getElementById("xyz-x").value = out[0].toFixed(6); + document.getElementById("xyz-y").value = out[1].toFixed(6); + document.getElementById("xyz-z").value = out[2].toFixed(6); } function hsl2lab(h, s, l) { - const labPtr = wasm_exports.alloc(3 * 4); - const lab = new Float64Array(wasm_exports.memory.buffer, labPtr, 3); - wasm_exports.hsl2lab(h, s, l, labPtr); - document.getElementById("lab-l").value = lab[0].toFixed(3); - document.getElementById("lab-a").value = lab[1].toFixed(3); - document.getElementById("lab-b").value = lab[2].toFixed(3); + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.hsl2lab(h, s, l, outPtr); + document.getElementById("lab-l").value = out[0].toFixed(6); + document.getElementById("lab-a").value = out[1].toFixed(6); + document.getElementById("lab-b").value = out[2].toFixed(6); + } + + function hsl2lms(h, s, l) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.hsl2lms(h, s, l, outPtr); + document.getElementById("lms-l").value = out[0].toFixed(6); + document.getElementById("lms-m").value = out[1].toFixed(6); + document.getElementById("lms-s").value = out[2].toFixed(6); + } + + function hsl2oklab(h, s, l) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.hsl2oklab(h, s, l, outPtr); + document.getElementById("oklab-l").value = out[0].toFixed(6); + document.getElementById("oklab-a").value = out[1].toFixed(6); + document.getElementById("oklab-b").value = out[2].toFixed(6); + } + function hsl2xyb(h, s, l) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.hsl2xyb(h, s, l, outPtr); + document.getElementById("xyb-x").value = out[0].toFixed(6); + document.getElementById("xyb-y").value = out[1].toFixed(6); + document.getElementById("xyb-b").value = out[2].toFixed(6); } function updateFromHsl() { @@ -205,45 +273,75 @@ hsl2hsv(h, s, l); hsl2xyz(h, s, l); hsl2lab(h, s, l); + hsl2lms(h, s, l); + hsl2oklab(h, s, l); + hsl2xyb(h, s, l); updateHex(); } // --- HSV --- function hsv2rgb(h, s, v) { - const rgbPtr = wasm_exports.alloc(3); - const rgb = new Uint8Array(wasm_exports.memory.buffer, rgbPtr, 3); - wasm_exports.hsv2rgb(h, s, v, rgbPtr); - document.getElementById("rgb-r").value = rgb[0]; - document.getElementById("rgb-g").value = rgb[1]; - document.getElementById("rgb-b").value = rgb[2]; + const outPtr = wasmExports.alloc(3); + const out = new Uint8Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.hsv2rgb(h, s, v, outPtr); + document.getElementById("rgb-r").value = out[0]; + document.getElementById("rgb-g").value = out[1]; + document.getElementById("rgb-b").value = out[2]; } function hsv2hsl(h, s, v) { - const hslPtr = wasm_exports.alloc(3 * 4); - const hsl = new Float64Array(wasm_exports.memory.buffer, hslPtr, 3); - wasm_exports.hsv2hsl(h, s, v, hslPtr); - document.getElementById("hsl-h").value = hsl[0].toFixed(3); - document.getElementById("hsl-s").value = hsl[1].toFixed(3); - document.getElementById("hsl-l").value = hsl[2].toFixed(3); + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.hsv2hsl(h, s, v, outPtr); + document.getElementById("hsl-h").value = out[0].toFixed(6); + document.getElementById("hsl-s").value = out[1].toFixed(6); + document.getElementById("hsl-l").value = out[2].toFixed(6); } function hsv2xyz(h, s, v) { - const xyzPtr = wasm_exports.alloc(3 * 4); - const xyz = new Float64Array(wasm_exports.memory.buffer, xyzPtr, 3); - wasm_exports.hsv2xyz(h, s, v, xyzPtr); - document.getElementById("xyz-x").value = xyz[0].toFixed(3); - document.getElementById("xyz-y").value = xyz[1].toFixed(3); - document.getElementById("xyz-z").value = xyz[2].toFixed(3); + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.hsv2xyz(h, s, v, outPtr); + document.getElementById("xyz-x").value = out[0].toFixed(6); + document.getElementById("xyz-y").value = out[1].toFixed(6); + document.getElementById("xyz-z").value = out[2].toFixed(6); } function hsv2lab(h, s, v) { - const labPtr = wasm_exports.alloc(3 * 4); - const lab = new Float64Array(wasm_exports.memory.buffer, labPtr, 3); - wasm_exports.hsv2lab(h, s, v, labPtr); - document.getElementById("lab-l").value = lab[0].toFixed(3); - document.getElementById("lab-a").value = lab[1].toFixed(3); - document.getElementById("lab-b").value = lab[2].toFixed(3); + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.hsv2lab(h, s, v, outPtr); + document.getElementById("lab-l").value = out[0].toFixed(6); + document.getElementById("lab-a").value = out[1].toFixed(6); + document.getElementById("lab-b").value = out[2].toFixed(6); + } + + function hsv2lms(h, s, v) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.hsv2lms(h, s, v, outPtr); + document.getElementById("lms-l").value = out[0].toFixed(6); + document.getElementById("lms-m").value = out[1].toFixed(6); + document.getElementById("lms-s").value = out[2].toFixed(6); + } + + function hsv2oklab(h, s, v) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.hsv2oklab(h, s, v, outPtr); + document.getElementById("oklab-l").value = out[0].toFixed(6); + document.getElementById("oklab-a").value = out[1].toFixed(6); + document.getElementById("oklab-b").value = out[2].toFixed(6); + } + + function hsv2xyb(h, s, v) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.hsv2xyb(h, s, v, outPtr); + document.getElementById("xyb-x").value = out[0].toFixed(6); + document.getElementById("xyb-y").value = out[1].toFixed(6); + document.getElementById("xyb-b").value = out[2].toFixed(6); } function updateFromHsv() { @@ -260,45 +358,75 @@ hsv2hsl(h, s, v); hsv2xyz(h, s, v); hsv2lab(h, s, v); + hsv2lms(h, s, v); + hsv2oklab(h, s, v); + hsv2xyb(h, s, v); updateHex(); } // --- XYZ --- function xyz2rgb(x, y, z) { - const rgbPtr = wasm_exports.alloc(3); - const rgb = new Uint8Array(wasm_exports.memory.buffer, rgbPtr, 3); - wasm_exports.xyz2rgb(x, y, z, rgbPtr); - document.getElementById("rgb-r").value = rgb[0]; - document.getElementById("rgb-g").value = rgb[1]; - document.getElementById("rgb-b").value = rgb[2]; + const outPtr = wasmExports.alloc(3); + const out = new Uint8Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.xyz2rgb(x, y, z, outPtr); + document.getElementById("rgb-r").value = out[0]; + document.getElementById("rgb-g").value = out[1]; + document.getElementById("rgb-b").value = out[2]; } function xyz2hsl(x, y, z) { - const hslPtr = wasm_exports.alloc(3 * 4); - const hsl = new Float64Array(wasm_exports.memory.buffer, hslPtr, 3); - wasm_exports.xyz2hsl(x, y, z, hslPtr); - document.getElementById("hsl-h").value = hsl[0].toFixed(3); - document.getElementById("hsl-s").value = hsl[1].toFixed(3); - document.getElementById("hsl-l").value = hsl[2].toFixed(3); + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.xyz2hsl(x, y, z, outPtr); + document.getElementById("hsl-h").value = out[0].toFixed(6); + document.getElementById("hsl-s").value = out[1].toFixed(6); + document.getElementById("hsl-l").value = out[2].toFixed(6); } function xyz2hsv(x, y, z) { - const hsvPtr = wasm_exports.alloc(3 * 4); - const hsv = new Float64Array(wasm_exports.memory.buffer, hsvPtr, 3); - wasm_exports.xyz2hsv(x, y, z, hsvPtr); - document.getElementById("hsl-h").value = hsv[0].toFixed(3); - document.getElementById("hsl-s").value = hsv[1].toFixed(3); - document.getElementById("hsl-l").value = hsv[2].toFixed(3); + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.xyz2hsv(x, y, z, outPtr); + document.getElementById("hsv-h").value = out[0].toFixed(6); + document.getElementById("hsv-s").value = out[1].toFixed(6); + document.getElementById("hsv-v").value = out[2].toFixed(6); } function xyz2lab(x, y, z) { - const labPtr = wasm_exports.alloc(3 * 4); - const lab = new Float64Array(wasm_exports.memory.buffer, labPtr, 3); - wasm_exports.xyz2lab(x, y, z, labPtr); - document.getElementById("lab-l").value = lab[0].toFixed(3); - document.getElementById("lab-a").value = lab[1].toFixed(3); - document.getElementById("lab-b").value = lab[2].toFixed(3); + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.xyz2lab(x, y, z, outPtr); + document.getElementById("lab-l").value = out[0].toFixed(6); + document.getElementById("lab-a").value = out[1].toFixed(6); + document.getElementById("lab-b").value = out[2].toFixed(6); + } + + function xyz2lms(x, y, z) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.xyz2lms(x, y, z, outPtr); + document.getElementById("lms-l").value = out[0].toFixed(6); + document.getElementById("lms-m").value = out[1].toFixed(6); + document.getElementById("lms-s").value = out[2].toFixed(6); + } + + function xyz2oklab(x, y, z) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.xyz2oklab(x, y, z, outPtr); + document.getElementById("oklab-l").value = out[0].toFixed(6); + document.getElementById("oklab-a").value = out[1].toFixed(6); + document.getElementById("oklab-b").value = out[2].toFixed(6); + } + + function xyz2xyb(x, y, z) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.xyz2xyb(x, y, z, outPtr); + document.getElementById("xyb-x").value = out[0].toFixed(6); + document.getElementById("xyb-y").value = out[1].toFixed(6); + document.getElementById("xyb-b").value = out[2].toFixed(6); } function updateFromXyz() { @@ -308,52 +436,82 @@ if (x < 0) document.getElementById("xyz-x").value = 0; if (y < 0) document.getElementById("xyz-y").value = 0; if (z < 0) document.getElementById("xyz-z").value = 0; - if (x > 95.050) document.getElementById("xyz-x").value = 95.050; + if (x > 95.05) document.getElementById("xyz-x").value = 95.05; if (y > 100) document.getElementById("xyz-y").value = 100; - if (z > 108.900) document.getElementById("xyz-z").value = 108.900; + if (z > 108.9) document.getElementById("xyz-z").value = 108.9; xyz2rgb(x, y, z); xyz2hsl(x, y, z); xyz2hsv(x, y, z); xyz2lab(x, y, z); + xyz2lms(x, y, z); + xyz2oklab(x, y, z); + xyz2xyb(x, y, z); updateHex(); } // --- LAB --- function lab2rgb(l, a, b) { - const rgbPtr = wasm_exports.alloc(3); - const rgb = new Uint8Array(wasm_exports.memory.buffer, rgbPtr, 3); - wasm_exports.lab2rgb(l, a, b, rgbPtr); - document.getElementById("rgb-r").value = rgb[0]; - document.getElementById("rgb-g").value = rgb[1]; - document.getElementById("rgb-b").value = rgb[2]; + const outPtr = wasmExports.alloc(3); + const out = new Uint8Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.lab2rgb(l, a, b, outPtr); + document.getElementById("rgb-r").value = out[0]; + document.getElementById("rgb-g").value = out[1]; + document.getElementById("rgb-b").value = out[2]; } function lab2hsl(l, a, b) { - const hslPtr = wasm_exports.alloc(3 * 4); - const hsl = new Float64Array(wasm_exports.memory.buffer, hslPtr, 3); - wasm_exports.lab2hsl(l, a, b, hslPtr); - document.getElementById("hsl-h").value = hsl[0].toFixed(3); - document.getElementById("hsl-s").value = hsl[1].toFixed(3); - document.getElementById("hsl-l").value = hsl[2].toFixed(3); + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.lab2hsl(l, a, b, outPtr); + document.getElementById("hsl-h").value = out[0].toFixed(6); + document.getElementById("hsl-s").value = out[1].toFixed(6); + document.getElementById("hsl-l").value = out[2].toFixed(6); } function lab2hsv(l, a, b) { - const hsvPtr = wasm_exports.alloc(3 * 4); - const hsv = new Float64Array(wasm_exports.memory.buffer, hsvPtr, 3); - wasm_exports.lab2hsv(l, a, b, hsvPtr); - document.getElementById("hsv-h").value = hsv[0].toFixed(3); - document.getElementById("hsv-s").value = hsv[1].toFixed(3); - document.getElementById("hsv-v").value = hsv[2].toFixed(3); + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.lab2hsv(l, a, b, outPtr); + document.getElementById("hsv-h").value = out[0].toFixed(6); + document.getElementById("hsv-s").value = out[1].toFixed(6); + document.getElementById("hsv-v").value = out[2].toFixed(6); } function lab2xyz(l, a, b) { - const xyzPtr = wasm_exports.alloc(3 * 4); - const xyz = new Float64Array(wasm_exports.memory.buffer, xyzPtr, 3); - wasm_exports.lab2xyz(l, a, b, xyzPtr); - document.getElementById("xyz-x").value = xyz[0].toFixed(3); - document.getElementById("xyz-y").value = xyz[1].toFixed(3); - document.getElementById("xyz-z").value = xyz[2].toFixed(3); + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.lab2xyz(l, a, b, outPtr); + document.getElementById("xyz-x").value = out[0].toFixed(6); + document.getElementById("xyz-y").value = out[1].toFixed(6); + document.getElementById("xyz-z").value = out[2].toFixed(6); + } + + function lab2lms(l, a, b) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.lab2lms(l, a, b, outPtr); + document.getElementById("lms-l").value = out[0].toFixed(6); + document.getElementById("lms-m").value = out[1].toFixed(6); + document.getElementById("lms-s").value = out[2].toFixed(6); + } + + function lab2oklab(l, a, b) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.lab2oklab(l, a, b, outPtr); + document.getElementById("oklab-l").value = out[0].toFixed(6); + document.getElementById("oklab-a").value = out[1].toFixed(6); + document.getElementById("oklab-b").value = out[2].toFixed(6); + } + + function lab2xyb(l, a, b) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.lab2xyb(l, a, b, outPtr); + document.getElementById("xyb-x").value = out[0].toFixed(6); + document.getElementById("xyb-y").value = out[1].toFixed(6); + document.getElementById("xyb-b").value = out[2].toFixed(6); } function updateFromLab() { @@ -370,6 +528,254 @@ lab2hsl(l, a, b); lab2hsv(l, a, b); lab2xyz(l, a, b); + lab2lms(l, a, b); + lab2oklab(l, a, b); + lab2xyb(l, a, b); + updateHex(); + } + + // --- LMS --- + + function lms2rgb(l, m, s) { + const outPtr = wasmExports.alloc(3); + const out = new Uint8Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.lms2rgb(l, m, s, outPtr); + document.getElementById("rgb-r").value = out[0]; + document.getElementById("rgb-g").value = out[1]; + document.getElementById("rgb-b").value = out[2]; + } + + function lms2hsl(l, m, s) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.lms2hsl(l, m, s, outPtr); + document.getElementById("hsl-h").value = out[0].toFixed(6); + document.getElementById("hsl-s").value = out[1].toFixed(6); + document.getElementById("hsl-l").value = out[2].toFixed(6); + } + + function lms2hsv(l, m, s) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.lms2hsv(l, m, s, outPtr); + document.getElementById("hsv-h").value = out[0].toFixed(6); + document.getElementById("hsv-s").value = out[1].toFixed(6); + document.getElementById("hsv-v").value = out[2].toFixed(6); + } + + function lms2xyz(l, m, s) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.lms2xyz(l, m, s, outPtr); + document.getElementById("xyz-x").value = out[0].toFixed(6); + document.getElementById("xyz-y").value = out[1].toFixed(6); + document.getElementById("xyz-z").value = out[2].toFixed(6); + } + + function lms2lab(l, m, s) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.lms2lab(l, m, s, outPtr); + document.getElementById("lab-l").value = out[0].toFixed(6); + document.getElementById("lab-a").value = out[1].toFixed(6); + document.getElementById("lab-b").value = out[2].toFixed(6); + } + + function lms2oklab(l, m, s) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.lms2oklab(l, m, s, outPtr); + document.getElementById("oklab-l").value = out[0].toFixed(6); + document.getElementById("oklab-a").value = out[1].toFixed(6); + document.getElementById("oklab-b").value = out[2].toFixed(6); + } + + function lms2xyb(l, m, s) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.lms2xyb(l, m, s, outPtr); + document.getElementById("xyb-x").value = out[0].toFixed(6); + document.getElementById("xyb-y").value = out[1].toFixed(6); + document.getElementById("xyb-b").value = out[2].toFixed(6); + } + + function updateFromLms() { + const l = parseFloat(document.getElementById("lms-l").value); + const a = parseFloat(document.getElementById("lms-m").value); + const b = parseFloat(document.getElementById("lms-s").value); + if (l < 0) document.getElementById("lms-l").value = 0; + if (a < 0) document.getElementById("lms-m").value = 0; + if (b < 0) document.getElementById("lms-s").value = 0; + lms2rgb(l, a, b); + lms2hsl(l, a, b); + lms2hsv(l, a, b); + lms2xyz(l, a, b); + lms2lab(l, a, b); + lms2oklab(l, a, b); + lms2xyb(l, a, b); + updateHex(); + } + + // --- Oklab--- + + function oklab2rgb(l, a, b) { + const outPtr = wasmExports.alloc(3); + const out = new Uint8Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.oklab2rgb(l, a, b, outPtr); + document.getElementById("rgb-r").value = out[0]; + document.getElementById("rgb-g").value = out[1]; + document.getElementById("rgb-b").value = out[2]; + } + + function oklab2hsl(l, a, b) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.oklab2hsl(l, a, b, outPtr); + document.getElementById("hsl-h").value = out[0].toFixed(6); + document.getElementById("hsl-s").value = out[1].toFixed(6); + document.getElementById("hsl-l").value = out[2].toFixed(6); + } + + function oklab2hsv(l, a, b) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.oklab2hsv(l, a, b, outPtr); + document.getElementById("hsv-h").value = out[0].toFixed(6); + document.getElementById("hsv-s").value = out[1].toFixed(6); + document.getElementById("hsv-v").value = out[2].toFixed(6); + } + + function oklab2xyz(l, a, b) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.oklab2xyz(l, a, b, outPtr); + document.getElementById("xyz-x").value = out[0].toFixed(6); + document.getElementById("xyz-y").value = out[1].toFixed(6); + document.getElementById("xyz-z").value = out[2].toFixed(6); + } + + function oklab2lab(l, a, b) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.oklab2lab(l, a, b, outPtr); + document.getElementById("lab-l").value = out[0].toFixed(6); + document.getElementById("lab-a").value = out[1].toFixed(6); + document.getElementById("lab-b").value = out[2].toFixed(6); + } + + function oklab2lms(l, a, b) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.oklab2lms(l, a, b, outPtr); + document.getElementById("lms-l").value = out[0].toFixed(6); + document.getElementById("lms-m").value = out[1].toFixed(6); + document.getElementById("lms-s").value = out[2].toFixed(6); + } + + function oklab2xyb(l, a, b) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.oklab2xyb(l, a, b, outPtr); + document.getElementById("xyb-x").value = out[0].toFixed(6); + document.getElementById("xyb-y").value = out[1].toFixed(6); + document.getElementById("xyb-b").value = out[2].toFixed(6); + } + + function updateFromOklab() { + const l = parseFloat(document.getElementById("oklab-l").value); + const a = parseFloat(document.getElementById("oklab-a").value); + const b = parseFloat(document.getElementById("oklab-b").value); + if (l < 0) document.getElementById("oklab-l").value = 0; + if (a < -0.5) document.getElementById("oklab-a").value = -0.5; + if (b < -0.5) document.getElementById("oklab-b").value = -0.5; + if (l > 1) document.getElementById("oklab-l").value = 1; + if (a > 0.5) document.getElementById("oklab-a").value = 0.5; + if (b > 0.5) document.getElementById("oklab-b").value = 0.5; + oklab2rgb(l, a, b); + oklab2hsl(l, a, b); + oklab2hsv(l, a, b); + oklab2xyz(l, a, b); + oklab2lms(l, a, b); + oklab2lab(l, a, b); + oklab2xyb(l, a, b); + updateHex(); + } + + function xyb2rgb(x, y, b) { + const outPtr = wasmExports.alloc(3); + const out = new Uint8Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.xyb2rgb(x, y, b, outPtr); + document.getElementById("rgb-r").value = out[0]; + document.getElementById("rgb-g").value = out[1]; + document.getElementById("rgb-b").value = out[2]; + } + + function xyb2hsl(x, y, b) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.xyb2hsl(x, y, b, outPtr); + document.getElementById("hsl-h").value = out[0].toFixed(6); + document.getElementById("hsl-s").value = out[1].toFixed(6); + document.getElementById("hsl-l").value = out[2].toFixed(6); + } + + function xyb2hsv(x, y, b) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.xyb2hsv(x, y, b, outPtr); + document.getElementById("hsv-h").value = out[0].toFixed(6); + document.getElementById("hsv-s").value = out[1].toFixed(6); + document.getElementById("hsv-v").value = out[2].toFixed(6); + } + + function xyb2lab(x, y, b) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.xyb2lab(x, y, b, outPtr); + document.getElementById("lab-l").value = out[0].toFixed(6); + document.getElementById("lab-a").value = out[1].toFixed(6); + document.getElementById("lab-b").value = out[2].toFixed(6); + } + + function xyb2xyz(x, y, b) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.xyb2xyz(x, y, b, outPtr); + document.getElementById("xyz-x").value = out[0].toFixed(6); + document.getElementById("xyz-y").value = out[1].toFixed(6); + document.getElementById("xyz-z").value = out[2].toFixed(6); + } + + function xyb2lms(x, y, b) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.xyb2lms(x, y, b, outPtr); + document.getElementById("lms-l").value = Math.max(0, out[0]).toFixed(6); + document.getElementById("lms-m").value = Math.max(0, out[1]).toFixed(6); + document.getElementById("lms-s").value = Math.max(0, out[2]).toFixed(6); + } + + function xyb2oklab(x, y, b) { + const outPtr = wasmExports.alloc(3 * 4); + const out = new Float64Array(wasmExports.memory.buffer, outPtr, 3); + wasmExports.xyb2oklab(x, y, b, outPtr); + document.getElementById("oklab-l").value = out[0].toFixed(6); + document.getElementById("oklab-a").value = out[1].toFixed(6); + document.getElementById("oklab-b").value = out[2].toFixed(6); + } + + function updateFromXyb() { + const x = parseFloat(document.getElementById("xyb-x").value); + const y = parseFloat(document.getElementById("xyb-y").value); + const b = parseFloat(document.getElementById("xyb-b").value); + if (b < 0) document.getElementById("xyb-b").value = 0; + xyb2rgb(x, y, b); + xyb2hsl(x, y, b); + xyb2hsv(x, y, b); + xyb2xyz(x, y, b); + xyb2lab(x, y, b); + xyb2lms(x, y, b); + xyb2oklab(x, y, b); updateHex(); } }); diff --git a/examples/lib/styles.css b/examples/lib/styles.css index 7d1430a..edfc415 100644 --- a/examples/lib/styles.css +++ b/examples/lib/styles.css @@ -24,12 +24,14 @@ top: 0px; } -#canvas { +#canvas-perlin { border: 1px solid black; } -#video { - position: absolute; +#canvas-seam{ + border: 1px solid black; + width: 640px; + height: auto; } #toggle-button { diff --git a/examples/src/colorspace.zig b/examples/src/colorspace.zig index ba5067e..7949b8d 100644 --- a/examples/src/colorspace.zig +++ b/examples/src/colorspace.zig @@ -1,17 +1,20 @@ const std = @import("std"); const builtin = @import("builtin"); -pub const std_options: std.Options = .{ - .logFn = if (builtin.cpu.arch.isWasm()) @import("js.zig").logFn else std.log.defaultLog, - .log_level = std.log.default_level, -}; - const convert = @import("zignal").colorspace.convert; -const Rgb = @import("zignal").Rgb; const Hsl = @import("zignal").Hsl; const Hsv = @import("zignal").Hsv; -const Xyz = @import("zignal").Xyz; const Lab = @import("zignal").Lab; +const Lms = @import("zignal").Lms; +const Oklab = @import("zignal").Oklab; +const Rgb = @import("zignal").Rgb; +const Xyb = @import("zignal").Xyb; +const Xyz = @import("zignal").Xyz; + +pub const std_options: std.Options = .{ + .logFn = if (builtin.cpu.arch.isWasm()) @import("js.zig").logFn else std.log.defaultLog, + .log_level = std.log.default_level, +}; // --- RGB --- @@ -34,19 +37,42 @@ export fn rgb2hsv(red: u8, green: u8, blue: u8, out: [*]f64) void { export fn rgb2xyz(red: u8, green: u8, blue: u8, out: [*]f64) void { const res = convert(Xyz, Rgb{ .r = red, .g = green, .b = blue }); std.log.debug("XYZ: {[x]d} {[y]d} {[z]d}", res); - out[0] = @floatCast(res.x); - out[1] = @floatCast(res.y); - out[2] = @floatCast(res.z); + out[0] = res.x; + out[1] = res.y; + out[2] = res.z; } export fn rgb2lab(red: u8, green: u8, blue: u8, out: [*]f64) void { const res = convert(Lab, Rgb{ .r = red, .g = green, .b = blue }); - std.log.debug("LAB: {[l]d} {[a]d} {[b]d}", res); - out[0] = @floatCast(res.l); - out[1] = @floatCast(res.a); - out[2] = @floatCast(res.b); + std.log.debug("Lab: {[l]d} {[a]d} {[b]d}", res); + out[0] = res.l; + out[1] = res.a; + out[2] = res.b; +} + +export fn rgb2lms(red: u8, green: u8, blue: u8, out: [*]f64) void { + const res = convert(Lms, Rgb{ .r = red, .g = green, .b = blue }); + std.log.debug("LMS: {[l]d} {[m]d} {[s]d}", res); + out[0] = res.l; + out[1] = res.m; + out[2] = res.s; } +export fn rgb2oklab(red: u8, green: u8, blue: u8, out: [*]f64) void { + const res = convert(Oklab, Rgb{ .r = red, .g = green, .b = blue }); + std.log.debug("Oklab: {[l]d} {[a]d} {[b]d}", res); + out[0] = res.l; + out[1] = res.a; + out[2] = res.b; +} + +export fn rgb2xyb(red: u8, green: u8, blue: u8, out: [*]f64) void { + const res = convert(Xyb, Rgb{ .r = red, .g = green, .b = blue }); + std.log.debug("Xyb: {[x]d} {[y]d} {[b]d}", res); + out[0] = res.x; + out[1] = res.y; + out[2] = res.b; +} // --- HSL --- export fn hsl2rgb(h: f64, s: f64, l: f64, out: [*]u8) void { @@ -68,17 +94,41 @@ export fn hsl2hsv(h: f64, s: f64, l: f64, out: [*]f64) void { export fn hsl2xyz(h: f64, s: f64, l: f64, out: [*]f64) void { const res = convert(Xyz, Hsl{ .h = h, .s = s, .l = l }); std.log.debug("XYZ: {[x]d} {[y]d} {[z]d}", res); - out[0] = @floatCast(res.x); - out[1] = @floatCast(res.y); - out[2] = @floatCast(res.z); + out[0] = res.x; + out[1] = res.y; + out[2] = res.z; } export fn hsl2lab(h: f64, s: f64, l: f64, out: [*]f64) void { const res = convert(Lab, Hsl{ .h = h, .s = s, .l = l }); std.log.debug("LAB: {[l]d} {[a]d} {[b]d}", res); - out[0] = @floatCast(res.l); - out[1] = @floatCast(res.a); - out[2] = @floatCast(res.b); + out[0] = res.l; + out[1] = res.a; + out[2] = res.b; +} + +export fn hsl2lms(h: f64, s: f64, l: f64, out: [*]f64) void { + const res = convert(Lms, Hsl{ .h = h, .s = s, .l = l }); + std.log.debug("LMS: {[l]d} {[m]d} {[s]d}", res); + out[0] = res.l; + out[1] = res.m; + out[2] = res.s; +} + +export fn hsl2oklab(h: f64, s: f64, l: f64, out: [*]f64) void { + const res = convert(Oklab, Hsl{ .h = h, .s = s, .l = l }); + std.log.debug("Oklab: {[l]d} {[a]d} {[b]d}", res); + out[0] = res.l; + out[1] = res.a; + out[2] = res.b; +} + +export fn hsl2xyb(h: f64, s: f64, l: f64, out: [*]f64) void { + const res = convert(Xyb, Hsl{ .h = h, .s = s, .l = l }); + std.log.debug("Xyb: {[x]d} {[y]d} {[b]d}", res); + out[0] = res.x; + out[1] = res.y; + out[2] = res.b; } // --- HSV --- @@ -102,17 +152,41 @@ export fn hsv2hsl(h: f64, s: f64, v: f64, out: [*]f64) void { export fn hsv2xyz(h: f64, s: f64, v: f64, out: [*]f64) void { const res = convert(Xyz, Hsv{ .h = h, .s = s, .v = v }); std.log.debug("XYZ: {[x]d} {[y]d} {[z]d}", res); - out[0] = @floatCast(res.x); - out[1] = @floatCast(res.y); - out[2] = @floatCast(res.z); + out[0] = res.x; + out[1] = res.y; + out[2] = res.z; } export fn hsv2lab(h: f64, s: f64, v: f64, out: [*]f64) void { const res = convert(Lab, Hsv{ .h = h, .s = s, .v = v }); std.log.debug("LAB: {[l]d} {[a]d} {[b]d}", res); - out[0] = @floatCast(res.l); - out[1] = @floatCast(res.a); - out[2] = @floatCast(res.b); + out[0] = res.l; + out[1] = res.a; + out[2] = res.b; +} + +export fn hsv2lms(h: f64, s: f64, v: f64, out: [*]f64) void { + const res = convert(Lms, Hsv{ .h = h, .s = s, .v = v }); + std.log.debug("LMS: {[l]d} {[m]d} {[s]d}", res); + out[0] = res.l; + out[1] = res.m; + out[2] = res.s; +} + +export fn hsv2oklab(h: f64, s: f64, v: f64, out: [*]f64) void { + const res = convert(Oklab, Hsv{ .h = h, .s = s, .v = v }); + std.log.debug("Oklab: {[l]d} {[a]d} {[b]d}", res); + out[0] = res.l; + out[1] = res.a; + out[2] = res.b; +} + +export fn hsv2xyb(h: f64, s: f64, v: f64, out: [*]f64) void { + const res = convert(Xyb, Hsv{ .h = h, .s = s, .v = v }); + std.log.debug("Xyb: {[x]d} {[y]d} {[b]d}", res); + out[0] = res.x; + out[1] = res.y; + out[2] = res.b; } // --- XYZ --- @@ -144,9 +218,33 @@ export fn xyz2hsv(x: f64, y: f64, z: f64, out: [*]f64) void { export fn xyz2lab(x: f64, y: f64, z: f64, out: [*]f64) void { const res = convert(Lab, Xyz{ .x = x, .y = y, .z = z }); std.log.debug("LAB: {[l]d} {[a]d} {[b]d}", res); - out[0] = @floatCast(res.l); - out[1] = @floatCast(res.a); - out[2] = @floatCast(res.b); + out[0] = res.l; + out[1] = res.a; + out[2] = res.b; +} + +export fn xyz2lms(l: f64, m: f64, s: f64, out: [*]f64) void { + const res = convert(Lms, Xyz{ .x = l, .y = m, .z = s }); + std.log.debug("LMS: {[l]d} {[m]d} {[s]d}", res); + out[0] = res.l; + out[1] = res.m; + out[2] = res.s; +} + +export fn xyz2oklab(x: f64, y: f64, z: f64, out: [*]f64) void { + const res = convert(Oklab, Xyz{ .x = x, .y = y, .z = z }); + std.log.debug("Oklab: {[l]d} {[a]d} {[b]d}", res); + out[0] = res.l; + out[1] = res.a; + out[2] = res.b; +} + +export fn xyz2xyb(x: f64, y: f64, z: f64, out: [*]f64) void { + const res = convert(Xyb, Xyz{ .x = x, .y = y, .z = z }); + std.log.debug("Xyb: {[x]d} {[y]d} {[b]d}", res); + out[0] = res.x; + out[1] = res.y; + out[2] = res.b; } // --- LAB --- @@ -178,7 +276,205 @@ export fn lab2hsv(l: f64, a: f64, b: f64, out: [*]f64) void { export fn lab2xyz(l: f64, a: f64, b: f64, out: [*]f64) void { const res = convert(Xyz, Lab{ .l = l, .a = a, .b = b }); std.log.debug("XYZ: {[x]d} {[y]d} {[z]d}", res); - out[0] = @floatCast(res.x); - out[1] = @floatCast(res.y); - out[2] = @floatCast(res.z); + out[0] = res.x; + out[1] = res.y; + out[2] = res.z; +} + +export fn lab2lms(l: f64, a: f64, b: f64, out: [*]f64) void { + const res = convert(Lms, Lab{ .l = l, .a = a, .b = b }); + std.log.debug("LMS: {[l]d} {[m]d} {[s]d}", res); + out[0] = res.l; + out[1] = res.m; + out[2] = res.s; +} + +export fn lab2oklab(l: f64, a: f64, b: f64, out: [*]f64) void { + const res = convert(Oklab, Lab{ .l = l, .a = a, .b = b }); + std.log.debug("Oklab: {[l]d} {[a]d} {[b]d}", res); + out[0] = res.l; + out[1] = res.a; + out[2] = res.b; +} + +export fn lab2xyb(l: f64, a: f64, b: f64, out: [*]f64) void { + const res = convert(Xyb, Lab{ .l = l, .a = a, .b = b }); + std.log.debug("Xyb: {[x]d} {[y]d} {[b]d}", res); + out[0] = res.x; + out[1] = res.y; + out[2] = res.b; +} + +// --- LMS --- + +export fn lms2rgb(l: f64, m: f64, s: f64, out: [*]u8) void { + const res = convert(Rgb, Lms{ .l = l, .m = m, .s = s }); + std.log.debug("RGB: {[r]d} {[g]d} {[b]d}", res); + out[0] = res.r; + out[1] = res.g; + out[2] = res.b; +} + +export fn lms2hsl(l: f64, m: f64, s: f64, out: [*]f64) void { + const res = convert(Hsl, Lms{ .l = l, .m = m, .s = s }); + std.log.debug("HSL: {[h]d} {[s]d} {[l]d}", res); + out[0] = res.h; + out[1] = res.s; + out[2] = res.l; +} + +export fn lms2hsv(l: f64, m: f64, s: f64, out: [*]f64) void { + const res = convert(Hsv, Lms{ .l = l, .m = m, .s = s }); + std.log.debug("HSV: {[h]d} {[s]d} {[v]d}", res); + out[0] = res.h; + out[1] = res.s; + out[2] = res.v; +} + +export fn lms2xyz(l: f64, m: f64, s: f64, out: [*]f64) void { + const res = convert(Xyz, Lms{ .l = l, .m = m, .s = s }); + std.log.debug("XYZ: {[x]d} {[y]d} {[z]d}", res); + out[0] = res.x; + out[1] = res.y; + out[2] = res.z; +} + +export fn lms2lab(l: f64, m: f64, s: f64, out: [*]f64) void { + const res = convert(Lms, Lms{ .l = l, .m = m, .s = s }); + std.log.debug("LMS: {[l]d} {[m]d} {[s]d}", res); + out[0] = res.l; + out[1] = res.m; + out[2] = res.s; +} + +export fn lms2oklab(h: f64, m: f64, s: f64, out: [*]f64) void { + const res = convert(Oklab, Lms{ .l = h, .m = m, .s = s }); + std.log.debug("Oklab: {[l]d} {[a]d} {[b]d}", res); + out[0] = res.l; + out[1] = res.a; + out[2] = res.b; +} + +export fn lms2xyb(l: f64, m: f64, s: f64, out: [*]f64) void { + const res = convert(Xyb, Lms{ .l = l, .m = m, .s = s }); + std.log.debug("Xyb: {[x]d} {[y]d} {[b]d}", res); + out[0] = res.x; + out[1] = res.y; + out[2] = res.b; +} + +// --- Oklab --- + +export fn oklab2rgb(l: f64, a: f64, b: f64, out: [*]u8) void { + const res = convert(Rgb, Oklab{ .l = l, .a = a, .b = b }); + std.log.debug("RGB: {[r]d} {[g]d} {[b]d}", res); + out[0] = res.r; + out[1] = res.g; + out[2] = res.b; +} + +export fn oklab2hsl(l: f64, a: f64, b: f64, out: [*]f64) void { + const res = convert(Hsl, Oklab{ .l = l, .a = a, .b = b }); + std.log.debug("HSL: {[h]d} {[s]d} {[l]d}", res); + out[0] = res.h; + out[1] = res.s; + out[2] = res.l; +} + +export fn oklab2hsv(l: f64, a: f64, b: f64, out: [*]f64) void { + const res = convert(Hsv, Oklab{ .l = l, .a = a, .b = b }); + std.log.debug("HSV: {[h]d} {[s]d} {[v]d}", res); + out[0] = res.h; + out[1] = res.s; + out[2] = res.v; +} + +export fn oklab2xyz(l: f64, a: f64, b: f64, out: [*]f64) void { + const res = convert(Xyz, Oklab{ .l = l, .a = a, .b = b }); + std.log.debug("XYZ: {[x]d} {[y]d} {[z]d}", res); + out[0] = res.x; + out[1] = res.y; + out[2] = res.z; +} + +export fn oklab2lms(l: f64, a: f64, b: f64, out: [*]f64) void { + const res = convert(Lms, Oklab{ .l = l, .a = a, .b = b }); + std.log.debug("LMS: {[l]d} {[m]d} {[s]d}", res); + out[0] = res.l; + out[1] = res.m; + out[2] = res.s; +} + +export fn oklab2lab(l: f64, a: f64, b: f64, out: [*]f64) void { + const res = convert(Lab, Oklab{ .l = l, .a = a, .b = b }); + std.log.debug("Oklab: {[l]d} {[a]d} {[b]d}", res); + out[0] = res.l; + out[1] = res.a; + out[2] = res.b; +} + +export fn oklab2xyb(l: f64, a: f64, b: f64, out: [*]f64) void { + const res = convert(Xyb, Oklab{ .l = l, .a = a, .b = b }); + std.log.debug("Xyb: {[x]d} {[y]d} {[b]d}", res); + out[0] = res.x; + out[1] = res.y; + out[2] = res.b; +} + +// --- XYB --- + +export fn xyb2rgb(x: f64, y: f64, b: f64, out: [*]u8) void { + const res = convert(Rgb, Xyb{ .x = x, .y = y, .b = b }); + std.log.debug("RGB: {[r]d} {[g]d} {[b]d}", res); + out[0] = res.r; + out[1] = res.g; + out[2] = res.b; +} + +export fn xyb2hsl(x: f64, y: f64, b: f64, out: [*]f64) void { + const res = convert(Hsl, Xyb{ .x = x, .y = y, .b = b }); + std.log.debug("HSL: {[h]d} {[s]d} {[l]d}", res); + out[0] = res.h; + out[1] = res.s; + out[2] = res.l; +} + +export fn xyb2hsv(x: f64, y: f64, b: f64, out: [*]f64) void { + const res = convert(Hsv, Xyb{ .x = x, .y = y, .b = b }); + std.log.debug("HSV: {[h]d} {[s]d} {[v]d}", res); + out[0] = res.h; + out[1] = res.s; + out[2] = res.v; +} + +export fn xyb2xyz(x: f64, y: f64, b: f64, out: [*]f64) void { + const res = convert(Xyz, Xyb{ .x = x, .y = y, .b = b }); + std.log.debug("XYZ: {[x]d} {[y]d} {[z]d}", res); + out[0] = res.x; + out[1] = res.y; + out[2] = res.z; +} + +export fn xyb2lms(x: f64, y: f64, b: f64, out: [*]f64) void { + const res = convert(Lms, Xyb{ .x = x, .y = y, .b = b }); + std.log.debug("LMS: {[l]d} {[m]d} {[s]d}", res); + out[0] = res.l; + out[1] = res.m; + out[2] = res.s; +} + +export fn xyb2oklab(x: f64, y: f64, b: f64, out: [*]f64) void { + const res = convert(Oklab, Xyb{ .x = x, .y = y, .b = b }); + std.log.debug("Oklab: {[l]d} {[a]d} {[b]d}", res); + out[0] = res.l; + out[1] = res.a; + out[2] = res.b; +} + +export fn xyb2lab(x: f64, y: f64, b: f64, out: [*]f64) void { + const res = convert(Lab, Xyb{ .x = x, .y = y, .b = b }); + std.log.debug("Lab: {[l]d} {[a]d} {[b]d}", res); + out[0] = res.l; + out[1] = res.a; + out[2] = res.b; } diff --git a/src/colorspace.zig b/src/colorspace.zig index d3176ff..b80b635 100644 --- a/src/colorspace.zig +++ b/src/colorspace.zig @@ -8,11 +8,18 @@ const pow = std.math.pow; /// Returns true if and only if T can be treated as a color. pub fn isColor(comptime T: type) bool { return switch (T) { - u8, Rgb, Rgba, Hsl, Hsv, Xyz, Lab => true, + u8, Rgb, Rgba, Hsl, Hsv, Xyz, Lab, Lms, Oklab, Xyb => true, else => false, }; } +fn assertAllFieldTypesAreSame(comptime T: type) void { + comptime assert(isColor(T)); + return for (std.meta.fields(T)) |field| { + if (std.meta.fields(T)[0].type != field.type) break false; + } else true; +} + /// Checks whether a type T can be used as an Rgb color, i.e., it has r, g, b fields of type u8. fn isRgbCompatible(comptime T: type) bool { if (T == Rgb or T == Rgba) return true; @@ -71,6 +78,21 @@ pub fn convert(comptime T: type, color: anytype) T { u8 => .{ .l = @as(f64, @floatFromInt(color)) / 255 * 100, .a = 0, .b = 0 }, inline else => color.toLab(), }, + Lms => switch (@TypeOf(color)) { + Lms => color, + u8 => .{}, + inline else => color.toLms(), + }, + Oklab => switch (@TypeOf(color)) { + Oklab => color, + u8 => .{}, + inline else => color.toOklab(), + }, + Xyb => switch (@TypeOf(color)) { + Xyb => color, + u8 => .{}, + inline else => color.toXyb(), + }, else => @compileError("Unsupported color " ++ @typeName(T)), }; } @@ -84,9 +106,12 @@ test "isRgbCompatible" { try comptime expectEqual(isRgbCompatible(Hsv), false); try comptime expectEqual(isRgbCompatible(Xyz), false); try comptime expectEqual(isRgbCompatible(Lab), false); + try comptime expectEqual(isRgbCompatible(Lms), false); + try comptime expectEqual(isRgbCompatible(Oklab), false); + try comptime expectEqual(isRgbCompatible(Xyb), false); } -test "convert" { +test "convert grayscale" { try expectEqual(convert(u8, Rgb{ .r = 128, .g = 128, .b = 128 }), 128); try expectEqual(convert(u8, Hsl{ .h = 0, .s = 100, .l = 50 }), 128); try expectEqual(convert(u8, Hsv{ .h = 0, .s = 100, .v = 50 }), 128); @@ -113,15 +138,15 @@ test "alphaBlend" { try expectEqualDeep(output, Rgb{ .r = 128, .g = 128, .b = 128 }); } -inline fn gammaToLinear(x: f64) f64 { - return if (x > 0.04045) pow(f64, (x + 0.055) / 1.055, 2.4) else x / 12.92; -} - inline fn linearToGamma(x: f64) f64 { return if (x > 0.0031308) 1.055 * pow(f64, x, (1.0 / 2.4)) - 0.055 else x * 12.92; } -/// Helper RGB color in floating point, with each channel ranging from 0 to 1. +inline fn gammaToLinear(x: f64) f64 { + return if (x > 0.04045) pow(f64, (x + 0.055) / 1.055, 2.4) else x / 12.92; +} + +/// Helper sRGB color in floating point, with each channel ranging from 0 to 1. /// Used to perform lossless conversions between colorspaces. const RgbFloat = struct { r: f64, @@ -221,9 +246,116 @@ const RgbFloat = struct { fn toLab(self: RgbFloat) Lab { return self.toXyz().toLab(); } + + /// Converts the RGB color into an LMS color. + fn toLms(self: RgbFloat) Lms { + return self.toXyz().toLms(); + } + + /// Converts the RGB color into an Oklab color. + fn toOklab(self: RgbFloat) Oklab { + return self.toLms().toOklab(); + } + + /// Converts the RGB color into an XYB color. + fn toXyb(self: RgbFloat) Xyb { + return self.toLms().toXyb(); + } }; -/// A Red-Green-Blue-Alpha color. +/// A color in the [sRGB](https://en.wikipedia.org/wiki/SRGB) colorspace, with all components +/// within the range 0-255. +pub const Rgb = struct { + r: u8 = 0, + g: u8 = 0, + b: u8 = 0, + + /// Returns the normalized RGB color in floating point. + fn toRgbFloat(self: Rgb) RgbFloat { + return .{ + .r = @as(f64, @floatFromInt(self.r)) / 255, + .g = @as(f64, @floatFromInt(self.g)) / 255, + .b = @as(f64, @floatFromInt(self.b)) / 255, + }; + } + + /// Constructs a RGB color from a gray value. + pub fn fromGray(gray: u8) Rgb { + return .{ .r = gray, .g = gray, .b = gray }; + } + + /// Constructs a RGB color from a hex value. + pub fn fromHex(hex_code: u24) Rgb { + return .{ + .r = @intCast((hex_code >> (8 * 2)) & 0x0000ff), + .g = @intCast((hex_code >> (8 * 1)) & 0x0000ff), + .b = @intCast((hex_code >> (8 * 0)) & 0x0000ff), + }; + } + + /// Alpha-blends color into self. + pub fn blend(self: *Rgb, color: Rgba) void { + alphaBlend(Rgb, self, color); + } + + /// Checks if the color is a shade of gray. + pub fn isGray(self: Rgb) bool { + return self.r == self.g and self.g == self.b; + } + + /// Converts the RGB color into grayscale. + pub fn toGray(self: Rgb) u8 { + return @intFromFloat(@round(self.toHsl().l / 100 * 255)); + } + + /// Converts the RGB color into a hex value. + pub fn toHex(self: Rgb) u24 { + return self.r << 16 + self.g << 8 + self.g; + } + + /// Converts the RGB color into a RGBA color with the specified alpha. + pub fn toRgba(self: Rgb, alpha: u8) Rgba { + return .{ .r = self.r, .g = self.g, .b = self.b, .a = alpha }; + } + + /// Converts the RGB color into a HSL color. + pub fn toHsl(self: Rgb) Hsl { + return self.toRgbFloat().toHsl(); + } + + /// Converts the RGB color into a HSV color. + pub fn toHsv(self: Rgb) Hsv { + return self.toRgbFloat().toHsv(); + } + + /// Converts the RGB color into a CIE 1931 XYZ color. + pub fn toXyz(self: Rgb) Xyz { + return self.toRgbFloat().toXyz(); + } + + /// Converts the RGB color into a CIELAB color. + pub fn toLab(self: Rgb) Lab { + return self.toXyz().toLab(); + } + + /// Converts the RGB color into an LMS color. + fn toLms(self: Rgb) Lms { + return self.toRgbFloat().toLms(); + } + + /// Converts the RGB color into an Oklab color. + fn toOklab(self: Rgb) Oklab { + return self.toRgbFloat().toOklab(); + } + + /// Converts the RGB color into an XYB color. + fn toXyb(self: Rgb) Xyb { + return self.toLms().toXyb(); + } +}; + +/// A color in the [sRGB](https://en.wikipedia.org/wiki/SRGB) colorspace with alpha channel, +/// with all components within the range 0-255. pub const Rgba = packed struct { r: u8 = 0, g: u8 = 0, @@ -298,84 +430,25 @@ pub const Rgba = packed struct { pub fn toLab(self: Rgba) Lab { return self.toRgbFloat().toLab(); } -}; - -/// A color in the RGB colorspace, with all components within the range 0-255. -pub const Rgb = struct { - r: u8 = 0, - g: u8 = 0, - b: u8 = 0, - - /// Returns the normalized RGB color in floating point. - fn toRgbFloat(self: Rgb) RgbFloat { - return .{ - .r = @as(f64, @floatFromInt(self.r)) / 255, - .g = @as(f64, @floatFromInt(self.g)) / 255, - .b = @as(f64, @floatFromInt(self.b)) / 255, - }; - } - - /// Constructs a RGB color from a gray value. - pub fn fromGray(gray: u8) Rgb { - return .{ .r = gray, .g = gray, .b = gray }; - } - - /// Constructs a RGB color from a hex value. - pub fn fromHex(hex_code: u24) Rgb { - return .{ - .r = @intCast((hex_code >> (8 * 2)) & 0x0000ff), - .g = @intCast((hex_code >> (8 * 1)) & 0x0000ff), - .b = @intCast((hex_code >> (8 * 0)) & 0x0000ff), - }; - } - - /// Alpha-blends color into self. - pub fn blend(self: *Rgb, color: Rgba) void { - alphaBlend(Rgb, self, color); - } - - /// Checks if the color is a shade of gray. - pub fn isGray(self: Rgb) bool { - return self.r == self.g and self.g == self.b; - } - - /// Converts the RGB color into grayscale. - pub fn toGray(self: Rgb) u8 { - return @intFromFloat(@round(self.toHsl().l / 100 * 255)); - } - - /// Converts the RGB color into a hex value. - pub fn toHex(self: Rgb) u24 { - return self.r << 16 + self.g << 8 + self.g; - } - /// Converts the RGB color into a RGBA color with the specified alpha. - pub fn toRgba(self: Rgb, alpha: u8) Rgba { - return .{ .r = self.r, .g = self.g, .b = self.b, .a = alpha }; + /// Converts the RGBA color into an LMS color. + fn toLms(self: Rgba) Lms { + return self.toXyz().toLms(); } - /// Converts the RGB color into a HSL color. - pub fn toHsl(self: Rgb) Hsl { - return self.toRgbFloat().toHsl(); + /// Converts the RGBA color into an Oklab color. + fn toOklab(self: Rgba) Oklab { + return self.toLms().toOklab(); } - /// Converts the RGB color into a HSV color. - pub fn toHsv(self: Rgb) Hsv { - return self.toRgbFloat().toHsv(); - } - - /// Converts the RGB color into a CIE 1931 XYZ color. - pub fn toXyz(self: Rgb) Xyz { - return self.toRgbFloat().toXyz(); - } - - /// Converts the RGB color into a CIELAB color. - pub fn toLab(self: Rgb) Lab { - return self.toXyz().toLab(); + /// Converts the RGBA color into an XYB color. + fn toXyb(self: Rgba) Xyb { + return self.toLms().toXyb(); } }; -/// A color in the HSL colorspace: h in degrees (0-359), s and l between 0-100. +/// A color in the [HSL](https://en.wikipedia.org/wiki/HSL_and_HSV) colorspace: h in degrees +/// (0-359), s and l between 0-100. pub const Hsl = struct { h: f64 = 0, s: f64 = 0, @@ -470,9 +543,25 @@ pub const Hsl = struct { pub fn toLab(self: Hsl) Lab { return self.toRgbFloat().toLab(); } + + /// Converts the HSL color into an LMS color. + fn toLms(self: Hsl) Lms { + return self.toRgb().toLms(); + } + + /// Converts the HSL color into an Oklab color. + fn toOklab(self: Hsl) Oklab { + return self.toRgb().toOklab(); + } + + /// Converts the HSL color into an XYB color. + fn toXyb(self: Hsl) Xyb { + return self.toLms().toXyb(); + } }; -/// A color in the HSV colorspace: h in degrees (0-359), s and v between 0-100. +/// A color in the [HSV](https://en.wikipedia.org/wiki/HSL_and_HSV) colorspace: h in degrees +/// (0-359), s and v between 0-100. pub const Hsv = struct { h: f64 = 0, s: f64 = 0, @@ -558,12 +647,7 @@ pub const Hsv = struct { /// Converts the HSV color into an RGB color. pub fn toRgb(self: Hsv) Rgb { - const rgb = self.toRgbFloat(); - return .{ - .r = @intFromFloat(@round(255 * rgb.r)), - .g = @intFromFloat(@round(255 * rgb.g)), - .b = @intFromFloat(@round(255 * rgb.b)), - }; + return self.toRgbFloat().toRgb(); } /// Converts the HSV color into an RGBA color with the specified alpha. @@ -585,10 +669,26 @@ pub const Hsv = struct { pub fn toLab(self: Hsv) Lab { return self.toRgbFloat().toLab(); } + + /// Converts the HSV color into an LMS color. + fn toLms(self: Hsv) Lms { + return self.toRgbFloat().toLms(); + } + + /// Converts the HSV color into an Oklab color. + fn toOklab(self: Hsv) Oklab { + return self.toRgbFloat().toOklab(); + } + + /// Converts the HSV color into an XYB color. + fn toXyb(self: Hsv) Xyb { + return self.toLms().toXyb(); + } }; -/// The CIE 1931 color space, a device independent space also known as XYZ which covers the -/// full gamut of human-perceptible colors visible to the CIE 2° standard observer. +/// The [CIE 1931 color space](https://en.wikipedia.org/wiki/CIE_1931_color_space), a device +/// independent space also known as XYZ which covers the full gamut of human-perceptible colors +/// visible to the CIE 2° standard observer. pub const Xyz = struct { x: f64 = 0, y: f64 = 0, @@ -673,14 +773,41 @@ pub const Xyz = struct { .b = @max(-128, @min(127, 200.0 * (y - z))), }; } + + /// Converts the CIE 1931 XYZ color into an LMS color using the Bradford method. + pub fn toLms(self: Xyz) Lms { + return .{ + .l = (0.8951 * self.x + 0.2664 * self.y - 0.1614 * self.z) / 100, + .m = (-0.7502 * self.x + 1.7135 * self.y + 0.0367 * self.z) / 100, + .s = (0.0389 * self.x - 0.0685 * self.y + 1.0296 * self.z) / 100, + }; + } + + /// Converts the CIE 1931 XYZ color into an Oklab color. + fn toOklab(self: Xyz) Oklab { + return self.toLms().toOklab(); + } + + /// Converts the CIE 1931 XYZ color into an XYB color. + fn toXyb(self: Xyz) Xyb { + return self.toLms().toXyb(); + } }; -/// A color in the CIELAB colorspace: L: 0 to 100, a: -128 to 127, b: -128 to 127. +/// A color in the [CIELAB colorspace](https://en.wikipedia.org/wiki/CIELAB_color_space). L: +/// 0 to 100, a: -128 to 127, b: -128 to 127. pub const Lab = struct { l: f64 = 0, a: f64 = 0, b: f64 = 0, + /// Alpha-blends color into self. + pub fn blend(self: *Lab, color: Rgba) void { + var rgb = self.toRgb(); + rgb.blend(color); + self = rgb.toLab(); + } + /// Checks if the color is a shade of gray. pub fn isGray(self: Lab) bool { return self.a == 0 and self.b == 0; @@ -748,12 +875,248 @@ pub const Lab = struct { }; } + /// Converts the CIELAB color into an LMS color. + pub fn toLms(self: Lab) Lms { + return self.toXyz().toLms(); + } + + /// Converts the CIELAB color into an Oklab color. + fn toOklab(self: Lab) Oklab { + return self.toLms().toOklab(); + } + + /// Converts the CIELAB color into an XYB color. + fn toXyb(self: Lab) Xyb { + return self.toLms().toXyb(); + } +}; + +/// A color in the [Oklab](https://en.wikipedia.org/wiki/Oklab_color_space) colorspace. L: +/// 0 to 1 a: -0.5 to 0.5, b: -0.5to 0.5. +pub const Oklab = struct { + l: f64 = 0, + a: f64 = 0, + b: f64 = 0, + /// Alpha-blends color into self. - pub fn blend(self: *Lab, color: Rgba) void { + pub fn blend(self: *Oklab, color: Rgba) void { var rgb = self.toRgb(); rgb.blend(color); self = rgb.toLab(); } + + /// Checks if the color is a shade of gray. + pub fn isGray(self: Oklab) bool { + return self.a == 0 and self.b == 0; + } + + /// Converts the Oklab color into grayscale. + pub fn toGray(self: Oklab) u8 { + return @intFromFloat(@round(@max(0, @min(1, self.l)) * 255)); + } + + /// Converts the Oklab color into a RGB color. + fn toRgbFloat(self: Oklab) RgbFloat { + return self.toXyz().toRgbFloat(); + } + + /// Converts the Oklab color into a RGB color. + pub fn toRgb(self: Oklab) Rgb { + return self.toRgbFloat().toRgb(); + } + + /// Converts the Oklab color into a RGBA color with the specified alpha. + pub fn toRgba(self: Oklab, alpha: u8) Rgba { + return self.toRgb().toRgba(alpha); + } + + /// Converts the Oklab color into a HSL color. + pub fn toHsl(self: Oklab) Hsl { + return self.toRgbFloat().toHsl(); + } + + /// Converts the Oklab color into a HSV color. + pub fn toHsv(self: Oklab) Hsv { + return self.toRgbFloat().toHsv(); + } + + /// Converts the Oklab color into an LMS color. + pub fn toLms(self: Oklab) Lms { + return .{ + .l = std.math.pow(f64, 0.9999999985 * self.l + 0.3963377922 * self.a + 0.2158037581 * self.b, 3), + .m = std.math.pow(f64, 1.000000009 * self.l - 0.1055613423 * self.a - 0.06385417477 * self.b, 3), + .s = std.math.pow(f64, 1.000000055 * self.l - 0.08948418209 * self.a - 1.291485538 * self.b, 3), + }; + } + + /// Converts the Oklab color into a CIE 1931 XYZ color. + pub fn toXyz(self: Oklab) Xyz { + return self.toLms().toXyz(); + } + + /// Converts the Oklab color into a CIE Lab color. + pub fn toLab(self: Oklab) Lab { + return self.toXyz().toLab(); + } + + /// Converts the Oklab color into an XYB color. + pub fn toXyb(self: Oklab) Xyb { + return self.toLms().toXyb(); + } +}; + +/// A color in the [LMS colorspace](https://en.wikipedia.org/wiki/LMS_color_space), representing +/// the response of the three types of cones of the human eye. +pub const Lms = struct { + l: f64 = 0, + m: f64 = 0, + s: f64 = 0, + + /// Alpha-blends color into self. + pub fn blend(self: *Lms, color: Rgba) void { + var rgb = self.toRgb(); + rgb.blend(color); + self = rgb.toLms(); + } + + /// Checks if the color is a shade of gray. + pub fn isGray(self: Lms) bool { + const lab = self.toOklab(); + return lab.a == 0 and lab.b == 0; + } + + /// Converts the LMS color into grayscale. + pub fn toGray(self: Lms) u8 { + return @intFromFloat(@round(@max(0, @min(1, self.toOklab().l)) * 255)); + } + + /// Converts the LMS color into a RGB color. + fn toRgbFloat(self: Lms) RgbFloat { + return self.toXyz().toRgbFloat(); + } + + /// Converts the LMS color into a RGB color. + pub fn toRgb(self: Lms) Rgb { + return self.toRgbFloat().toRgb(); + } + + /// Converts the LMS color into a RGBA color with the specified alpha. + pub fn toRgba(self: Lms, alpha: u8) Rgba { + return self.toRgb().toRgba(alpha); + } + + /// Converts the LMS color into a HSL color. + pub fn toHsl(self: Lms) Hsl { + return self.toRgbFloat().toHsl(); + } + + /// Converts the LMS color into a HSV color. + pub fn toHsv(self: Lms) Hsv { + return self.toRgbFloat().toHsv(); + } + + /// Converts the LMS color into a CIE 1931 XYZ color using the Bradford method. + pub fn toXyz(self: Lms) Xyz { + return .{ + .x = 100 * (0.9869929 * self.l - 0.1470543 * self.m + 0.1599627 * self.s), + .y = 100 * (0.4323053 * self.l + 0.5183603 * self.m + 0.0492912 * self.s), + .z = 100 * (-0.0085287 * self.l + 0.0400428 * self.m + 0.9684867 * self.s), + }; + } + + /// Converts the LMS color into an Oklab color. + pub fn toOklab(self: Lms) Oklab { + const lp = std.math.cbrt(self.l); + const mp = std.math.cbrt(self.m); + const sp = std.math.cbrt(self.s); + return .{ + .l = 0.2104542553 * lp + 0.7936177850 * mp - 0.0040720468 * sp, + .a = 1.9779984951 * lp - 2.4285922050 * mp + 0.4505937099 * sp, + .b = 0.0259040371 * lp + 0.7827717662 * mp - 0.8086757660 * sp, + }; + } + + /// Converts the LMS color into an XYB color. + pub fn toXyb(self: Lms) Xyb { + return .{ .x = self.l - self.m, .y = self.l + self.m, .b = self.s }; + } +}; + +/// A color in the [XYB colorspace](https://en.wikipedia.org/wiki/LMS_color_space#Image_processing) +/// used in JPEG XL, which can be interpreted as a hybrid color theory where L and M are +/// opponents but S is handled in a tricromatic way. In practical terms, this allows for using +/// less data for storing blue signals without losing much perceived quality. +pub const Xyb = struct { + x: f64 = 0, + y: f64 = 0, + b: f64 = 0, + + /// Alpha-blends color into self. + pub fn blend(self: *Xyb, color: Rgba) void { + var rgb = self.toRgb(); + rgb.blend(color); + self = rgb.toXyb(); + } + + /// Checks if the color is a shade of gray. + pub fn isGray(self: Xyb) bool { + const lab = self.toOklab(); + return lab.a == 0 and lab.b == 0; + } + + /// Converts the XYB color into grayscale. + pub fn toGray(self: Xyb) u8 { + return @intFromFloat(@round(@max(0, @min(1, self.toOklab().l)) * 255)); + } + + /// Converts the XYB color into a RGB color. + fn toRgbFloat(self: Xyb) RgbFloat { + return self.toLms().toRgbFloat(); + } + + /// Converts the XYB color into a RGB color. + pub fn toRgb(self: Xyb) Rgb { + return self.toLms().toRgb(); + } + + /// Converts the XYB color into a RGBA color with the specified alpha. + pub fn toRgba(self: Xyb, alpha: u8) Rgba { + return self.toLms().toRgba(alpha); + } + + /// Converts the XYB color into a HSL color. + pub fn toHsl(self: Xyb) Hsl { + return self.toLms().toHsl(); + } + + /// Converts the XYB color into a HSV color. + pub fn toHsv(self: Xyb) Hsv { + return self.toLms().toHsv(); + } + + /// Converts the XYB color into an XYZ color. + pub fn toXyz(self: Xyb) Xyz { + return self.toLms().toXyz(); + } + + /// Converts the XYB color into a CIE LAB color. + pub fn toLab(self: Xyb) Lab { + return self.toXyz().toLab(); + } + + /// Converts the XYB into an LMS color. + pub fn toLms(self: Xyb) Lms { + return .{ + .l = 0.5 * (self.x + self.y), + .m = 0.5 * (self.y - self.x), + .s = self.b, + }; + } + + /// Converts the XYB color into an LMS color. + pub fn toOklab(self: Xyb) Oklab { + return self.toLms().toOklab(); + } }; test "hex to RGB/A" { diff --git a/src/root.zig b/src/root.zig index 351ec2e..962ba82 100644 --- a/src/root.zig +++ b/src/root.zig @@ -6,6 +6,9 @@ pub const Hsl = colorspace.Hsl; pub const Hsv = colorspace.Hsv; pub const Xyz = colorspace.Xyz; pub const Lab = colorspace.Lab; +pub const Lms = colorspace.Lms; +pub const Oklab = colorspace.Oklab; +pub const Xyb = colorspace.Xyb; const draw = @import("draw.zig"); pub const drawCircle = draw.drawCircle; pub const drawCircleFast = draw.drawCircleFast;