From 8376a451a49396c066b769d44fc826bcbc6ae83c Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Tue, 5 Mar 2024 11:59:43 -0500 Subject: [PATCH 1/8] apps/gamut-mapping Add gradient tool view --- apps/gamut-mapping/gradients.css | 36 +++++++++++++ apps/gamut-mapping/gradients.html | 47 +++++++++++++++++ apps/gamut-mapping/gradients.js | 73 +++++++++++++++++++++++++++ apps/gamut-mapping/mapped-gradient.js | 66 ++++++++++++++++++++++++ 4 files changed, 222 insertions(+) create mode 100644 apps/gamut-mapping/gradients.css create mode 100644 apps/gamut-mapping/gradients.html create mode 100644 apps/gamut-mapping/gradients.js create mode 100644 apps/gamut-mapping/mapped-gradient.js diff --git a/apps/gamut-mapping/gradients.css b/apps/gamut-mapping/gradients.css new file mode 100644 index 00000000..3b35f048 --- /dev/null +++ b/apps/gamut-mapping/gradients.css @@ -0,0 +1,36 @@ +:root { + --font-mono: Consolas, Inconsolata, Monaco, monospace; +} + +body { + font: 100%/1.5 system-ui; + max-width: 84em; + margin: 1em auto; + padding-inline: 1em; + + .gradient { + height: 100px; + display: flex; + > div { + flex: auto; + background: var(--step-color); + } + } + .color-inputs { + display: flex; + justify-content: space-between; + } + + .oog .gradient{ + height: 15px; + } + + .method .info { + display: none; + } + .gradients:not(.flush) { + .method .info { + display: block; + } + } +} diff --git a/apps/gamut-mapping/gradients.html b/apps/gamut-mapping/gradients.html new file mode 100644 index 00000000..46f41447 --- /dev/null +++ b/apps/gamut-mapping/gradients.html @@ -0,0 +1,47 @@ + + + + + + Gamut Mapping Experiments - Gradients + + + + + + +
+

Gamut Mapping Gradients

+

Use keyboard arrow keys to increment/decrement, share by copying the URL

+
+
+ +
+ +
+
{{steps.length}} Gradient Steps
+ +
+
+ + + + + + +
+
+
Gamut indicator
+
+
+
+
+
+
+ +
+
+ + \ No newline at end of file diff --git a/apps/gamut-mapping/gradients.js b/apps/gamut-mapping/gradients.js new file mode 100644 index 00000000..548f742e --- /dev/null +++ b/apps/gamut-mapping/gradients.js @@ -0,0 +1,73 @@ +import { createApp } from "https://unpkg.com/vue@3.2.37/dist/vue.esm-browser.js"; +import Color from "../../dist/color.js"; +import methods from "./methods.js"; +import Gradient from "./mapped-gradient.js"; + +globalThis.Color = Color; + +let app = createApp({ + data () { + let params = new URLSearchParams(location.search); + let urlFromColor = params.getAll("from").filter(Boolean); + let urlToColor = params.getAll("to").filter(Boolean); + return { + methods: ["none", "clip", "scale-lh", "css", "raytrace", "edge-seeker"], + Color, + from: "oklch(90% .8 250)", + to: "oklch(40% .1 20)", + space: "oklch", + maxDeltaE: 10, + flush: false, + interpolationSpaces: ["oklch", "oklab", "p3", "rec2020", "lab"], + }; + }, + + computed: { + colors () { + return [this.from, this.to]; + }, + + steps () { + const from = new Color(this.colors[0]); + const to = new Color(this.colors[1]); + let steps = from.steps(to, { + maxDeltaE: this.maxDeltaE, + space: this.space, + }); + return steps; + }, + oogSteps () { + return this.steps.map(step => { + switch (true) { + case step.inGamut("srgb"): + return ["in srgb", "yellowgreen"]; + case step.inGamut("p3"): + return ["in p3", "gold"]; + case step.inGamut("rec2020"): + return ["in rec2020", "orange"]; + default: + return ["out of rec2020", "red"]; + } + }); + }, + }, + + methods: { + + }, + + watch: { + + }, + + components: { + "mapped-gradient": Gradient, + }, + compilerOptions: { + isCustomElement (tag) { + return tag === "css-color"; + }, + }, +}).mount(document.body); + +globalThis.app = app; diff --git a/apps/gamut-mapping/mapped-gradient.js b/apps/gamut-mapping/mapped-gradient.js new file mode 100644 index 00000000..bfe5dc63 --- /dev/null +++ b/apps/gamut-mapping/mapped-gradient.js @@ -0,0 +1,66 @@ +import methods from "./methods.js"; + +export default { + props: { + method: String | Object, + steps: Array, + }, + + data () { + return { + time: 0, + mappedSteps: [], + }; + }, + + computed: { + name () { + return methods[this.method]?.label || "None"; + }, + }, + + methods: { + mapSteps () { + const start = performance.now(); + let steps = this.steps.map(step => { + let mappedColor; + if (this.method === "none") { + return step; + } + if (methods[this.method].compute) { + mappedColor = methods[this.method].compute(step); + } + else { + mappedColor = step.clone().toGamut({ space: "p3", method: this.method }); + } + return mappedColor; + }); + this.time = Color.util.toPrecision(performance.now() - start, 4); + this.mappedSteps = steps; + }, + }, + + watch: { + steps: { + handler () { + this.mapSteps(); + }, + immediate: true, + }, + }, + + compilerOptions: { + isCustomElement (tag) { + return tag === "css-color"; + }, + }, + + template: ` +
+
{{ name }} {{time}}ms
+
+
+
+
+ `, +}; From b52675d2f705048544f630cdc1ebe27abaa0636f Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Tue, 5 Mar 2024 12:03:53 -0500 Subject: [PATCH 2/8] Format CSS --- apps/gamut-mapping/gradients.css | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/gamut-mapping/gradients.css b/apps/gamut-mapping/gradients.css index 3b35f048..46fe794b 100644 --- a/apps/gamut-mapping/gradients.css +++ b/apps/gamut-mapping/gradients.css @@ -10,20 +10,20 @@ body { .gradient { height: 100px; - display: flex; - > div { - flex: auto; - background: var(--step-color); - } + display: flex; + > div { + flex: auto; + background: var(--step-color); + } } - .color-inputs { + .color-inputs { display: flex; justify-content: space-between; } - .oog .gradient{ - height: 15px; - } + .oog .gradient { + height: 15px; + } .method .info { display: none; From 3867d177bafc4ed720aa34788e4e96871d11f5c1 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Tue, 5 Mar 2024 13:42:09 -0500 Subject: [PATCH 3/8] Add gamut legend --- apps/gamut-mapping/gradients.css | 14 ++++++++++++++ apps/gamut-mapping/gradients.html | 10 +++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/apps/gamut-mapping/gradients.css b/apps/gamut-mapping/gradients.css index 46fe794b..88e2a14b 100644 --- a/apps/gamut-mapping/gradients.css +++ b/apps/gamut-mapping/gradients.css @@ -33,4 +33,18 @@ body { display: block; } } + .gamut-legend { + ul{ + list-style: none; + display: flex; + gap: 3em; + padding: 0; + } + .color-block { + height: 1em; + width: 1em; + display: inline-block; + background-color: var(--step-color); + } + } } diff --git a/apps/gamut-mapping/gradients.html b/apps/gamut-mapping/gradients.html index 46f41447..d415b5bf 100644 --- a/apps/gamut-mapping/gradients.html +++ b/apps/gamut-mapping/gradients.html @@ -33,7 +33,15 @@

Gamut Mapping Gradients

-
Gamut indicator
+
Gamut indicator + Shows the smallest gamut that the color from the unmapped gradient fits in. +
    +
  • In sRGB
  • +
  • In p3
  • +
  • In rec2020
  • +
  • Out of rec2020
  • +
+
From 79c0b87d23371e02b16563a273196d16d45d1386 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Tue, 5 Mar 2024 13:46:38 -0500 Subject: [PATCH 4/8] Add method to title --- apps/gamut-mapping/mapped-gradient.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/gamut-mapping/mapped-gradient.js b/apps/gamut-mapping/mapped-gradient.js index bfe5dc63..8e51dd02 100644 --- a/apps/gamut-mapping/mapped-gradient.js +++ b/apps/gamut-mapping/mapped-gradient.js @@ -56,10 +56,10 @@ export default { }, template: ` -
+
{{ name }} {{time}}ms
-
+
`, From 2e1e9141effb2b5683918268152bfdc0fed3af04 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Tue, 5 Mar 2024 14:01:19 -0500 Subject: [PATCH 5/8] Hook up parameters --- apps/gamut-mapping/gradients.js | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/apps/gamut-mapping/gradients.js b/apps/gamut-mapping/gradients.js index 548f742e..a90c0232 100644 --- a/apps/gamut-mapping/gradients.js +++ b/apps/gamut-mapping/gradients.js @@ -8,16 +8,16 @@ globalThis.Color = Color; let app = createApp({ data () { let params = new URLSearchParams(location.search); - let urlFromColor = params.getAll("from").filter(Boolean); - let urlToColor = params.getAll("to").filter(Boolean); + const urlFromColor = params.get("from"); + const urlToColor = params.get("to"); return { methods: ["none", "clip", "scale-lh", "css", "raytrace", "edge-seeker"], - Color, - from: "oklch(90% .8 250)", - to: "oklch(40% .1 20)", + from: urlFromColor || "oklch(90% .8 250)", + to: urlToColor || "oklch(40% .1 20)", space: "oklch", maxDeltaE: 10, flush: false, + params: params, interpolationSpaces: ["oklch", "oklab", "p3", "rec2020", "lab"], }; }, @@ -57,7 +57,22 @@ let app = createApp({ }, watch: { - + from: { + handler (value) { + this.params.set("from", value); + history.pushState(null, "", "?" + this.params.toString()); + }, + deep: true, + immediate: true, + }, + to: { + handler (value) { + this.params.set("to", value); + history.pushState(null, "", "?" + this.params.toString()); + }, + deep: true, + immediate: true, + }, }, components: { From ffaaaf060170d817556ed9ee6ad182182d95b984 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Tue, 5 Mar 2024 14:45:20 -0500 Subject: [PATCH 6/8] Link to index --- apps/gamut-mapping/gradients.css | 4 ++++ apps/gamut-mapping/gradients.html | 3 +++ 2 files changed, 7 insertions(+) diff --git a/apps/gamut-mapping/gradients.css b/apps/gamut-mapping/gradients.css index 88e2a14b..f12f4178 100644 --- a/apps/gamut-mapping/gradients.css +++ b/apps/gamut-mapping/gradients.css @@ -47,4 +47,8 @@ body { background-color: var(--step-color); } } + .footer { + margin-top: 2em; + text-align: center; + } } diff --git a/apps/gamut-mapping/gradients.html b/apps/gamut-mapping/gradients.html index d415b5bf..325c82bd 100644 --- a/apps/gamut-mapping/gradients.html +++ b/apps/gamut-mapping/gradients.html @@ -51,5 +51,8 @@

Gamut Mapping Gradients

+ \ No newline at end of file From 4cfdd2f339240c1be8bb7aebdb16414894822c0f Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Wed, 6 Mar 2024 10:39:50 -0500 Subject: [PATCH 7/8] Handle invalid colors --- apps/gamut-mapping/gradients.css | 11 ++++++++++ apps/gamut-mapping/gradients.html | 4 ++-- apps/gamut-mapping/gradients.js | 36 ++++++++++++++++++++++--------- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/apps/gamut-mapping/gradients.css b/apps/gamut-mapping/gradients.css index f12f4178..32616f45 100644 --- a/apps/gamut-mapping/gradients.css +++ b/apps/gamut-mapping/gradients.css @@ -1,6 +1,17 @@ :root { --font-mono: Consolas, Inconsolata, Monaco, monospace; } +css-color input { + padding: .15em .3em .1em; + border: 1px solid hsl(220 10% 78%); + border-radius: .25em; + font-family: var(--font-mono); + + &:invalid { + background-color: hsl(0 60% 95%); + border-color: hsl(0 60% 80%); + } +} body { font: 100%/1.5 system-ui; diff --git a/apps/gamut-mapping/gradients.html b/apps/gamut-mapping/gradients.html index 325c82bd..6670069a 100644 --- a/apps/gamut-mapping/gradients.html +++ b/apps/gamut-mapping/gradients.html @@ -25,10 +25,10 @@

Gamut Mapping Gradients

- + - +
diff --git a/apps/gamut-mapping/gradients.js b/apps/gamut-mapping/gradients.js index a90c0232..325290be 100644 --- a/apps/gamut-mapping/gradients.js +++ b/apps/gamut-mapping/gradients.js @@ -10,10 +10,14 @@ let app = createApp({ let params = new URLSearchParams(location.search); const urlFromColor = params.get("from"); const urlToColor = params.get("to"); + const from = urlFromColor || "oklch(90% .8 250)"; + const to = urlToColor || "oklch(40% .1 20)"; return { methods: ["none", "clip", "scale-lh", "css", "raytrace", "edge-seeker"], - from: urlFromColor || "oklch(90% .8 250)", - to: urlToColor || "oklch(40% .1 20)", + from: from, + to: to, + parsedFrom: this.tryParse(from), + parsedTo: this.tryParse(to), space: "oklch", maxDeltaE: 10, flush: false, @@ -23,14 +27,12 @@ let app = createApp({ }, computed: { - colors () { - return [this.from, this.to]; - }, - steps () { - const from = new Color(this.colors[0]); - const to = new Color(this.colors[1]); - let steps = from.steps(to, { + if ( !this.parsedFrom || !this.parsedTo) { + return []; + } + const from = new Color(this.parsedFrom); + let steps = from.steps(this.parsedTo, { maxDeltaE: this.maxDeltaE, space: this.space, }); @@ -53,7 +55,21 @@ let app = createApp({ }, methods: { - + colorChangeFrom (event) { + this.parsedFrom = this.tryParse(event.detail.color) || this.parsedFrom; + }, + colorChangeTo (event) { + this.parsedTo = this.tryParse(event.detail.color) || this.parsedFrom; + }, + tryParse (input) { + try { + const color = new Color.parse(input); + return color; + } + catch (error) { + // do nothing + } + }, }, watch: { From 2de8b34ddceb630bb9286cceb34ff7d3514098e6 Mon Sep 17 00:00:00 2001 From: James Stuckey Weber Date: Wed, 6 Mar 2024 11:33:49 -0500 Subject: [PATCH 8/8] Add timing info --- apps/gamut-mapping/gradients.css | 18 ++++++++++ apps/gamut-mapping/gradients.html | 3 +- apps/gamut-mapping/gradients.js | 13 ++++++-- apps/gamut-mapping/mapped-gradient.js | 3 ++ apps/gamut-mapping/timing-info.js | 47 +++++++++++++++++++++++++++ 5 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 apps/gamut-mapping/timing-info.js diff --git a/apps/gamut-mapping/gradients.css b/apps/gamut-mapping/gradients.css index 32616f45..8dab6c0c 100644 --- a/apps/gamut-mapping/gradients.css +++ b/apps/gamut-mapping/gradients.css @@ -63,3 +63,21 @@ body { text-align: center; } } +details.timing-info { + margin-top: 2em; + > dl { + display: flex; + } + .timing-result{ + padding: 1em; + dd { + margin: 0; + } + } + .metric > div { + display: flex; + justify-content: space-between; + gap: 1em; + } + +} \ No newline at end of file diff --git a/apps/gamut-mapping/gradients.html b/apps/gamut-mapping/gradients.html index 6670069a..62bfd836 100644 --- a/apps/gamut-mapping/gradients.html +++ b/apps/gamut-mapping/gradients.html @@ -48,9 +48,10 @@

Gamut Mapping Gradients

- +
+ diff --git a/apps/gamut-mapping/gradients.js b/apps/gamut-mapping/gradients.js index 325290be..bbd9011d 100644 --- a/apps/gamut-mapping/gradients.js +++ b/apps/gamut-mapping/gradients.js @@ -1,7 +1,7 @@ import { createApp } from "https://unpkg.com/vue@3.2.37/dist/vue.esm-browser.js"; import Color from "../../dist/color.js"; -import methods from "./methods.js"; import Gradient from "./mapped-gradient.js"; +import TimingInfo from "./timing-info.js"; globalThis.Color = Color; @@ -12,8 +12,11 @@ let app = createApp({ const urlToColor = params.get("to"); const from = urlFromColor || "oklch(90% .8 250)"; const to = urlToColor || "oklch(40% .1 20)"; + const methods = ["none", "clip", "scale-lh", "css", "raytrace", "edge-seeker"]; + const runResults = {}; + methods.forEach(method => runResults[method] = []); return { - methods: ["none", "clip", "scale-lh", "css", "raytrace", "edge-seeker"], + methods: methods, from: from, to: to, parsedFrom: this.tryParse(from), @@ -23,6 +26,7 @@ let app = createApp({ flush: false, params: params, interpolationSpaces: ["oklch", "oklab", "p3", "rec2020", "lab"], + runResults: runResults, }; }, @@ -70,6 +74,10 @@ let app = createApp({ // do nothing } }, + reportTime ({time, method}) { + this.runResults[method].push(time); + this.runResults = {...this.runResults}; + }, }, watch: { @@ -93,6 +101,7 @@ let app = createApp({ components: { "mapped-gradient": Gradient, + "timing-info": TimingInfo, }, compilerOptions: { isCustomElement (tag) { diff --git a/apps/gamut-mapping/mapped-gradient.js b/apps/gamut-mapping/mapped-gradient.js index 8e51dd02..d3ece3fc 100644 --- a/apps/gamut-mapping/mapped-gradient.js +++ b/apps/gamut-mapping/mapped-gradient.js @@ -6,6 +6,8 @@ export default { steps: Array, }, + emits: ["report-time"], + data () { return { time: 0, @@ -36,6 +38,7 @@ export default { return mappedColor; }); this.time = Color.util.toPrecision(performance.now() - start, 4); + this.$emit("report-time", {time: this.time, method: this.method}); this.mappedSteps = steps; }, }, diff --git a/apps/gamut-mapping/timing-info.js b/apps/gamut-mapping/timing-info.js new file mode 100644 index 00000000..c1c19a16 --- /dev/null +++ b/apps/gamut-mapping/timing-info.js @@ -0,0 +1,47 @@ +import methods from "./methods.js"; +import Color from "../../dist/color.js"; + +export default { + props: { + runResults: Object, + }, + + computed: { + results () { + const [none, ...methodsTested] = Object.keys(this.runResults); + return methodsTested.map(method => { + const data = this.runResults[method]; + const total = data.reduce((acc, value) => acc + value); + + return { + id: method, + name: methods[method].label, + metrics: { + runs: data.length, + min: Color.util.toPrecision(Math.min(...data), 8), + max: Color.util.toPrecision(Math.max(...data), 8), + mean: Color.util.toPrecision(total / data.length, 8), + }, + }; + }).sort((a, b) => a.metrics.mean - b.metrics.mean); + }, + }, + template: ` +
Timing info +
+
+
{{ method.name }}
+
+
+
+
{{ metric.toUpperCase() }}
+
{{ value }}
+
+
+
+
+
+
+ `, +}; +