Skip to content

Commit 21ee6ba

Browse files
committed
Update transformColor to match GPU color+brightness
1 parent d37c847 commit 21ee6ba

File tree

1 file changed

+105
-91
lines changed

1 file changed

+105
-91
lines changed

src/EffectTransform.js

Lines changed: 105 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -20,95 +20,104 @@ const CENTER_X = 0.5;
2020
*/
2121
const CENTER_Y = 0.5;
2222

23-
// color conversions grabbed from https://gist.github.com/mjackson/5311256
23+
/**
24+
* Reused memory location for storing an HSV color value.
25+
* @type {Array<number>}
26+
*/
27+
const __hsv = [0, 0, 0];
2428

2529
/**
26-
* Converts an RGB color value to HSL. Conversion formula
27-
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
28-
* Assumes r, g, and b are contained in the set [0, 255] and
29-
* returns h, s, and l in the set [0, 1].
30+
* Converts an RGB color value to HSV. Conversion formula
31+
* adapted from http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv.
32+
* Assumes r, g, and b are in the range [0, 255] and
33+
* returns h, s, and v in the range [0, 1].
3034
*
31-
* @param {number} r The red color value
32-
* @param {number} g The green color value
33-
* @param {number} b The blue color value
34-
* @return {Array} The HSL representation
35+
* @param {Array<number>} rgb The RGB color value
36+
* @param {number} rgb.r The red color value
37+
* @param {number} rgb.g The green color value
38+
* @param {number} rgb.b The blue color value
39+
* @param {Array<number>} dst The array to store the RGB values in
40+
* @return {Array<number>} The `dst` array passed in
3541
*/
36-
const rgbToHsl = ([r, g, b]) => {
37-
r /= 255;
38-
g /= 255;
39-
b /= 255;
40-
41-
const max = Math.max(r, g, b);
42-
const min = Math.min(r, g, b);
43-
let h;
44-
let s;
45-
const l = (max + min) / 2;
46-
47-
if (max === min) {
48-
h = s = 0; // achromatic
49-
} else {
50-
const d = max - min;
51-
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
52-
53-
switch (max) {
54-
case r: h = ((g - b) / d) + (g < b ? 6 : 0); break;
55-
case g: h = ((b - r) / d) + 2; break;
56-
case b: h = ((r - g) / d) + 4; break;
57-
}
42+
const rgbToHsv = ([_r, _g, _b], dst) => {
43+
let K = 0.0;
44+
45+
let r = _r / 255;
46+
let g = _g / 255;
47+
let b = _b / 255;
48+
let tmp = 0;
49+
50+
if (g < b) {
51+
tmp = g;
52+
g = b;
53+
b = tmp;
5854

59-
h /= 6;
55+
K = -1;
6056
}
6157

62-
return [h, s, l];
63-
};
58+
if (r < g) {
59+
tmp = r;
60+
r = g;
61+
g = tmp;
6462

65-
/**
66-
* Helper function for hslToRgb is called with varying 't' values to get
67-
* red green and blue values from the p/q/t color space calculations
68-
* @param {number} p vector coordinates
69-
* @param {number} q vector coordinates
70-
* @param {number} t vector coordinates
71-
* @return {number} amount of r/g/b byte
72-
*/
73-
const hue2rgb = (p, q, t) => {
74-
if (t < 0) t += 1;
75-
if (t > 1) t -= 1;
76-
if (t < 1 / 6) return p + ((q - p) * 6 * t);
77-
if (t < 1 / 2) return q;
78-
if (t < 2 / 3) return p + ((q - p) * ((2 / 3) - t) * 6);
79-
return p;
80-
};
63+
K = (-2 / 6) - K;
64+
}
8165

66+
const chroma = r - Math.min(g, b);
67+
const h = Math.abs(K + ((g - b) / ((6 * chroma) + Number.EPSILON)));
68+
const s = chroma / (r + Number.EPSILON);
69+
const v = r;
70+
71+
dst[0] = h;
72+
dst[1] = s;
73+
dst[2] = v;
74+
75+
return dst;
76+
};
8277

8378
/**
84-
* Converts an HSL color value to RGB. Conversion formula
85-
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
86-
* Assumes h, s, and l are contained in the set [0, 1] and
79+
* Converts an HSV color value to RGB. Conversion formula
80+
* adapted from https://gist.github.com/mjackson/5311256.
81+
* Assumes h, s, and v are contained in the set [0, 1] and
8782
* returns r, g, and b in the set [0, 255].
8883
*
89-
* @param {number} h The hue
90-
* @param {number} s The saturation
91-
* @param {number} l The lightness
92-
* @return {Array} The RGB representation
84+
* @param {Array<number>} hsv The HSV color value
85+
* @param {number} hsv.h The hue
86+
* @param {number} hsv.s The saturation
87+
* @param {number} hsv.v The value
88+
* @param {Uint8Array|Uint8ClampedArray} dst The array to store the RGB values in
89+
* @return {Uint8Array|Uint8ClampedArray} The `dst` array passed in
9390
*/
94-
const hslToRgb = ([h, s, l]) => {
95-
let r;
96-
let g;
97-
let b;
98-
91+
const hsvToRgb = ([h, s, v], dst) => {
9992
if (s === 0) {
100-
r = g = b = l; // achromatic
101-
} else {
102-
103-
const q = l < 0.5 ? l * (1 + s) : l + s - (l * s);
104-
const p = (2 * l) - q;
93+
dst[0] = dst[1] = dst[2] = (v * 255) + 0.5;
94+
return dst;
95+
}
10596

106-
r = hue2rgb(p, q, h + (1 / 3));
107-
g = hue2rgb(p, q, h);
108-
b = hue2rgb(p, q, h - (1 / 3));
97+
const i = (h * 6) | 0;
98+
const f = (h * 6) - i;
99+
const p = v * (1 - s);
100+
const q = v * (1 - (s * f));
101+
const t = v * (1 - (s * (1 - f)));
102+
103+
let r = 0;
104+
let g = 0;
105+
let b = 0;
106+
107+
switch (i) {
108+
case 0: r = v; g = t; b = p; break;
109+
case 1: r = q; g = v; b = p; break;
110+
case 2: r = p; g = v; b = t; break;
111+
case 3: r = p; g = q; b = v; break;
112+
case 4: r = t; g = p; b = v; break;
113+
case 5: r = v; g = p; b = q; break;
109114
}
110115

111-
return [r * 255, g * 255, b * 255];
116+
// Add 0.5 in order to round. Setting integer TypedArray elements implicitly floors.
117+
dst[0] = (r * 255) + 0.5;
118+
dst[1] = (g * 255) + 0.5;
119+
dst[2] = (b * 255) + 0.5;
120+
return dst;
112121
};
113122

114123
class EffectTransform {
@@ -145,38 +154,43 @@ class EffectTransform {
145154
inOutColor[1] /= alpha;
146155
inOutColor[2] /= alpha;
147156

148-
// vec3 hsl = convertRGB2HSL(gl_FragColor.xyz);
149-
const hsl = rgbToHsl(inOutColor);
150-
151157
if (enableColor) {
158+
// vec3 hsv = convertRGB2HSV(gl_FragColor.xyz);
159+
const hsv = rgbToHsv(inOutColor, __hsv);
160+
152161
// this code forces grayscale values to be slightly saturated
153162
// so that some slight change of hue will be visible
154163
// const float minLightness = 0.11 / 2.0;
155-
const minL = 0.11 / 2.0;
164+
const minV = 0.11 / 2.0;
156165
// const float minSaturation = 0.09;
157166
const minS = 0.09;
158-
// if (hsl.z < minLightness) hsl = vec3(0.0, 1.0, minLightness);
159-
if (hsl[2] < minL) {
160-
hsl[0] = 0;
161-
hsl[1] = 1;
162-
hsl[2] = minL;
163-
// else if (hsl.y < minSaturation) hsl = vec3(0.0, minSaturation, hsl.z);
164-
} else if (hsl[1] < minS) {
165-
hsl[0] = 0;
166-
hsl[1] = minS;
167+
// if (hsv.z < minLightness) hsv = vec3(0.0, 1.0, minLightness);
168+
if (hsv[2] < minV) {
169+
hsv[0] = 0;
170+
hsv[1] = 1;
171+
hsv[2] = minV;
172+
// else if (hsv.y < minSaturation) hsv = vec3(0.0, minSaturation, hsv.z);
173+
} else if (hsv[1] < minS) {
174+
hsv[0] = 0;
175+
hsv[1] = minS;
167176
}
168177

169-
// hsl.x = mod(hsl.x + u_color, 1.0);
170-
// if (hsl.x < 0.0) hsl.x += 1.0;
171-
hsl[0] = (uniforms.u_color + hsl[0] + 1) % 1;
178+
// hsv.x = mod(hsv.x + u_color, 1.0);
179+
// if (hsv.x < 0.0) hsv.x += 1.0;
180+
hsv[0] = (uniforms.u_color + hsv[0] + 1) % 1;
181+
182+
// gl_FragColor.rgb = convertHSV2RGB(hsl);
183+
hsvToRgb(hsv, inOutColor);
172184
}
173185

174186
if (enableBrightness) {
175-
// hsl.z = clamp(hsl.z + u_brightness, 0.0, 1.0);
176-
hsl[2] = Math.min(1, hsl[2] + uniforms.u_brightness);
187+
const brightness = uniforms.u_brightness * 255;
188+
// gl_FragColor.rgb = clamp(gl_FragColor.rgb + vec3(u_brightness), vec3(0), vec3(1));
189+
// We don't need to clamp because the Uint8ClampedArray does that for us
190+
inOutColor[0] += brightness;
191+
inOutColor[1] += brightness;
192+
inOutColor[2] += brightness;
177193
}
178-
// gl_FragColor.rgb = convertHSL2RGB(hsl);
179-
inOutColor.set(hslToRgb(hsl));
180194

181195
// gl_FragColor.rgb *= gl_FragColor.a + epsilon;
182196
// Now we're doing the reverse, premultiplying by the alpha once again.

0 commit comments

Comments
 (0)