Skip to content

Commit 794cd26

Browse files
committed
Update transformColor to match GPU
1 parent a67cd48 commit 794cd26

File tree

1 file changed

+104
-103
lines changed

1 file changed

+104
-103
lines changed

src/EffectTransform.js

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

23-
// color conversions grabbed from https://gist.github.com/mjackson/5311256
24-
2523
/**
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].
24+
* Converts an RGB color value to HSV. Conversion formula
25+
* adapted from http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv.
26+
* Assumes r, g, and b are in the range [0, 255] and
27+
* returns h, s, and v in the range [0, 1].
3028
*
3129
* @param {number} r The red color value
3230
* @param {number} g The green color value
3331
* @param {number} b The blue color value
34-
* @return {Array} The HSL representation
32+
* @return {Array<number>} The HSV representation
3533
*/
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-
}
34+
const rgbToHsv = ([_r, _g, _b]) => {
35+
let K = 0.0;
36+
37+
let r = _r / 255;
38+
let g = _g / 255;
39+
let b = _b / 255;
40+
let tmp = 0;
5841

59-
h /= 6;
42+
if (g < b) {
43+
tmp = g;
44+
g = b;
45+
b = tmp;
46+
47+
K = -1;
6048
}
6149

62-
return [h, s, l];
63-
};
50+
if (r < g) {
51+
tmp = r;
52+
r = g;
53+
g = tmp;
6454

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-
};
55+
K = (-2 / 6) - K;
56+
}
8157

58+
const chroma = r - Math.min(g, b);
59+
const h = Math.abs(K + ((g - b) / ((6 * chroma) + Number.EPSILON)));
60+
const s = chroma / (r + Number.EPSILON);
61+
const v = r;
8262

63+
return [h, s, v];
64+
};
65+
66+
/* eslint-disable valid-jsdoc */
67+
/* it doesn't seem to work with destructured parameters */
8368
/**
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
69+
* Converts an HSV color value to RGB. Conversion formula
70+
* adapted from https://gist.github.com/mjackson/5311256.
71+
* Assumes h, s, and v are contained in the set [0, 1] and
8772
* returns r, g, and b in the set [0, 255].
8873
*
89-
* @param {number} h The hue
90-
* @param {number} s The saturation
91-
* @param {number} l The lightness
92-
* @return {Array} The RGB representation
74+
* @param {number} h The hue
75+
* @param {number} s The saturation
76+
* @param {number} v The value
77+
* @param {Array<number>} dst The array to store the RGB values in
78+
* @return {Array<number>} The `dst` array passed in
9379
*/
94-
const hslToRgb = ([h, s, l]) => {
95-
let r;
96-
let g;
97-
let b;
98-
80+
const hsvToRgb = ([h, s, v], dst) => {
9981
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;
82+
dst[0] = v;
83+
dst[1] = v;
84+
dst[2] = v;
85+
return dst;
86+
}
10587

106-
r = hue2rgb(p, q, h + (1 / 3));
107-
g = hue2rgb(p, q, h);
108-
b = hue2rgb(p, q, h - (1 / 3));
88+
const i = (h * 6) | 0;
89+
const f = (h * 6) - i;
90+
const p = v * (1 - s);
91+
const q = v * (1 - (s * f));
92+
const t = v * (1 - (s * (1 - f)));
93+
94+
let r = 0;
95+
let g = 0;
96+
let b = 0;
97+
98+
switch (i) {
99+
case 0: r = v; g = t; b = p; break;
100+
case 1: r = q; g = v; b = p; break;
101+
case 2: r = p; g = v; b = t; break;
102+
case 3: r = p; g = q; b = v; break;
103+
case 4: r = t; g = p; b = v; break;
104+
case 5: r = v; g = p; b = q; break;
109105
}
110106

111-
return [r * 255, g * 255, b * 255];
107+
// Add 0.5 in order to round. Setting integer TypedArray elements implicitly floors.
108+
dst[0] = (r * 255) + 0.5;
109+
dst[1] = (g * 255) + 0.5;
110+
dst[2] = (b * 255) + 0.5;
111+
return dst;
112112
};
113+
/* eslint-enable valid-jsdoc */
113114

114115
class EffectTransform {
115116

@@ -135,42 +136,42 @@ class EffectTransform {
135136
inOutColor[3] *= uniforms.u_ghost;
136137
}
137138

138-
const enableColor = (effects & ShaderManager.EFFECT_INFO.color.mask) !== 0;
139-
const enableBrightness = (effects & ShaderManager.EFFECT_INFO.brightness.mask) !== 0;
140-
141-
if (enableColor || enableBrightness) {
142-
// vec3 hsl = convertRGB2HSL(gl_FragColor.xyz);
143-
const hsl = rgbToHsl(inOutColor);
144-
145-
if (enableColor) {
146-
// this code forces grayscale values to be slightly saturated
147-
// so that some slight change of hue will be visible
148-
// const float minLightness = 0.11 / 2.0;
149-
const minL = 0.11 / 2.0;
150-
// const float minSaturation = 0.09;
151-
const minS = 0.09;
152-
// if (hsl.z < minLightness) hsl = vec3(0.0, 1.0, minLightness);
153-
if (hsl[2] < minL) {
154-
hsl[0] = 0;
155-
hsl[1] = 1;
156-
hsl[2] = minL;
157-
// else if (hsl.y < minSaturation) hsl = vec3(0.0, minSaturation, hsl.z);
158-
} else if (hsl[1] < minS) {
159-
hsl[0] = 0;
160-
hsl[1] = minS;
161-
}
162-
163-
// hsl.x = mod(hsl.x + u_color, 1.0);
164-
// if (hsl.x < 0.0) hsl.x += 1.0;
165-
hsl[0] = (uniforms.u_color + hsl[0] + 1) % 1;
139+
if ((effects & ShaderManager.EFFECT_INFO.color.mask) !== 0) {
140+
// vec3 hsv = convertRGB2HSV(gl_FragColor.xyz);
141+
const hsv = rgbToHsv(inOutColor);
142+
143+
// this code forces grayscale values to be slightly saturated
144+
// so that some slight change of hue will be visible
145+
// const float minLightness = 0.11 / 2.0;
146+
const minV = 0.11 / 2.0;
147+
// const float minSaturation = 0.09;
148+
const minS = 0.09;
149+
// if (hsv.z < minLightness) hsv = vec3(0.0, 1.0, minLightness);
150+
if (hsv[2] < minV) {
151+
hsv[0] = 0;
152+
hsv[1] = 1;
153+
hsv[2] = minV;
154+
// else if (hsv.y < minSaturation) hsv = vec3(0.0, minSaturation, hsv.z);
155+
} else if (hsv[1] < minS) {
156+
hsv[0] = 0;
157+
hsv[1] = minS;
166158
}
167159

168-
if (enableBrightness) {
169-
// hsl.z = clamp(hsl.z + u_brightness, 0.0, 1.0);
170-
hsl[2] = Math.min(1, hsl[2] + uniforms.u_brightness);
171-
}
172-
// gl_FragColor.rgb = convertHSL2RGB(hsl);
173-
inOutColor.set(hslToRgb(hsl));
160+
// hsv.x = mod(hsv.x + u_color, 1.0);
161+
// if (hsv.x < 0.0) hsv.x += 1.0;
162+
hsv[0] = (uniforms.u_color + hsv[0] + 1) % 1;
163+
164+
// gl_FragColor.rgb = convertHSV2RGB(hsl);
165+
hsvToRgb(hsv, inOutColor);
166+
}
167+
168+
if ((effects & ShaderManager.EFFECT_INFO.brightness.mask) !== 0) {
169+
const brightness = uniforms.u_brightness;
170+
// gl_FragColor.rgb = clamp(gl_FragColor.rgb + vec3(u_brightness), vec3(0), vec3(1));
171+
// We don't need to clamp because the Uint8ClampedArray does that for us
172+
inOutColor[0] = inOutColor[0] + brightness;
173+
inOutColor[1] = inOutColor[1] + brightness;
174+
inOutColor[2] = inOutColor[2] + brightness;
174175
}
175176

176177
return inOutColor;

0 commit comments

Comments
 (0)