Created
April 30, 2022 19:23
-
-
Save vipertechofficial/c5353b4df4910aebffed4f0a07fd725e to your computer and use it in GitHub Desktop.
Fastest RGBA, HSLA, and HEX color conversion, formatting and blending (mix) in JavaScript developed for the pixa.pics project.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
_format_color = (color) => { // Supports #fff (short rgb), #fff0 (short rgba), #e2e2e2 (full rgb) and #e2e2e2ff (full rgba) | |
const hex = color || "#00000000"; | |
const hex_length = hex.length; | |
if(hex_length === 9) { | |
return hex; | |
} else if (hex_length === 7) { | |
return hex.concat("ff"); | |
} else if (hex_length === 5) { | |
const a = hex.charAt(1), b = hex.charAt(2), c = hex.charAt(3), d = hex.charAt(4); | |
return "#".concat(a, a, b, b, c, c, d, d); | |
} else if (hex_length === 4) { | |
const a = hex.charAt(1), b = hex.charAt(2), c = hex.charAt(3); | |
return "#".concat(a, a, b, b, c, c, "ff"); | |
} | |
}; | |
_get_rgba_from_hex = (hex) => { | |
return new Uint8ClampedArray(Uint32Array.of(parseInt(hex.slice(1), 16)).buffer).reverse(); | |
}; | |
_get_hex_color_from_rgba_values = (r, g, b, a) => { | |
return "#".concat("00000000".concat(new Uint32Array(Uint8ClampedArray.of(a, b, g, r).buffer)[0].toString(16)).slice(-8)); | |
}; | |
_rgb_to_hsl = (r, g, b) => { | |
r /= 255, g /= 255, b /= 255; | |
const max = Math.max(r, g, b), min = Math.min(r, g, b); | |
let h, s, l = (max + min) / 2; | |
if(max === min){ | |
h = s = 0; // achromatic | |
}else { | |
const d = max - min; | |
s = l > 0.5 ? d / (2 - max - min) : d / (max + min); | |
switch(max){ | |
case r: h = (g - b) / d + (g < b ? 6 : 0); break; | |
case g: h = (b - r) / d + 2; break; | |
case b: h = (r - g) / d + 4; break; | |
} | |
h /= 6; | |
} | |
return Array.of(parseInt(h * 360), parseInt(s * 100), parseInt(l * 100)); | |
} | |
_hsl_to_rgb = (h, s, l) => { | |
h /= 360, s /= 100, l /= 100; | |
let r, g, b; | |
if (s === 0) { | |
r = g = b = l; | |
} else { | |
const hue_to_rgb = function(p, q, t) { | |
if (t < 0) t += 1; | |
if (t > 1) t -= 1; | |
if (t < 1 / 6) return p + (q - p) * 6 * t; | |
if (t < 1 / 2) return q; | |
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; | |
return p; | |
}; | |
const q = l < 0.5 ? l * (1 + s) : l + s - l * s; | |
const p = 2 * l - q; | |
r = hue_to_rgb(p, q, h + 1 / 3); | |
g = hue_to_rgb(p, q, h); | |
b = hue_to_rgb(p, q, h - 1 / 3); | |
} | |
return Uint8ClampedArray.of(r * 255, g * 255, b * 255); | |
}; | |
_blend_colors = (color_a, color_b, amount = 1, should_return_transparent = false, alpha_addition = false) => { | |
color_a = this._format_color(color_a); | |
// If we blend the first color with the second with 0 "force", return transparent | |
if(amount === 0 && color_b !== "hover" && should_return_transparent) { | |
return "#00000000"; | |
} | |
// Make sure we have a color based on the 4*2 hex char format | |
if(color_b === "hover") { | |
const rgba = this._get_rgba_from_hex(color_a); | |
const hsl = this._rgb_to_hsl(rgba[0], rgba[1], rgba[2], rgba[3]); | |
const rgb = this._hsl_to_rgb(hsl[0], hsl[1], parseInt(hsl[2] >= 50 ? hsl[2]/2: hsl[2]*2)); | |
color_b = this._get_hex_color_from_rgba_values(rgb[0], rgb[1], rgb[2], 255); | |
}else { | |
color_b = this._format_color(color_b); | |
} | |
// If the second color is transparent, return transparent | |
if(should_return_transparent && color_b === "#00000000" && amount === 1) { return "#00000000"; } | |
// Extract RGBA from both colors | |
const base = this._get_rgba_from_hex(color_a); | |
const added = this._get_rgba_from_hex(color_b); | |
if(added[3] === 255 && amount === 1) { return color_b; } | |
const ba3 = base[3] / 255; | |
const ad3 = (added[3] / 255) * amount; | |
let mix = new Uint8ClampedArray(4); | |
let mi3 = 0; | |
if (ba3 > 0 && ad3 > 0) { | |
if(alpha_addition) { | |
mi3 = ad3 + ba3; | |
}else { | |
mi3 = 1 - ((1 - ad3) * (1 - ba3)); | |
} | |
const ao = ad3 / mi3; | |
const bo = ba3 * (1 - ad3) / mi3; | |
mix[0] = parseInt(added[0] * ao + base[0] * bo); // red | |
mix[1] = parseInt(added[1] * ao + base[1] * bo); // green | |
mix[2] = parseInt(added[2] * ao + base[2] * bo); // blue | |
}else if(ad3 > 0) { | |
mi3 = added[3] / 255; | |
mix[0] = added[0]; | |
mix[1] = added[1]; | |
mix[2] = added[2]; | |
}else { | |
mi3 = base[3] / 255; | |
mix[0] = base[0]; | |
mix[1] = base[1]; | |
mix[2] = base[2]; | |
} | |
if(alpha_addition) { | |
mi3 /= 2; | |
} | |
mix[3] = parseInt(mi3 * 255); | |
return this._get_hex_color_from_rgba_values(mix[0], mix[1], mix[2], mix[3]); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment