From ab8eaf572bc5dc9c19ed66f2f27729a49f428878 Mon Sep 17 00:00:00 2001 From: LincolnX <118082287+LincolnXGames@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:31:07 -0600 Subject: [PATCH 01/12] Create colors.js --- extensions/LincolnX/colors.js | 999 ++++++++++++++++++++++++++++++++++ 1 file changed, 999 insertions(+) create mode 100644 extensions/LincolnX/colors.js diff --git a/extensions/LincolnX/colors.js b/extensions/LincolnX/colors.js new file mode 100644 index 0000000000..52715f5379 --- /dev/null +++ b/extensions/LincolnX/colors.js @@ -0,0 +1,999 @@ +// Name: Colors +// ID: lxColors +// Description: Adds a variety of new blocks that manipulates and utilizes colors. +// By: LincolnX +// License: MPL-2.0 + +const { abs, round, floor, sqrt } = Math; + +const toDec = (hex) => parseInt(hex, 16); +const toHex = (dec) => dec.toString(16); +const limitHex = (hex, mi, ma) => toHex(Math.min(Math.max(toDec(hex), mi), ma)); +const clamp = (n, mi, ma) => Math.min(Math.max(n, mi), ma); +const fixHex = (hex) => limitHex(hex, 0, 255).padStart(2, '0'); +const toFixHex = (dec) => fixHex(dec.toString(16)); +const lerp = (a, b, t) => a + (b - a) * t; +function interpolateHexColorsHsv(color1, color2, factor) { + const hsv1 = hexToHsv(color1); + const hsv2 = hexToHsv(color2); + factor = Math.min(1, Math.max(0, factor)); + // Handle hue interpolation for the shortest path around the color wheel + let h1 = hsv1.h; + let h2 = hsv2.h; + let hueDiff = h2 - h1; + if (hueDiff > 180) { + h1 += 360; + } else if (hueDiff < -180) { + h2 += 360; + } + + const h = h1 + factor * (h2 - h1); + const s = hsv1.s + factor * (hsv2.s - hsv1.s); + const v = hsv1.v + factor * (hsv2.v - hsv1.v); + + const interpolatedRgb = hsvToRgb({ h, s, v }); + return rgbToHex(interpolatedRgb); +} +function overlayHex(hex1, hex2) { + let a = toDec(hex1) / 255; + let b = toDec(hex2) / 255; + let overlay; + if (a < 0.5) { + overlay = 2 * a * b; + } else { + overlay = 1 - (2 * (1 - a) * (1 - b)); + } + return toFixHex(overlay * 255) +} + +function hexToRgb(hex) { + let r = 0, g = 0, b = 0; + // Handle 3-digit shorthand + if (hex.length === 4) { + r = parseInt(hex[1] + hex[1], 16); + g = parseInt(hex[2] + hex[2], 16); + b = parseInt(hex[3] + hex[3], 16); + } else if (hex.length === 7) { + r = parseInt(hex.substring(1, 3), 16); + g = parseInt(hex.substring(3, 5), 16); + b = parseInt(hex.substring(5, 7), 16); + } + return { r, g, b }; +} +function rgbToHex(rgb) { + const makeHex = c => Math.round(c).toString(16).padStart(2, '0'); + return `#${makeHex(rgb.r)}${makeHex(rgb.g)}${makeHex(rgb.b)}`; +} +function hsvToRgb(h, s, v) { + var r, g, b, i, f, p, q, t; + if (arguments.length === 1) { + s = h.s, v = h.v, h = h.h; + } + h /= 360; + s /= 100; + v /= 100; + i = floor(h * 6); + f = h * 6 - i; + p = v * (1 - s); + q = v * (1 - f * s); + t = v * (1 - (1 - f) * s); + switch (i % 6) { + case 0: r = v, g = t, b = p; break; + case 1: r = q, g = v, b = p; break; + case 2: r = p, g = v, b = t; break; + case 3: r = p, g = q, b = v; break; + case 4: r = t, g = p, b = v; break; + case 5: r = v, g = p, b = q; break; + } + return { + r: round(r * 255), + g: round(g * 255), + b: round(b * 255) + }; +} +function rgbToHsv(r, g, b) { + if (arguments.length === 1) { + g = r.g, b = r.b, r = r.r; + } + var max = Math.max(r, g, b), min = Math.min(r, g, b), + d = max - min, + h, + s = (max === 0 ? 0 : d / max), + v = max / 255; + switch (max) { + case min: h = 0; break; + case r: h = (g - b) + d * (g < b ? 6: 0); h /= 6 * d; break; + case g: h = (b - r) + d * 2; h /= 6 * d; break; + case b: h = (r - g) + d * 4; h /= 6 * d; break; + } + return { + h: h * 360, + s: s * 100, + v: v * 100 + }; +} +const hexToHsv = (hex) => rgbToHsv(hexToRgb(hex)); +function hsvToHex(h, s, v) { + if (arguments.length === 1) { + s = h.s, v = h.v, h = h.h; + } + return rgbToHex(hsvToRgb(h, s, v)); +} + +function hslToRgb(h, s, l) { + h /= 360; + s /= 100; + l /= 100; + let r, g, b; + if (s === 0) { + r = g = b = l; // achromatic + } else { + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; + const p = 2 * l - q; + r = hueToRgb(p, q, h + 1/3); + g = hueToRgb(p, q, h); + b = hueToRgb(p, q, h - 1/3); + } + return {r: round(r * 255), g: round(g * 255), b: round(b * 255)}; +} + +function hueToRgb(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; +} + +function channelToLinear(c) { + c /= 255; + return c <= 0.03928 + ? c / 12.92 + : ((c + 0.055) / 1.055) ** 2.4; +} + +function relativeLuminance(hex) { + const { r, g, b } = hexToRgb(hex); + const R = channelToLinear(r); + const G = channelToLinear(g); + const B = channelToLinear(b); + return 0.2126 * R + 0.7152 * G + 0.0722 * B; +} + +function contrastRatio(hex1, hex2) { + const L1 = relativeLuminance(hex1); + const L2 = relativeLuminance(hex2); + const light = Math.max(L1, L2); + const dark = Math.min(L1, L2); + return (light + 0.05) / (dark + 0.05); +} + + + +function distanceBetweenHex(hex1, hex2) { + // convert Hex to RGB + const rgb1 = hexToRgb(hex1); + const rgb2 = hexToRgb(hex2); + + // convert RGB to XYZ + const xyz1 = rgbToXyz(rgb1.r, rgb1.g, rgb1.b); + const xyz2 = rgbToXyz(rgb2.r, rgb2.g, rgb2.b); + + // convert XYZ to Lab + const lab1 = xyzToLab(xyz1.x, xyz1.y, xyz1.z); + const lab2 = xyzToLab(xyz2.x, xyz2.y, xyz2.z); + + // calculate Delta E 2000 + return deltaE2000(lab1, lab2); +} + +function rgbToXyz(r, g, b) { + r /= 255; + g /= 255; + b /= 255; + + r = (r > 0.04045) ? ((r + 0.055) / 1.055) ** 2.4 : r / 12.92; + g = (g > 0.04045) ? ((g + 0.055) / 1.055) ** 2.4 : g / 12.92; + b = (b > 0.04045) ? ((b + 0.055) / 1.055) ** 2.4 : b / 12.92; + + r *= 100; + g *= 100; + b *= 100; + + // D65 white point reference primaries + const x = r * 0.4124 + g * 0.3576 + b * 0.1805; + const y = r * 0.2126 + g * 0.7152 + b * 0.0722; + const z = r * 0.0193 + g * 0.1192 + b * 0.9505; + + return { x, y, z }; +} + +// converts XYZ values to CIELAB color space. +function xyzToLab(x, y, z) { + // D65 white point values + const refX = 95.047; + const refY = 100.000; + const refZ = 108.883; + + x /= refX; + y /= refY; + z /= refZ; + + x = (x > 0.008856) ? x ** (1/3) : (7.787 * x) + 16/116; + y = (y > 0.008856) ? y ** (1/3) : (7.787 * y) + 16/116; + z = (z > 0.008856) ? z ** (1/3) : (7.787 * z) + 16/116; + + const L = (116 * y) - 16; + const a = 500 * (x - y); + const b = 200 * (y - z); + + return { L, a, b }; +} + + +// calculates the Delta E 2000 difference between two Lab colors. +// based on the implementation notes from Sharma et al.. +function deltaE2000(lab1, lab2) { + const kL = 1.0, kC = 1.0, kH = 1.0; // parametric factors often set to 1.0 + const deg2rad = Math.PI / 180; + const rad2deg = 180 / Math.PI; + + // extract Lab values + const L1 = lab1.L, a1 = lab1.a, b1 = lab1.b; + const L2 = lab2.L, a2 = lab2.a, b2 = lab2.b; + + // calculate C*ab values (Chroma) + const C1 = Math.sqrt(a1*a1 + b1*b1); + const C2 = Math.sqrt(a2*a2 + b2*b2); + const CBar = (C1 + C2) / 2.0; + + // calculate G (chroma correction factor) + const CBarPow7 = CBar ** 7; + const G = 0.5 * (1 - Math.sqrt(CBarPow7 / (CBarPow7 + 6103515625.0))); // 6103515625 = 25^7 + + // calculate a' and C' (lightness corrected a* and new chroma) + const a1Prime = a1 * (1 + G); + const a2Prime = a2 * (1 + G); + const C1Prime = Math.sqrt(a1Prime*a1Prime + b1*b1); + const C2Prime = Math.sqrt(a2Prime*a2Prime + b2*b2); + const CBarPrime = (C1Prime + C2Prime) / 2.0; + const DeltaCPrime = C2Prime - C1Prime; + + // calculate h' (hue angle) + const h1Prime = (Math.atan2(b1, a1Prime) * rad2deg); + const h2Prime = (Math.atan2(b2, a2Prime) * rad2deg); + + // normalize hue angles to 0-360 range + const normalizedH1 = h1Prime >= 0 ? h1Prime : (h1Prime + 360); + const normalizedH2 = h2Prime >= 0 ? h2Prime : (h2Prime + 360); + + // calculate Delta h' (hue difference) and Delta H' (weighted hue difference) + let DeltaHPrime; + if (C1Prime * C2Prime === 0) { + DeltaHPrime = 0; // if one chroma is zero, hue difference is meaningless. + } else if (abs(normalizedH1 - normalizedH2) <= 180) { + DeltaHPrime = normalizedH2 - normalizedH1; + } else if ((normalizedH2 - normalizedH1) > 180) { + DeltaHPrime = (normalizedH2 - normalizedH1) - 360; + } else { // (h2 - h1) < -180 + DeltaHPrime = (normalizedH2 - normalizedH1) + 360; + } + + // convert Delta H' to a metric difference (ΔH') + const DeltaSmallHPrime = 2 * Math.sqrt(C1Prime * C2Prime) * Math.sin(DeltaHPrime * deg2rad / 2.0); + + // calculate Delta L' + const DeltaLPrime = L2 - L1; + + // calculate Average H' (HBarPrime) + let HBarPrime; + if (C1Prime * C2Prime === 0) { + HBarPrime = normalizedH1 + normalizedH2; // use sum as average if one is indeterminate + } else if (abs(normalizedH1 - normalizedH2) > 180) { + if ((normalizedH1 + normalizedH2) < 360) { + HBarPrime = (normalizedH1 + normalizedH2 + 360) / 2.0; + } else { + HBarPrime = (normalizedH1 + normalizedH2 - 360) / 2.0; + } + } else { + HBarPrime = (normalizedH1 + normalizedH2) / 2.0; + } + + // calculate T (hue weighting function) + const T = 1.0 - 0.17 * Math.cos((HBarPrime - 30.0) * deg2rad) + + 0.24 * Math.cos((2.0 * HBarPrime) * deg2rad) + + 0.32 * Math.cos((3.0 * HBarPrime + 6.0) * deg2rad) + - 0.20 * Math.cos((4.0 * HBarPrime - 63.0) * deg2rad); + + // calculate SL, SC, SH (weighting functions) + const SL = 1.0 + ((0.015 * ((HBarPrime - 27.5) ** 2) / (20.0 + ((HBarPrime - 27.5) ** 2)))); + const SC = 1.0 + 0.045 * CBarPrime; + const SH = 1.0 + 0.015 * CBarPrime * T; + + // calculate RT (rotation term) + const CBarPrimePow7 = CBarPrime ** 7; + const RT = -2.0 * Math.sin(HBarPrime * deg2rad) + * Math.sqrt(CBarPrimePow7 / (CBarPrimePow7 + 6103515625.0)); + + // calculate the final Delta E 2000 value + const deltaE = Math.sqrt( + (DeltaLPrime / (kL * SL)) ** 2 + + (DeltaCPrime / (kC * SC)) ** 2 + + (DeltaSmallHPrime / (kH * SH)) ** 2 + + RT * (DeltaCPrime / (kC * SC)) * (DeltaSmallHPrime / (kH * SH)) + ); + + return deltaE; +} + +(function(Scratch) { + 'use strict'; + + const spectrumIcon = "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI1NS42NTIwNCIgaGVpZ2h0PSI1NS42NTIwNCIgdmlld0JveD0iMCwwLDU1LjY1MjA0LDU1LjY1MjA0Ij48ZGVmcz48bGluZWFyR3JhZGllbnQgeDE9IjI0Mi4wNjU4NSIgeTE9IjE1Mi4zNjgwMSIgeDI9IjIzNy45MzQxOSIgeTI9IjIwNy42MzE5OSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIGlkPSJjb2xvci0xIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiMzMzNhZmYiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMzMzNhZmYiIHN0b3Atb3BhY2l0eT0iMCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IHgxPSIyMTIuMzY4MDIiIHkxPSIxNzcuOTM0MTciIHgyPSIyNjcuNjMxOTkiIHkyPSIxODIuMDY1ODMiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiBpZD0iY29sb3ItMiI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjMzZmZjMzIiBzdG9wLW9wYWNpdHk9IjAuNzAxOTYiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMzNmZmMzMiIHN0b3Atb3BhY2l0eT0iMCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMTIuMTc0LC0xNTIuMTczOTgpIj48ZyBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiPjxwYXRoIGQ9Ik0yMTIuMjkwOTEsMTgwYzAsLTE1LjMwMzMyIDEyLjQwNTc5LC0yNy43MDkxMSAyNy43MDkxMSwtMjcuNzA5MTFjMTUuMzAzMzIsMCAyNy43MDkxMSwxMi40MDU3OSAyNy43MDkxMSwyNy43MDkxMWMwLDE1LjMwMzMyIC0xMi40MDU3OSwyNy43MDkxMSAtMjcuNzA5MTEsMjcuNzA5MTFjLTE1LjMwMzMyLDAgLTI3LjcwOTExLC0xMi40MDU3OSAtMjcuNzA5MTEsLTI3LjcwOTExeiIgZmlsbD0iI2ZmMzMzMyIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9Ik5hTiIvPjxwYXRoIGQ9Ik0yMTIuMzY4MDMsMTc3LjkzNDE3YzEuMTQwOTMsLTE1LjI2MDczIDE0LjQzNzEsLTI2LjcwNzA5IDI5LjY5NzgyLC0yNS41NjYxNmMxNS4yNjA3MywxLjE0MDkzIDI2LjcwNzA5LDE0LjQzNzEgMjUuNTY2MTYsMjkuNjk3ODJjLTEuMTQwOTMsMTUuMjYwNzMgLTE0LjQzNzEsMjYuNzA3MDkgLTI5LjY5NzgyLDI1LjU2NjE2Yy0xNS4yNjA3MywtMS4xNDA5MyAtMjYuNzA3MDksLTE0LjQzNzEgLTI1LjU2NjE2LC0yOS42OTc4MnoiIGZpbGw9InVybCgjY29sb3ItMSkiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSJOYU4iLz48cGF0aCBkPSJNMjM3LjkzNDE5LDIwNy42MzJjLTE1LjI2MDcyLC0xLjE0MDkzIC0yNi43MDcwOSwtMTQuNDM3MSAtMjUuNTY2MTYsLTI5LjY5NzgzYzEuMTQwOTMsLTE1LjI2MDczIDE0LjQzNzEsLTI2LjcwNzA5IDI5LjY5NzgzLC0yNS41NjYxN2MxNS4yNjA3MywxLjE0MDkzIDI2LjcwNzA5LDE0LjQzNzEgMjUuNTY2MTYsMjkuNjk3ODNjLTEuMTQwOTMsMTUuMjYwNzMgLTE0LjQzNzEsMjYuNzA3MDkgLTI5LjY5NzgzLDI1LjU2NjE3eiIgZmlsbD0idXJsKCNjb2xvci0yKSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9Ik5hTiIvPjxwYXRoIGQ9Ik0yMTMuNDI0LDE4MGMwLC0xNC42Nzc1MyAxMS44OTg0OSwtMjYuNTc2MDIgMjYuNTc2MDIsLTI2LjU3NjAyYzE0LjY3NzUzLDAgMjYuNTc2MDIsMTEuODk4NDkgMjYuNTc2MDIsMjYuNTc2MDJjMCwxNC42Nzc1MyAtMTEuODk4NDksMjYuNTc2MDIgLTI2LjU3NjAyLDI2LjU3NjAyYy0xNC42Nzc1MywwIC0yNi41NzYwMiwtMTEuODk4NDkgLTI2LjU3NjAyLC0yNi41NzYwMnoiIGZpbGw9Im5vbmUiIHN0cm9rZS1vcGFjaXR5PSIwLjQ0MzE0IiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS13aWR0aD0iMi41Ii8+PC9nPjwvZz48L3N2Zz4="; + const blockIcon = "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIxNi41ODk4NSIgaGVpZ2h0PSIxNi41ODk4NSIgdmlld0JveD0iMCwwLDE2LjU4OTg1LDE2LjU4OTg1Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjMxLjcwNTA4LC0xNzEuNzA1MDYpIj48ZyBzdHJva2Utd2lkdGg9IjAuNjI1IiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiPjxwYXRoIGQ9Ik0yMzIuMDQ1LDE4M2MwLC0yLjQ4NTI4IDIuMDE0NzIsLTQuNSA0LjUsLTQuNWMyLjQ4NTI4LDAgNC41LDIuMDE0NzIgNC41LDQuNWMwLDIuNDg1MjggLTIuMDE0NzIsNC41IC00LjUsNC41Yy0yLjQ4NTI4LDAgLTQuNSwtMi4wMTQ3MiAtNC41LC00LjV6IiBmaWxsPSIjNGM5N2ZmIiBzdHJva2U9IiMzMzczY2MiLz48cGF0aCBkPSJNMjQwLjA0NSwxNzIuNWMyLjQ4NTI4LDAgNC41LDIuMDE0NzIgNC41LDQuNWMwLDIuNDg1MjggLTIuMDE0NzIsNC41IC00LjUsNC41Yy0yLjQ4NTI4LDAgLTQuNSwtMi4wMTQ3MiAtNC41LC00LjVjMCwtMi40ODUyOCAyLjAxNDcyLC00LjUgNC41LC00LjV6IiBmaWxsPSIjZmY2NjgwIiBzdHJva2U9IiNmZjMzNTUiLz48cGF0aCBkPSJNMjQzLjQ1NSwxNzguNWMyLjQ4NTI4LDAgNC41LDIuMDE0NzIgNC41LDQuNWMwLDIuNDg1MjggLTIuMDE0NzIsNC41IC00LjUsNC41Yy0yLjQ4NTI4LDAgLTQuNSwtMi4wMTQ3MiAtNC41LC00LjVjMCwtMi40ODUyOCAyLjAxNDcyLC00LjUgNC41LC00LjV6IiBmaWxsPSIjZmZiZjAwIiBzdHJva2U9IiNjYzk5MDAiLz48cGF0aCBkPSJNMjMxLjcwNTA5LDE4OC4yOTQ5MnYtMTYuNTg5ODVoMTYuNTg5ODV2MTYuNTg5ODV6IiBmaWxsPSJub25lIiBzdHJva2U9Im5vbmUiLz48cGF0aCBkPSJNMjQzLjQ1NSwxNzguNWMyLjQ4NTI4LDAgNC41LDIuMDE0NzIgNC41LDQuNWMwLDIuNDg1MjggLTIuMDE0NzIsNC41IC00LjUsNC41Yy0yLjQ4NTI4LDAgLTQuNSwtMi4wMTQ3MiAtNC41LC00LjVjMCwtMi40ODUyOCAyLjAxNDcyLC00LjUgNC41LC00LjV6IiBmaWxsLW9wYWNpdHk9IjAuNTAxOTYiIGZpbGw9IiNmZmJmMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjUwMTk2IiBzdHJva2U9IiNjYzk5MDAiLz48cGF0aCBkPSJNMjMyLjA0NSwxODNjMCwtMi40ODUyOCAyLjAxNDcyLC00LjUgNC41LC00LjVjMi40ODUyOCwwIDQuNSwyLjAxNDcyIDQuNSw0LjVjMCwyLjQ4NTI4IC0yLjAxNDcyLDQuNSAtNC41LDQuNWMtMi40ODUyOCwwIC00LjUsLTIuMDE0NzIgLTQuNSwtNC41eiIgZmlsbC1vcGFjaXR5PSIwLjUwMTk2IiBmaWxsPSIjNGQ5N2ZmIiBzdHJva2Utb3BhY2l0eT0iMC41MDE5NiIgc3Ryb2tlPSIjMzM3M2NjIi8+PHBhdGggZD0iTTI0MC4wNDUsMTcyLjVjMi40ODUyOCwwIDQuNSwyLjAxNDcyIDQuNSw0LjVjMCwyLjQ4NTI4IC0yLjAxNDcyLDQuNSAtNC41LDQuNWMtMi40ODUyOCwwIC00LjUsLTIuMDE0NzIgLTQuNSwtNC41YzAsLTIuNDg1MjggMi4wMTQ3MiwtNC41IDQuNSwtNC41eiIgZmlsbC1vcGFjaXR5PSIwLjUwMTk2IiBmaWxsPSIjZmY2NjgwIiBzdHJva2Utb3BhY2l0eT0iMC41MDE5NiIgc3Ryb2tlPSIjZmYzMzU1Ii8+PC9nPjwvZz48L3N2Zz48IS0tcm90YXRpb25DZW50ZXI6OC4yOTQ5MTUwMDAwMDAwMDM6OC4yOTQ5MzUwMDAwMDAwMS0tPg=="; + class Colors { + getInfo() { + return { + id: 'lxColors', + name: Scratch.translate('Colors'), + color1: '#f94c97', + menuIconURI: blockIcon, + blocks: [ + { + opcode: 'newColor', + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate('new color [COL]'), + arguments: { + COL: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#a45eff' + } + } + }, + { + opcode: 'newColorRGB', + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate('from RGB [R] [G] [B]'), + arguments: { + R: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '164' + }, + G: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '94' + }, + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '255' + } + } + }, + { + opcode: 'newColorHSV', + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate('from HSV [H] [S] [V]'), + arguments: { + H: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '266' + }, + S: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '63' + }, + V: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '100' + } + } + }, + { + opcode: 'newColorHSL', + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate('from HSL [H] [S] [L]'), + arguments: { + H: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '266' + }, + S: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '100' + }, + L: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '68' + } + } + }, + { + opcode: 'newColorDecimal', + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate({ + default: 'from decimal [DEC]', + description: "From decimal - as in the base system" + }), + arguments: { + DEC: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '10772223' + } + } + }, + '---', + { + opcode: 'randomColor', + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate('random color'), + disableMonitor: true + }, + '---', + { + opcode: 'additiveBlend', + blockType: Scratch.BlockType.REPORTER, + text: '[COL1] + [COL2]', + arguments: { + COL1: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#a45eff' + }, + COL2: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#eb57ab' + } + } + }, + { + opcode: 'subtractiveBlend', + blockType: Scratch.BlockType.REPORTER, + text: '[COL1] - [COL2]', + arguments: { + COL1: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#a45eff' + }, + COL2: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#eb57ab' + } + } + }, + { + opcode: 'multiplicativeBlend', + blockType: Scratch.BlockType.REPORTER, + text: '[COL1] * [COL2]', + arguments: { + COL1: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#a45eff' + }, + COL2: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#eb57ab' + } + } + }, + { + opcode: 'divisingBlend', + blockType: Scratch.BlockType.REPORTER, + text: '[COL1] / [COL2]', + arguments: { + COL1: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#a45eff' + }, + COL2: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#eb57ab' + } + } + }, + '---', + { + opcode: 'differenceBlend', + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate('difference of [COL1] - [COL2]'), + arguments: { + COL1: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#a45eff' + }, + COL2: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#eb57ab' + } + } + }, + { + opcode: 'screenBlend', + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate('screen [COL1] * [COL2]'), + arguments: { + COL1: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#a45eff' + }, + COL2: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#eb57ab' + } + } + }, + { + opcode: 'overlayBlend', + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate('overlay [COL1] * [COL2]'), + arguments: { + COL1: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#a45eff' + }, + COL2: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#eb57ab' + } + } + }, + '---', + { + opcode: 'invertColor', + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate('invert [COL]'), + arguments: { + COL: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#a45eff' + }, + } + }, + { + opcode: 'contrastColor', + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate({ + default: 'contrast [COL] by [NUM]', + description: "Contrast - as a verb, comparing to highlight differences" + }), + arguments: { + COL: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#a45eff' + }, + NUM: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '0.5' + }, + } + }, + { + opcode: 'grayscaleColor', + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate('grayscale [COL]'), + arguments: { + COL: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#a45eff' + } + } + }, + { + opcode: 'percentWhite', + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate('[NUM] % white'), + arguments: { + NUM: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '75' + } + } + }, + '---', + { + opcode: 'distanceBetweenColors', + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate('distance between [COL1] and [COL2]'), + arguments: { + COL1: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#a45eff' + }, + COL2: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#eb57ab' + } + } + }, + { + opcode: 'contrastRatioOfColors', + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate('contrast ratio of [COL1] and [COL2]'), + arguments: { + COL1: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#a45eff' + }, + COL2: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#eb57ab' + } + } + }, + { + opcode: 'nearEqualColors', + blockType: Scratch.BlockType.BOOLEAN, + text: Scratch.translate('[COL1] ≈ [COL2] threshold [THR]'), + arguments: { + COL1: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#a45eff' + }, + COL2: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#eb57ab' + }, + THR: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '25' + } + } + }, + { + opcode: 'colorFollowsWCAG', + blockType: Scratch.BlockType.BOOLEAN, + text: Scratch.translate('does [COL1] and [COL2] follow [AAA] for [TXT] text'), + arguments: { + COL1: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#a45eff' + }, + COL2: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#eb57ab' + }, + AAA: { + type: Scratch.ArgumentType.STRING, + menu: 'WCAG_MENU' + }, + TXT: { + type: Scratch.ArgumentType.STRING, + menu: 'TEXTWCAG_SIZE_MENU' + } + }, + hideFromPalette: true + }, + '---', + { + opcode: 'interpolateColors', + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate('interpolate [COL1] to [COL2] ratio [RATIO] using [SPACE]'), + arguments: { + COL1: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#a45eff' + }, + COL2: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#eb57ab' + }, + RATIO: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: '0.5' + }, + SPACE: { + type: Scratch.ArgumentType.STRING, + menu: 'SPACE_MENU' + } + } + }, + '---', + { + opcode: 'getChannelFromColor', + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate('get [CHN] of [COL]'), + arguments: { + CHN: { + type: Scratch.ArgumentType.STRING, + menu: 'CHANNEL_MENU' + }, + COL: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#a45eff' + } + } + }, + { + opcode: 'setChannelOfColor', + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate('set [CHN] of [COL] to [SET]'), + arguments: { + CHN: { + type: Scratch.ArgumentType.STRING, + menu: 'CHANNEL_MENU' + }, + COL: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#a45eff' + }, + SET: { + type: Scratch.ArgumentType.NUMBER, + } + } + }, + { + opcode: 'changeChannelOfColor', + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate('change [CHN] of [COL] by [SET]'), + arguments: { + CHN: { + type: Scratch.ArgumentType.STRING, + menu: 'CHANNEL_MENU' + }, + COL: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#a45eff' + }, + SET: { + type: Scratch.ArgumentType.NUMBER, + } + } + }, + '---', + { + opcode: 'colorToDecimal', + blockType: Scratch.BlockType.REPORTER, + text: Scratch.translate({ + default: '[COL] to decimal', + description: "To decimal - as in the base system" + }), + arguments: { + COL: { + type: Scratch.ArgumentType.COLOR, + defaultValue: '#a45eff' + } + } + }, + ], + menus: { + CHANNEL_MENU: { + acceptReporters: true, + items: [ + { text: Scratch.translate("red"), value: "red" }, + { text: Scratch.translate("green"), value: "green" }, + { text: Scratch.translate("blue"), value: "blue" }, + { text: Scratch.translate("hue"), value: "hue" }, + { text: Scratch.translate("saturation"), value: "saturation" }, + { text: Scratch.translate("value"), value: "value" }, + ], + }, + SPACE_MENU: { + acceptReporters: true, + items: [ + { text: Scratch.translate("RGB"), value: "RGB" }, + { text: Scratch.translate("HSV"), value: "HSV" }, + ], + }, + WCAG_MENU: { + acceptReporters: true, + items: ['A', 'AA', 'AAA'], + }, + TEXTWCAG_SIZE_MENU: { + acceptReporters: true, + items: [ + { text: Scratch.translate("normal"), value: "normal" }, + { text: Scratch.translate("large"), value: "large" }, + ], + }, + } + }; + } + newColor(args) { + return args.COL; + } + newColorRGB(args) { + return '#' + toFixHex(args.R) + toFixHex(args.G) + toFixHex(args.B); + } + newColorHSV(args) { + return hsvToHex(args.H, args.S, args.V); + } + newColorHSL(args) { + let convRGB = hslToRgb(args.H, args.S, args.L); + return '#' + toFixHex(convRGB.r) + toFixHex(convRGB.g) + toFixHex(convRGB.b); + } + newColorDecimal(args) { + return '#' + toHex(args.DEC); + } + randomColor() { + return '#' + Math.floor(Math.random() * 0xFFFFFF).toString(16).padStart(6, '0'); + } + additiveBlend(args) { + let a = hexToRgb(args.COL1); + let b = hexToRgb(args.COL2); + const add = (c1, c2) => clamp(c1 + c2, 0, 255); + return rgbToHex({ + r: add(a.r, b.r), + g: add(a.g, b.g), + b: add(a.b, b.b) + }); + } + subtractiveBlend(args) { + let a = hexToRgb(args.COL1); + let b = hexToRgb(args.COL2); + const sub = (c1, c2) => clamp(c1 - c2, 0, 255); + return rgbToHex({ + r: sub(a.r, b.r), + g: sub(a.g, b.g), + b: sub(a.b, b.b) + }); + } + multiplicativeBlend(args) { + let a = hexToRgb(args.COL1); + let b = hexToRgb(args.COL2); + const mul = (c1, c2) => clamp((c1 * c2) / 255, 0, 255); + return rgbToHex({ + r: mul(a.r, b.r), + g: mul(a.g, b.g), + b: mul(a.b, b.b) + }); + } + divisingBlend(args) { + let a = hexToRgb(args.COL1); + let b = hexToRgb(args.COL2); + const div = (c1, c2) => clamp((c1 / Math.max(c2, 1)) * 255, 0, 255); + return rgbToHex({ + r: div(a.r, b.r), + g: div(a.g, b.g), + b: div(a.b, b.b) + }); + } + differenceBlend(args) { + let a = hexToRgb(args.COL1); + let b = hexToRgb(args.COL2); + const sub = (c1, c2) => clamp(abs(c1 - c2), 0, 255); + return rgbToHex({ + r: sub(a.r, b.r), + g: sub(a.g, b.g), + b: sub(a.b, b.b) + }); + } + screenBlend(args) { + let a = hexToRgb(args.COL1); + let b = hexToRgb(args.COL2); + const scr = (c1, c2) => clamp(c1 + c2 - (c1 * c2) / 255, 0, 255); + return rgbToHex({ + r: scr(a.r, b.r), + g: scr(a.g, b.g), + b: scr(a.b, b.b) + }); + } + overlayBlend(args) { + let a = hexToRgb(args.COL1); + let b = hexToRgb(args.COL2); + const ove = (c1, c2) => clamp(c1 < 128 ? (2 * c1 * c2) / 255 : 255 - (2 * (255 - c1) * (255 - c2)) / 255, 0, 255); + return rgbToHex({ + r: ove(a.r, b.r), + g: ove(a.g, b.g), + b: ove(a.b, b.b) + }); + } + invertColor(args) { + let col = hexToRgb(args.COL); + const inv = c => 255-c; + return rgbToHex({ + r: inv(col.r), + g: inv(col.g), + b: inv(col.b), + }); + } + contrastColor(args) { + let col = hexToRgb(args.COL); + const cnt = c => ((c - 128) * (1 - args.NUM)) + 128; + return rgbToHex({ + r: cnt(col.r), + g: cnt(col.g), + b: cnt(col.b), + }); + } + grayscaleColor(args) { + let rgb = hexToRgb(args.COL); + let gray = Math.round(0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b); + return '#' + toFixHex(gray) + toFixHex(gray) + toFixHex(gray); + } + percentWhite(args) { + let white = round(args.NUM * 2.55) + return '#' + toFixHex(white) + toFixHex(white) + toFixHex(white); + } + distanceBetweenColors(args) { + return distanceBetweenHex(args.COL1, args.COL2); + } + nearEqualColors(args) { + return distanceBetweenHex(args.COL1, args.COL2) <= args.THR; + } + contrastRatioOfColors(args) { + return round(contrastRatio(args.COL1, args.COL2) * 100) / 100; + } + colorFollowsWCAG(args) { + let ratio = round(contrastRatio(args.COL1, args.COL2) * 100) / 100; + let req = 0; + let level = args.AAA; + let size = args.TXT; + if (level === 'AA') { + req = (size === 'large') ? 3.0 : 4.5; + } + if (level === 'AAA') { + req = (size === 'large') ? 4.5 : 7.0; + } + return ratio >= req; + } + interpolateColors(args) { + if (args.SPACE == 'RGB') { + let a = hexToRgb(args.COL1); + let b = hexToRgb(args.COL2); + let t = args.RATIO; + return rgbToHex({ + r: lerp(a.r, b.r, t), + g: lerp(a.g, b.g, t), + b: lerp(a.b, b.b, t) + }); + } else { + return interpolateHexColorsHsv(args.COL1, args.COL2, args.RATIO) + } + } + getChannelFromColor(args) { + if (['red', 'green', 'blue'].includes(args.CHN)) { + let channel = ['red', 'green', 'blue'].indexOf(args.CHN); + let letter = ['red', 'green', 'blue'][channel][0]; + return hexToRgb(args.COL)[letter]; + } else if (['hue', 'saturation', 'value'].includes(args.CHN)) { + let hsv = hexToHsv(args.COL); + switch (args.CHN) { + case 'hue': return round(hsv.h); + case 'saturation': return round(hsv.s); + case 'value': return round(hsv.v); + } + } + } + setChannelOfColor(args) { + if (['red', 'green', 'blue'].includes(args.CHN)) { + let rgb = hexToRgb(args.COL); + switch (args.CHN) { + case 'red': rgb.r = args.SET; break; + case 'green': rgb.g = args.SET; break; + case 'blue': rgb.b = args.SET; break; + } + return '#' + toFixHex(rgb.r) + toFixHex(rgb.g) + toFixHex(rgb.b); + } else if (['hue', 'saturation', 'value'].includes(args.CHN)) { + let hsv = hexToHsv(args.COL); + switch (args.CHN) { + case 'hue': hsv.h = args.SET; break; + case 'saturation': hsv.s = args.SET; break; + case 'value': hsv.v = args.SET; break; + } + return hsvToHex(hsv.h, hsv.s, hsv.v); + } + } + changeChannelOfColor(args) { + if (['red', 'green', 'blue'].includes(args.CHN)) { + let rgb = hexToRgb(args.COL); + switch (args.CHN) { + case 'red': rgb.r = clamp(rgb.r + args.SET, 0, 255); break; + case 'green': rgb.g = clamp(rgb.g + args.SET, 0, 255); break; + case 'blue': rgb.b = clamp(rgb.b + args.SET, 0, 255); break; + } + return '#' + toFixHex(rgb.r) + toFixHex(rgb.g) + toFixHex(rgb.b); + } else if (['hue', 'saturation', 'value'].includes(args.CHN)) { + let hsv = hexToHsv(args.COL); + switch (args.CHN) { + case 'hue': hsv.h = (hsv.h + args.SET) % 360; break; + case 'saturation': hsv.s = clamp(hsv.s + args.SET, 0, 100); break; + case 'value': hsv.v = clamp(hsv.v + args.SET, 0, 100); break; + } + return hsvToHex(hsv.h, hsv.s, hsv.v); + } + } + colorToDecimal(args) { + return toDec(args.COL.slice(1)); + } + } + Scratch.extensions.register(new Colors()); +})(Scratch); From ab498c14af82837703acc1bdff1b47d5d7be7685 Mon Sep 17 00:00:00 2001 From: LincolnX <118082287+LincolnXGames@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:32:04 -0600 Subject: [PATCH 02/12] Add files via upload --- images/colors.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 images/colors.svg diff --git a/images/colors.svg b/images/colors.svg new file mode 100644 index 0000000000..0e1b22bc91 --- /dev/null +++ b/images/colors.svg @@ -0,0 +1 @@ + \ No newline at end of file From 103e8555deb2a20492fe071e523fe7b8edf90b85 Mon Sep 17 00:00:00 2001 From: LincolnX <118082287+LincolnXGames@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:33:06 -0600 Subject: [PATCH 03/12] Delete images/colors.svg --- images/colors.svg | 1 - 1 file changed, 1 deletion(-) delete mode 100644 images/colors.svg diff --git a/images/colors.svg b/images/colors.svg deleted file mode 100644 index 0e1b22bc91..0000000000 --- a/images/colors.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From 1d17f8e8de7713c4195b63f595157aae02475842 Mon Sep 17 00:00:00 2001 From: LincolnX <118082287+LincolnXGames@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:33:37 -0600 Subject: [PATCH 04/12] Create colors.svg --- images/LincolnX/colors.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 images/LincolnX/colors.svg diff --git a/images/LincolnX/colors.svg b/images/LincolnX/colors.svg new file mode 100644 index 0000000000..66b6046557 --- /dev/null +++ b/images/LincolnX/colors.svg @@ -0,0 +1 @@ + From 1f02b50bd0bbb6b2bde219cd35cb929699887e46 Mon Sep 17 00:00:00 2001 From: LincolnX <118082287+LincolnXGames@users.noreply.github.com> Date: Wed, 17 Dec 2025 21:35:45 -0600 Subject: [PATCH 05/12] Add LincolnX/colors extension to extensions.json --- extensions/extensions.json | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/extensions.json b/extensions/extensions.json index 442f300d59..e27bdf8722 100644 --- a/extensions/extensions.json +++ b/extensions/extensions.json @@ -21,6 +21,7 @@ "iframe", "Clay/htmlEncode", "Xeltalliv/clippingblending", + "LincolnX/colors", "clipboard", "obviousAlexC/penPlus", "penplus", From 1eb80ebddb4a71be9ab58cd5238551e5ffafbe7f Mon Sep 17 00:00:00 2001 From: LincolnX <118082287+LincolnXGames@users.noreply.github.com> Date: Thu, 18 Dec 2025 10:09:26 -0600 Subject: [PATCH 06/12] Update colors.js --- extensions/LincolnX/colors.js | 36 ++++++++++++----------------------- 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/extensions/LincolnX/colors.js b/extensions/LincolnX/colors.js index 52715f5379..e08c7ef3db 100644 --- a/extensions/LincolnX/colors.js +++ b/extensions/LincolnX/colors.js @@ -13,10 +13,10 @@ const clamp = (n, mi, ma) => Math.min(Math.max(n, mi), ma); const fixHex = (hex) => limitHex(hex, 0, 255).padStart(2, '0'); const toFixHex = (dec) => fixHex(dec.toString(16)); const lerp = (a, b, t) => a + (b - a) * t; -function interpolateHexColorsHsv(color1, color2, factor) { - const hsv1 = hexToHsv(color1); - const hsv2 = hexToHsv(color2); - factor = Math.min(1, Math.max(0, factor)); +function interpolateHexColorsHsv(hex1, hex2, t) { + const hsv1 = hexToHsv(hex1); + const hsv2 = hexToHsv(hex2); + t = clamp(t, 0, 1) // Handle hue interpolation for the shortest path around the color wheel let h1 = hsv1.h; let h2 = hsv2.h; @@ -27,23 +27,11 @@ function interpolateHexColorsHsv(color1, color2, factor) { h2 += 360; } - const h = h1 + factor * (h2 - h1); - const s = hsv1.s + factor * (hsv2.s - hsv1.s); - const v = hsv1.v + factor * (hsv2.v - hsv1.v); - - const interpolatedRgb = hsvToRgb({ h, s, v }); - return rgbToHex(interpolatedRgb); -} -function overlayHex(hex1, hex2) { - let a = toDec(hex1) / 255; - let b = toDec(hex2) / 255; - let overlay; - if (a < 0.5) { - overlay = 2 * a * b; - } else { - overlay = 1 - (2 * (1 - a) * (1 - b)); - } - return toFixHex(overlay * 255) + const h = h1 + t * (h2 - h1); + const s = hsv1.s + t * (hsv2.s - hsv1.s); + const v = hsv1.v + t * (hsv2.v - hsv1.v); + + return hsvToHex({ h, s, v }); } function hexToRgb(hex) { @@ -171,7 +159,7 @@ function contrastRatio(hex1, hex2) { -function distanceBetweenHex(hex1, hex2) { +function distanceBetweenHexColorsDeltaE2000(hex1, hex2) { // convert Hex to RGB const rgb1 = hexToRgb(hex1); const rgb2 = hexToRgb(hex2); @@ -904,10 +892,10 @@ function deltaE2000(lab1, lab2) { return '#' + toFixHex(white) + toFixHex(white) + toFixHex(white); } distanceBetweenColors(args) { - return distanceBetweenHex(args.COL1, args.COL2); + return distanceBetweenHexColorsDeltaE2000(args.COL1, args.COL2); } nearEqualColors(args) { - return distanceBetweenHex(args.COL1, args.COL2) <= args.THR; + return distanceBetweenHexColorsDeltaE2000(args.COL1, args.COL2) <= args.THR; } contrastRatioOfColors(args) { return round(contrastRatio(args.COL1, args.COL2) * 100) / 100; From 758e596c8e1cb8865fe50da5005df1dd3a383d01 Mon Sep 17 00:00:00 2001 From: LincolnX <118082287+LincolnXGames@users.noreply.github.com> Date: Fri, 26 Dec 2025 20:43:28 -0600 Subject: [PATCH 07/12] Update colors.js --- extensions/LincolnX/colors.js | 178 +++++++++++++++++----------------- 1 file changed, 89 insertions(+), 89 deletions(-) diff --git a/extensions/LincolnX/colors.js b/extensions/LincolnX/colors.js index e08c7ef3db..6b42ebbe94 100644 --- a/extensions/LincolnX/colors.js +++ b/extensions/LincolnX/colors.js @@ -4,16 +4,19 @@ // By: LincolnX // License: MPL-2.0 -const { abs, round, floor, sqrt } = Math; - -const toDec = (hex) => parseInt(hex, 16); -const toHex = (dec) => dec.toString(16); -const limitHex = (hex, mi, ma) => toHex(Math.min(Math.max(toDec(hex), mi), ma)); -const clamp = (n, mi, ma) => Math.min(Math.max(n, mi), ma); -const fixHex = (hex) => limitHex(hex, 0, 255).padStart(2, '0'); -const toFixHex = (dec) => fixHex(dec.toString(16)); -const lerp = (a, b, t) => a + (b - a) * t; -function interpolateHexColorsHsv(hex1, hex2, t) { +(function(Scratch) { + 'use strict'; + + const { abs, round, floor, sqrt } = Math; + + const toDec = (hex) => parseInt(hex, 16); + const toHex = (dec) => dec.toString(16); + const limitHex = (hex, mi, ma) => toHex(Math.min(Math.max(toDec(hex), mi), ma)); + const clamp = (n, mi, ma) => Math.min(Math.max(n, mi), ma); + const fixHex = (hex) => limitHex(hex, 0, 255).padStart(2, '0'); + const toFixHex = (dec) => fixHex(dec.toString(16)); + const lerp = (a, b, t) => a + (b - a) * t; + function interpolateHexColorsHsv(hex1, hex2, t) { const hsv1 = hexToHsv(hex1); const hsv2 = hexToHsv(hex2); t = clamp(t, 0, 1) @@ -32,9 +35,9 @@ function interpolateHexColorsHsv(hex1, hex2, t) { const v = hsv1.v + t * (hsv2.v - hsv1.v); return hsvToHex({ h, s, v }); -} + } -function hexToRgb(hex) { + function hexToRgb(hex) { let r = 0, g = 0, b = 0; // Handle 3-digit shorthand if (hex.length === 4) { @@ -47,12 +50,12 @@ function hexToRgb(hex) { b = parseInt(hex.substring(5, 7), 16); } return { r, g, b }; -} -function rgbToHex(rgb) { + } + function rgbToHex(rgb) { const makeHex = c => Math.round(c).toString(16).padStart(2, '0'); return `#${makeHex(rgb.r)}${makeHex(rgb.g)}${makeHex(rgb.b)}`; -} -function hsvToRgb(h, s, v) { + } + function hsvToRgb(h, s, v) { var r, g, b, i, f, p, q, t; if (arguments.length === 1) { s = h.s, v = h.v, h = h.h; @@ -78,8 +81,8 @@ function hsvToRgb(h, s, v) { g: round(g * 255), b: round(b * 255) }; -} -function rgbToHsv(r, g, b) { + } + function rgbToHsv(r, g, b) { if (arguments.length === 1) { g = r.g, b = r.b, r = r.r; } @@ -99,67 +102,67 @@ function rgbToHsv(r, g, b) { s: s * 100, v: v * 100 }; -} -const hexToHsv = (hex) => rgbToHsv(hexToRgb(hex)); -function hsvToHex(h, s, v) { - if (arguments.length === 1) { + } + const hexToHsv = (hex) => rgbToHsv(hexToRgb(hex)); + function hsvToHex(h, s, v) { + if (arguments.length === 1) { s = h.s, v = h.v, h = h.h; } - return rgbToHex(hsvToRgb(h, s, v)); -} - -function hslToRgb(h, s, l) { - h /= 360; - s /= 100; - l /= 100; - let r, g, b; - if (s === 0) { - r = g = b = l; // achromatic - } else { - const q = l < 0.5 ? l * (1 + s) : l + s - l * s; - const p = 2 * l - q; - r = hueToRgb(p, q, h + 1/3); - g = hueToRgb(p, q, h); - b = hueToRgb(p, q, h - 1/3); + return rgbToHex(hsvToRgb(h, s, v)); + } + + function hslToRgb(h, s, l) { + h /= 360; + s /= 100; + l /= 100; + let r, g, b; + if (s === 0) { + r = g = b = l; // achromatic + } else { + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; + const p = 2 * l - q; + r = hueToRgb(p, q, h + 1/3); + g = hueToRgb(p, q, h); + b = hueToRgb(p, q, h - 1/3); + } + return {r: round(r * 255), g: round(g * 255), b: round(b * 255)}; + } + + function hueToRgb(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; + } + + function channelToLinear(c) { + c /= 255; + return c <= 0.03928 + ? c / 12.92 + : ((c + 0.055) / 1.055) ** 2.4; + } + + function relativeLuminance(hex) { + const { r, g, b } = hexToRgb(hex); + const R = channelToLinear(r); + const G = channelToLinear(g); + const B = channelToLinear(b); + return 0.2126 * R + 0.7152 * G + 0.0722 * B; } - return {r: round(r * 255), g: round(g * 255), b: round(b * 255)}; -} - -function hueToRgb(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; -} - -function channelToLinear(c) { - c /= 255; - return c <= 0.03928 - ? c / 12.92 - : ((c + 0.055) / 1.055) ** 2.4; -} - -function relativeLuminance(hex) { - const { r, g, b } = hexToRgb(hex); - const R = channelToLinear(r); - const G = channelToLinear(g); - const B = channelToLinear(b); - return 0.2126 * R + 0.7152 * G + 0.0722 * B; -} - -function contrastRatio(hex1, hex2) { - const L1 = relativeLuminance(hex1); - const L2 = relativeLuminance(hex2); - const light = Math.max(L1, L2); - const dark = Math.min(L1, L2); - return (light + 0.05) / (dark + 0.05); -} - - - -function distanceBetweenHexColorsDeltaE2000(hex1, hex2) { + + function contrastRatio(hex1, hex2) { + const L1 = relativeLuminance(hex1); + const L2 = relativeLuminance(hex2); + const light = Math.max(L1, L2); + const dark = Math.min(L1, L2); + return (light + 0.05) / (dark + 0.05); + } + + + + function distanceBetweenHexColorsDeltaE2000(hex1, hex2) { // convert Hex to RGB const rgb1 = hexToRgb(hex1); const rgb2 = hexToRgb(hex2); @@ -174,9 +177,9 @@ function distanceBetweenHexColorsDeltaE2000(hex1, hex2) { // calculate Delta E 2000 return deltaE2000(lab1, lab2); -} + } -function rgbToXyz(r, g, b) { + function rgbToXyz(r, g, b) { r /= 255; g /= 255; b /= 255; @@ -195,10 +198,10 @@ function rgbToXyz(r, g, b) { const z = r * 0.0193 + g * 0.1192 + b * 0.9505; return { x, y, z }; -} + } -// converts XYZ values to CIELAB color space. -function xyzToLab(x, y, z) { + // converts XYZ values to CIELAB color space. + function xyzToLab(x, y, z) { // D65 white point values const refX = 95.047; const refY = 100.000; @@ -217,12 +220,12 @@ function xyzToLab(x, y, z) { const b = 200 * (y - z); return { L, a, b }; -} + } -// calculates the Delta E 2000 difference between two Lab colors. -// based on the implementation notes from Sharma et al.. -function deltaE2000(lab1, lab2) { + // calculates the Delta E 2000 difference between two Lab colors. + // based on the implementation notes from Sharma et al.. + function deltaE2000(lab1, lab2) { const kL = 1.0, kC = 1.0, kH = 1.0; // parametric factors often set to 1.0 const deg2rad = Math.PI / 180; const rad2deg = 180 / Math.PI; @@ -313,10 +316,7 @@ function deltaE2000(lab1, lab2) { ); return deltaE; -} - -(function(Scratch) { - 'use strict'; + } const spectrumIcon = "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI1NS42NTIwNCIgaGVpZ2h0PSI1NS42NTIwNCIgdmlld0JveD0iMCwwLDU1LjY1MjA0LDU1LjY1MjA0Ij48ZGVmcz48bGluZWFyR3JhZGllbnQgeDE9IjI0Mi4wNjU4NSIgeTE9IjE1Mi4zNjgwMSIgeDI9IjIzNy45MzQxOSIgeTI9IjIwNy42MzE5OSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIGlkPSJjb2xvci0xIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiMzMzNhZmYiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMzMzNhZmYiIHN0b3Atb3BhY2l0eT0iMCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IHgxPSIyMTIuMzY4MDIiIHkxPSIxNzcuOTM0MTciIHgyPSIyNjcuNjMxOTkiIHkyPSIxODIuMDY1ODMiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiBpZD0iY29sb3ItMiI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjMzZmZjMzIiBzdG9wLW9wYWNpdHk9IjAuNzAxOTYiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMzNmZmMzMiIHN0b3Atb3BhY2l0eT0iMCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMTIuMTc0LC0xNTIuMTczOTgpIj48ZyBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiPjxwYXRoIGQ9Ik0yMTIuMjkwOTEsMTgwYzAsLTE1LjMwMzMyIDEyLjQwNTc5LC0yNy43MDkxMSAyNy43MDkxMSwtMjcuNzA5MTFjMTUuMzAzMzIsMCAyNy43MDkxMSwxMi40MDU3OSAyNy43MDkxMSwyNy43MDkxMWMwLDE1LjMwMzMyIC0xMi40MDU3OSwyNy43MDkxMSAtMjcuNzA5MTEsMjcuNzA5MTFjLTE1LjMwMzMyLDAgLTI3LjcwOTExLC0xMi40MDU3OSAtMjcuNzA5MTEsLTI3LjcwOTExeiIgZmlsbD0iI2ZmMzMzMyIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9Ik5hTiIvPjxwYXRoIGQ9Ik0yMTIuMzY4MDMsMTc3LjkzNDE3YzEuMTQwOTMsLTE1LjI2MDczIDE0LjQzNzEsLTI2LjcwNzA5IDI5LjY5NzgyLC0yNS41NjYxNmMxNS4yNjA3MywxLjE0MDkzIDI2LjcwNzA5LDE0LjQzNzEgMjUuNTY2MTYsMjkuNjk3ODJjLTEuMTQwOTMsMTUuMjYwNzMgLTE0LjQzNzEsMjYuNzA3MDkgLTI5LjY5NzgyLDI1LjU2NjE2Yy0xNS4yNjA3MywtMS4xNDA5MyAtMjYuNzA3MDksLTE0LjQzNzEgLTI1LjU2NjE2LC0yOS42OTc4MnoiIGZpbGw9InVybCgjY29sb3ItMSkiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSJOYU4iLz48cGF0aCBkPSJNMjM3LjkzNDE5LDIwNy42MzJjLTE1LjI2MDcyLC0xLjE0MDkzIC0yNi43MDcwOSwtMTQuNDM3MSAtMjUuNTY2MTYsLTI5LjY5NzgzYzEuMTQwOTMsLTE1LjI2MDczIDE0LjQzNzEsLTI2LjcwNzA5IDI5LjY5NzgzLC0yNS41NjYxN2MxNS4yNjA3MywxLjE0MDkzIDI2LjcwNzA5LDE0LjQzNzEgMjUuNTY2MTYsMjkuNjk3ODNjLTEuMTQwOTMsMTUuMjYwNzMgLTE0LjQzNzEsMjYuNzA3MDkgLTI5LjY5NzgzLDI1LjU2NjE3eiIgZmlsbD0idXJsKCNjb2xvci0yKSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9Ik5hTiIvPjxwYXRoIGQ9Ik0yMTMuNDI0LDE4MGMwLC0xNC42Nzc1MyAxMS44OTg0OSwtMjYuNTc2MDIgMjYuNTc2MDIsLTI2LjU3NjAyYzE0LjY3NzUzLDAgMjYuNTc2MDIsMTEuODk4NDkgMjYuNTc2MDIsMjYuNTc2MDJjMCwxNC42Nzc1MyAtMTEuODk4NDksMjYuNTc2MDIgLTI2LjU3NjAyLDI2LjU3NjAyYy0xNC42Nzc1MywwIC0yNi41NzYwMiwtMTEuODk4NDkgLTI2LjU3NjAyLC0yNi41NzYwMnoiIGZpbGw9Im5vbmUiIHN0cm9rZS1vcGFjaXR5PSIwLjQ0MzE0IiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS13aWR0aD0iMi41Ii8+PC9nPjwvZz48L3N2Zz4="; const blockIcon = "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIxNi41ODk4NSIgaGVpZ2h0PSIxNi41ODk4NSIgdmlld0JveD0iMCwwLDE2LjU4OTg1LDE2LjU4OTg1Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjMxLjcwNTA4LC0xNzEuNzA1MDYpIj48ZyBzdHJva2Utd2lkdGg9IjAuNjI1IiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiPjxwYXRoIGQ9Ik0yMzIuMDQ1LDE4M2MwLC0yLjQ4NTI4IDIuMDE0NzIsLTQuNSA0LjUsLTQuNWMyLjQ4NTI4LDAgNC41LDIuMDE0NzIgNC41LDQuNWMwLDIuNDg1MjggLTIuMDE0NzIsNC41IC00LjUsNC41Yy0yLjQ4NTI4LDAgLTQuNSwtMi4wMTQ3MiAtNC41LC00LjV6IiBmaWxsPSIjNGM5N2ZmIiBzdHJva2U9IiMzMzczY2MiLz48cGF0aCBkPSJNMjQwLjA0NSwxNzIuNWMyLjQ4NTI4LDAgNC41LDIuMDE0NzIgNC41LDQuNWMwLDIuNDg1MjggLTIuMDE0NzIsNC41IC00LjUsNC41Yy0yLjQ4NTI4LDAgLTQuNSwtMi4wMTQ3MiAtNC41LC00LjVjMCwtMi40ODUyOCAyLjAxNDcyLC00LjUgNC41LC00LjV6IiBmaWxsPSIjZmY2NjgwIiBzdHJva2U9IiNmZjMzNTUiLz48cGF0aCBkPSJNMjQzLjQ1NSwxNzguNWMyLjQ4NTI4LDAgNC41LDIuMDE0NzIgNC41LDQuNWMwLDIuNDg1MjggLTIuMDE0NzIsNC41IC00LjUsNC41Yy0yLjQ4NTI4LDAgLTQuNSwtMi4wMTQ3MiAtNC41LC00LjVjMCwtMi40ODUyOCAyLjAxNDcyLC00LjUgNC41LC00LjV6IiBmaWxsPSIjZmZiZjAwIiBzdHJva2U9IiNjYzk5MDAiLz48cGF0aCBkPSJNMjMxLjcwNTA5LDE4OC4yOTQ5MnYtMTYuNTg5ODVoMTYuNTg5ODV2MTYuNTg5ODV6IiBmaWxsPSJub25lIiBzdHJva2U9Im5vbmUiLz48cGF0aCBkPSJNMjQzLjQ1NSwxNzguNWMyLjQ4NTI4LDAgNC41LDIuMDE0NzIgNC41LDQuNWMwLDIuNDg1MjggLTIuMDE0NzIsNC41IC00LjUsNC41Yy0yLjQ4NTI4LDAgLTQuNSwtMi4wMTQ3MiAtNC41LC00LjVjMCwtMi40ODUyOCAyLjAxNDcyLC00LjUgNC41LC00LjV6IiBmaWxsLW9wYWNpdHk9IjAuNTAxOTYiIGZpbGw9IiNmZmJmMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjUwMTk2IiBzdHJva2U9IiNjYzk5MDAiLz48cGF0aCBkPSJNMjMyLjA0NSwxODNjMCwtMi40ODUyOCAyLjAxNDcyLC00LjUgNC41LC00LjVjMi40ODUyOCwwIDQuNSwyLjAxNDcyIDQuNSw0LjVjMCwyLjQ4NTI4IC0yLjAxNDcyLDQuNSAtNC41LDQuNWMtMi40ODUyOCwwIC00LjUsLTIuMDE0NzIgLTQuNSwtNC41eiIgZmlsbC1vcGFjaXR5PSIwLjUwMTk2IiBmaWxsPSIjNGQ5N2ZmIiBzdHJva2Utb3BhY2l0eT0iMC41MDE5NiIgc3Ryb2tlPSIjMzM3M2NjIi8+PHBhdGggZD0iTTI0MC4wNDUsMTcyLjVjMi40ODUyOCwwIDQuNSwyLjAxNDcyIDQuNSw0LjVjMCwyLjQ4NTI4IC0yLjAxNDcyLDQuNSAtNC41LDQuNWMtMi40ODUyOCwwIC00LjUsLTIuMDE0NzIgLTQuNSwtNC41YzAsLTIuNDg1MjggMi4wMTQ3MiwtNC41IDQuNSwtNC41eiIgZmlsbC1vcGFjaXR5PSIwLjUwMTk2IiBmaWxsPSIjZmY2NjgwIiBzdHJva2Utb3BhY2l0eT0iMC41MDE5NiIgc3Ryb2tlPSIjZmYzMzU1Ii8+PC9nPjwvZz48L3N2Zz48IS0tcm90YXRpb25DZW50ZXI6OC4yOTQ5MTUwMDAwMDAwMDM6OC4yOTQ5MzUwMDAwMDAwMS0tPg=="; From b85424d7da52a18d31641e9b49bb50482f6a18fd Mon Sep 17 00:00:00 2001 From: LincolnX <118082287+LincolnXGames@users.noreply.github.com> Date: Fri, 26 Dec 2025 21:29:48 -0600 Subject: [PATCH 08/12] Update colors.js --- extensions/LincolnX/colors.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/extensions/LincolnX/colors.js b/extensions/LincolnX/colors.js index 6b42ebbe94..3f9f178f76 100644 --- a/extensions/LincolnX/colors.js +++ b/extensions/LincolnX/colors.js @@ -235,19 +235,19 @@ const L2 = lab2.L, a2 = lab2.a, b2 = lab2.b; // calculate C*ab values (Chroma) - const C1 = Math.sqrt(a1*a1 + b1*b1); - const C2 = Math.sqrt(a2*a2 + b2*b2); + const C1 = sqrt(a1*a1 + b1*b1); + const C2 = sqrt(a2*a2 + b2*b2); const CBar = (C1 + C2) / 2.0; // calculate G (chroma correction factor) const CBarPow7 = CBar ** 7; - const G = 0.5 * (1 - Math.sqrt(CBarPow7 / (CBarPow7 + 6103515625.0))); // 6103515625 = 25^7 + const G = 0.5 * (1 - sqrt(CBarPow7 / (CBarPow7 + 6103515625.0))); // 6103515625 = 25^7 // calculate a' and C' (lightness corrected a* and new chroma) const a1Prime = a1 * (1 + G); const a2Prime = a2 * (1 + G); - const C1Prime = Math.sqrt(a1Prime*a1Prime + b1*b1); - const C2Prime = Math.sqrt(a2Prime*a2Prime + b2*b2); + const C1Prime = sqrt(a1Prime*a1Prime + b1*b1); + const C2Prime = sqrt(a2Prime*a2Prime + b2*b2); const CBarPrime = (C1Prime + C2Prime) / 2.0; const DeltaCPrime = C2Prime - C1Prime; @@ -272,7 +272,7 @@ } // convert Delta H' to a metric difference (ΔH') - const DeltaSmallHPrime = 2 * Math.sqrt(C1Prime * C2Prime) * Math.sin(DeltaHPrime * deg2rad / 2.0); + const DeltaSmallHPrime = 2 * sqrt(C1Prime * C2Prime) * Math.sin(DeltaHPrime * deg2rad / 2.0); // calculate Delta L' const DeltaLPrime = L2 - L1; @@ -305,10 +305,10 @@ // calculate RT (rotation term) const CBarPrimePow7 = CBarPrime ** 7; const RT = -2.0 * Math.sin(HBarPrime * deg2rad) - * Math.sqrt(CBarPrimePow7 / (CBarPrimePow7 + 6103515625.0)); + * sqrt(CBarPrimePow7 / (CBarPrimePow7 + 6103515625.0)); // calculate the final Delta E 2000 value - const deltaE = Math.sqrt( + const deltaE =sqrt( (DeltaLPrime / (kL * SL)) ** 2 + (DeltaCPrime / (kC * SC)) ** 2 + (DeltaSmallHPrime / (kH * SH)) ** 2 + @@ -318,7 +318,7 @@ return deltaE; } - const spectrumIcon = "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI1NS42NTIwNCIgaGVpZ2h0PSI1NS42NTIwNCIgdmlld0JveD0iMCwwLDU1LjY1MjA0LDU1LjY1MjA0Ij48ZGVmcz48bGluZWFyR3JhZGllbnQgeDE9IjI0Mi4wNjU4NSIgeTE9IjE1Mi4zNjgwMSIgeDI9IjIzNy45MzQxOSIgeTI9IjIwNy42MzE5OSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIGlkPSJjb2xvci0xIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiMzMzNhZmYiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMzMzNhZmYiIHN0b3Atb3BhY2l0eT0iMCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IHgxPSIyMTIuMzY4MDIiIHkxPSIxNzcuOTM0MTciIHgyPSIyNjcuNjMxOTkiIHkyPSIxODIuMDY1ODMiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiBpZD0iY29sb3ItMiI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjMzZmZjMzIiBzdG9wLW9wYWNpdHk9IjAuNzAxOTYiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMzNmZmMzMiIHN0b3Atb3BhY2l0eT0iMCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMTIuMTc0LC0xNTIuMTczOTgpIj48ZyBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiPjxwYXRoIGQ9Ik0yMTIuMjkwOTEsMTgwYzAsLTE1LjMwMzMyIDEyLjQwNTc5LC0yNy43MDkxMSAyNy43MDkxMSwtMjcuNzA5MTFjMTUuMzAzMzIsMCAyNy43MDkxMSwxMi40MDU3OSAyNy43MDkxMSwyNy43MDkxMWMwLDE1LjMwMzMyIC0xMi40MDU3OSwyNy43MDkxMSAtMjcuNzA5MTEsMjcuNzA5MTFjLTE1LjMwMzMyLDAgLTI3LjcwOTExLC0xMi40MDU3OSAtMjcuNzA5MTEsLTI3LjcwOTExeiIgZmlsbD0iI2ZmMzMzMyIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9Ik5hTiIvPjxwYXRoIGQ9Ik0yMTIuMzY4MDMsMTc3LjkzNDE3YzEuMTQwOTMsLTE1LjI2MDczIDE0LjQzNzEsLTI2LjcwNzA5IDI5LjY5NzgyLC0yNS41NjYxNmMxNS4yNjA3MywxLjE0MDkzIDI2LjcwNzA5LDE0LjQzNzEgMjUuNTY2MTYsMjkuNjk3ODJjLTEuMTQwOTMsMTUuMjYwNzMgLTE0LjQzNzEsMjYuNzA3MDkgLTI5LjY5NzgyLDI1LjU2NjE2Yy0xNS4yNjA3MywtMS4xNDA5MyAtMjYuNzA3MDksLTE0LjQzNzEgLTI1LjU2NjE2LC0yOS42OTc4MnoiIGZpbGw9InVybCgjY29sb3ItMSkiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSJOYU4iLz48cGF0aCBkPSJNMjM3LjkzNDE5LDIwNy42MzJjLTE1LjI2MDcyLC0xLjE0MDkzIC0yNi43MDcwOSwtMTQuNDM3MSAtMjUuNTY2MTYsLTI5LjY5NzgzYzEuMTQwOTMsLTE1LjI2MDczIDE0LjQzNzEsLTI2LjcwNzA5IDI5LjY5NzgzLC0yNS41NjYxN2MxNS4yNjA3MywxLjE0MDkzIDI2LjcwNzA5LDE0LjQzNzEgMjUuNTY2MTYsMjkuNjk3ODNjLTEuMTQwOTMsMTUuMjYwNzMgLTE0LjQzNzEsMjYuNzA3MDkgLTI5LjY5NzgzLDI1LjU2NjE3eiIgZmlsbD0idXJsKCNjb2xvci0yKSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9Ik5hTiIvPjxwYXRoIGQ9Ik0yMTMuNDI0LDE4MGMwLC0xNC42Nzc1MyAxMS44OTg0OSwtMjYuNTc2MDIgMjYuNTc2MDIsLTI2LjU3NjAyYzE0LjY3NzUzLDAgMjYuNTc2MDIsMTEuODk4NDkgMjYuNTc2MDIsMjYuNTc2MDJjMCwxNC42Nzc1MyAtMTEuODk4NDksMjYuNTc2MDIgLTI2LjU3NjAyLDI2LjU3NjAyYy0xNC42Nzc1MywwIC0yNi41NzYwMiwtMTEuODk4NDkgLTI2LjU3NjAyLC0yNi41NzYwMnoiIGZpbGw9Im5vbmUiIHN0cm9rZS1vcGFjaXR5PSIwLjQ0MzE0IiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS13aWR0aD0iMi41Ii8+PC9nPjwvZz48L3N2Zz4="; + const _spectrumIcon = "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI1NS42NTIwNCIgaGVpZ2h0PSI1NS42NTIwNCIgdmlld0JveD0iMCwwLDU1LjY1MjA0LDU1LjY1MjA0Ij48ZGVmcz48bGluZWFyR3JhZGllbnQgeDE9IjI0Mi4wNjU4NSIgeTE9IjE1Mi4zNjgwMSIgeDI9IjIzNy45MzQxOSIgeTI9IjIwNy42MzE5OSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIGlkPSJjb2xvci0xIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiMzMzNhZmYiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMzMzNhZmYiIHN0b3Atb3BhY2l0eT0iMCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IHgxPSIyMTIuMzY4MDIiIHkxPSIxNzcuOTM0MTciIHgyPSIyNjcuNjMxOTkiIHkyPSIxODIuMDY1ODMiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiBpZD0iY29sb3ItMiI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjMzZmZjMzIiBzdG9wLW9wYWNpdHk9IjAuNzAxOTYiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMzNmZmMzMiIHN0b3Atb3BhY2l0eT0iMCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMTIuMTc0LC0xNTIuMTczOTgpIj48ZyBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiPjxwYXRoIGQ9Ik0yMTIuMjkwOTEsMTgwYzAsLTE1LjMwMzMyIDEyLjQwNTc5LC0yNy43MDkxMSAyNy43MDkxMSwtMjcuNzA5MTFjMTUuMzAzMzIsMCAyNy43MDkxMSwxMi40MDU3OSAyNy43MDkxMSwyNy43MDkxMWMwLDE1LjMwMzMyIC0xMi40MDU3OSwyNy43MDkxMSAtMjcuNzA5MTEsMjcuNzA5MTFjLTE1LjMwMzMyLDAgLTI3LjcwOTExLC0xMi40MDU3OSAtMjcuNzA5MTEsLTI3LjcwOTExeiIgZmlsbD0iI2ZmMzMzMyIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9Ik5hTiIvPjxwYXRoIGQ9Ik0yMTIuMzY4MDMsMTc3LjkzNDE3YzEuMTQwOTMsLTE1LjI2MDczIDE0LjQzNzEsLTI2LjcwNzA5IDI5LjY5NzgyLC0yNS41NjYxNmMxNS4yNjA3MywxLjE0MDkzIDI2LjcwNzA5LDE0LjQzNzEgMjUuNTY2MTYsMjkuNjk3ODJjLTEuMTQwOTMsMTUuMjYwNzMgLTE0LjQzNzEsMjYuNzA3MDkgLTI5LjY5NzgyLDI1LjU2NjE2Yy0xNS4yNjA3MywtMS4xNDA5MyAtMjYuNzA3MDksLTE0LjQzNzEgLTI1LjU2NjE2LC0yOS42OTc4MnoiIGZpbGw9InVybCgjY29sb3ItMSkiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSJOYU4iLz48cGF0aCBkPSJNMjM3LjkzNDE5LDIwNy42MzJjLTE1LjI2MDcyLC0xLjE0MDkzIC0yNi43MDcwOSwtMTQuNDM3MSAtMjUuNTY2MTYsLTI5LjY5NzgzYzEuMTQwOTMsLTE1LjI2MDczIDE0LjQzNzEsLTI2LjcwNzA5IDI5LjY5NzgzLC0yNS41NjYxN2MxNS4yNjA3MywxLjE0MDkzIDI2LjcwNzA5LDE0LjQzNzEgMjUuNTY2MTYsMjkuNjk3ODNjLTEuMTQwOTMsMTUuMjYwNzMgLTE0LjQzNzEsMjYuNzA3MDkgLTI5LjY5NzgzLDI1LjU2NjE3eiIgZmlsbD0idXJsKCNjb2xvci0yKSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9Ik5hTiIvPjxwYXRoIGQ9Ik0yMTMuNDI0LDE4MGMwLC0xNC42Nzc1MyAxMS44OTg0OSwtMjYuNTc2MDIgMjYuNTc2MDIsLTI2LjU3NjAyYzE0LjY3NzUzLDAgMjYuNTc2MDIsMTEuODk4NDkgMjYuNTc2MDIsMjYuNTc2MDJjMCwxNC42Nzc1MyAtMTEuODk4NDksMjYuNTc2MDIgLTI2LjU3NjAyLDI2LjU3NjAyYy0xNC42Nzc1MywwIC0yNi41NzYwMiwtMTEuODk4NDkgLTI2LjU3NjAyLC0yNi41NzYwMnoiIGZpbGw9Im5vbmUiIHN0cm9rZS1vcGFjaXR5PSIwLjQ0MzE0IiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS13aWR0aD0iMi41Ii8+PC9nPjwvZz48L3N2Zz4="; const blockIcon = "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIxNi41ODk4NSIgaGVpZ2h0PSIxNi41ODk4NSIgdmlld0JveD0iMCwwLDE2LjU4OTg1LDE2LjU4OTg1Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjMxLjcwNTA4LC0xNzEuNzA1MDYpIj48ZyBzdHJva2Utd2lkdGg9IjAuNjI1IiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiPjxwYXRoIGQ9Ik0yMzIuMDQ1LDE4M2MwLC0yLjQ4NTI4IDIuMDE0NzIsLTQuNSA0LjUsLTQuNWMyLjQ4NTI4LDAgNC41LDIuMDE0NzIgNC41LDQuNWMwLDIuNDg1MjggLTIuMDE0NzIsNC41IC00LjUsNC41Yy0yLjQ4NTI4LDAgLTQuNSwtMi4wMTQ3MiAtNC41LC00LjV6IiBmaWxsPSIjNGM5N2ZmIiBzdHJva2U9IiMzMzczY2MiLz48cGF0aCBkPSJNMjQwLjA0NSwxNzIuNWMyLjQ4NTI4LDAgNC41LDIuMDE0NzIgNC41LDQuNWMwLDIuNDg1MjggLTIuMDE0NzIsNC41IC00LjUsNC41Yy0yLjQ4NTI4LDAgLTQuNSwtMi4wMTQ3MiAtNC41LC00LjVjMCwtMi40ODUyOCAyLjAxNDcyLC00LjUgNC41LC00LjV6IiBmaWxsPSIjZmY2NjgwIiBzdHJva2U9IiNmZjMzNTUiLz48cGF0aCBkPSJNMjQzLjQ1NSwxNzguNWMyLjQ4NTI4LDAgNC41LDIuMDE0NzIgNC41LDQuNWMwLDIuNDg1MjggLTIuMDE0NzIsNC41IC00LjUsNC41Yy0yLjQ4NTI4LDAgLTQuNSwtMi4wMTQ3MiAtNC41LC00LjVjMCwtMi40ODUyOCAyLjAxNDcyLC00LjUgNC41LC00LjV6IiBmaWxsPSIjZmZiZjAwIiBzdHJva2U9IiNjYzk5MDAiLz48cGF0aCBkPSJNMjMxLjcwNTA5LDE4OC4yOTQ5MnYtMTYuNTg5ODVoMTYuNTg5ODV2MTYuNTg5ODV6IiBmaWxsPSJub25lIiBzdHJva2U9Im5vbmUiLz48cGF0aCBkPSJNMjQzLjQ1NSwxNzguNWMyLjQ4NTI4LDAgNC41LDIuMDE0NzIgNC41LDQuNWMwLDIuNDg1MjggLTIuMDE0NzIsNC41IC00LjUsNC41Yy0yLjQ4NTI4LDAgLTQuNSwtMi4wMTQ3MiAtNC41LC00LjVjMCwtMi40ODUyOCAyLjAxNDcyLC00LjUgNC41LC00LjV6IiBmaWxsLW9wYWNpdHk9IjAuNTAxOTYiIGZpbGw9IiNmZmJmMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjUwMTk2IiBzdHJva2U9IiNjYzk5MDAiLz48cGF0aCBkPSJNMjMyLjA0NSwxODNjMCwtMi40ODUyOCAyLjAxNDcyLC00LjUgNC41LC00LjVjMi40ODUyOCwwIDQuNSwyLjAxNDcyIDQuNSw0LjVjMCwyLjQ4NTI4IC0yLjAxNDcyLDQuNSAtNC41LDQuNWMtMi40ODUyOCwwIC00LjUsLTIuMDE0NzIgLTQuNSwtNC41eiIgZmlsbC1vcGFjaXR5PSIwLjUwMTk2IiBmaWxsPSIjNGQ5N2ZmIiBzdHJva2Utb3BhY2l0eT0iMC41MDE5NiIgc3Ryb2tlPSIjMzM3M2NjIi8+PHBhdGggZD0iTTI0MC4wNDUsMTcyLjVjMi40ODUyOCwwIDQuNSwyLjAxNDcyIDQuNSw0LjVjMCwyLjQ4NTI4IC0yLjAxNDcyLDQuNSAtNC41LDQuNWMtMi40ODUyOCwwIC00LjUsLTIuMDE0NzIgLTQuNSwtNC41YzAsLTIuNDg1MjggMi4wMTQ3MiwtNC41IDQuNSwtNC41eiIgZmlsbC1vcGFjaXR5PSIwLjUwMTk2IiBmaWxsPSIjZmY2NjgwIiBzdHJva2Utb3BhY2l0eT0iMC41MDE5NiIgc3Ryb2tlPSIjZmYzMzU1Ii8+PC9nPjwvZz48L3N2Zz48IS0tcm90YXRpb25DZW50ZXI6OC4yOTQ5MTUwMDAwMDAwMDM6OC4yOTQ5MzUwMDAwMDAwMS0tPg=="; class Colors { getInfo() { @@ -421,7 +421,7 @@ { opcode: 'additiveBlend', blockType: Scratch.BlockType.REPORTER, - text: '[COL1] + [COL2]', + text: Scratch.translate('[COL1] + [COL2]'), arguments: { COL1: { type: Scratch.ArgumentType.COLOR, @@ -436,7 +436,7 @@ { opcode: 'subtractiveBlend', blockType: Scratch.BlockType.REPORTER, - text: '[COL1] - [COL2]', + text: Scratch.translate('[COL1] - [COL2]'), arguments: { COL1: { type: Scratch.ArgumentType.COLOR, @@ -451,7 +451,7 @@ { opcode: 'multiplicativeBlend', blockType: Scratch.BlockType.REPORTER, - text: '[COL1] * [COL2]', + text: Scratch.translate('[COL1] * [COL2]'), arguments: { COL1: { type: Scratch.ArgumentType.COLOR, @@ -466,7 +466,7 @@ { opcode: 'divisingBlend', blockType: Scratch.BlockType.REPORTER, - text: '[COL1] / [COL2]', + text: Scratch.translate('[COL1] / [COL2]'), arguments: { COL1: { type: Scratch.ArgumentType.COLOR, From 780bd79a81579e8053b68974fe9e1fbf0dff6bc2 Mon Sep 17 00:00:00 2001 From: LincolnX <118082287+LincolnXGames@users.noreply.github.com> Date: Fri, 26 Dec 2025 21:58:10 -0600 Subject: [PATCH 09/12] Update colors.svg --- images/LincolnX/colors.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/images/LincolnX/colors.svg b/images/LincolnX/colors.svg index 66b6046557..5ddb8641e9 100644 --- a/images/LincolnX/colors.svg +++ b/images/LincolnX/colors.svg @@ -1 +1 @@ - + From 7f2a68dad68b23de1862f62e74005f8c5ffdd11f Mon Sep 17 00:00:00 2001 From: LincolnX <118082287+LincolnXGames@users.noreply.github.com> Date: Fri, 26 Dec 2025 21:58:49 -0600 Subject: [PATCH 10/12] Update colors.svg --- images/LincolnX/colors.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/images/LincolnX/colors.svg b/images/LincolnX/colors.svg index 5ddb8641e9..31de52cc8a 100644 --- a/images/LincolnX/colors.svg +++ b/images/LincolnX/colors.svg @@ -1 +1 @@ - + From ed406902efda85922c71fbc5af2f3abc2bfea951 Mon Sep 17 00:00:00 2001 From: LincolnX <118082287+LincolnXGames@users.noreply.github.com> Date: Fri, 26 Dec 2025 21:59:22 -0600 Subject: [PATCH 11/12] Update colors.svg --- images/LincolnX/colors.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/images/LincolnX/colors.svg b/images/LincolnX/colors.svg index 31de52cc8a..5258c4da87 100644 --- a/images/LincolnX/colors.svg +++ b/images/LincolnX/colors.svg @@ -1 +1 @@ - + From ebd68f3f6b086991fcd84f6d6679b6e93f0b6eb9 Mon Sep 17 00:00:00 2001 From: LincolnX <118082287+LincolnXGames@users.noreply.github.com> Date: Fri, 26 Dec 2025 22:29:02 -0600 Subject: [PATCH 12/12] prettify colors.js --- extensions/LincolnX/colors.js | 724 +++++++++++++++++++--------------- 1 file changed, 405 insertions(+), 319 deletions(-) diff --git a/extensions/LincolnX/colors.js b/extensions/LincolnX/colors.js index 3f9f178f76..c5b5600fe6 100644 --- a/extensions/LincolnX/colors.js +++ b/extensions/LincolnX/colors.js @@ -4,61 +4,64 @@ // By: LincolnX // License: MPL-2.0 -(function(Scratch) { - 'use strict'; +(function (Scratch) { + "use strict"; const { abs, round, floor, sqrt } = Math; const toDec = (hex) => parseInt(hex, 16); const toHex = (dec) => dec.toString(16); - const limitHex = (hex, mi, ma) => toHex(Math.min(Math.max(toDec(hex), mi), ma)); + const limitHex = (hex, mi, ma) => + toHex(Math.min(Math.max(toDec(hex), mi), ma)); const clamp = (n, mi, ma) => Math.min(Math.max(n, mi), ma); - const fixHex = (hex) => limitHex(hex, 0, 255).padStart(2, '0'); + const fixHex = (hex) => limitHex(hex, 0, 255).padStart(2, "0"); const toFixHex = (dec) => fixHex(dec.toString(16)); const lerp = (a, b, t) => a + (b - a) * t; function interpolateHexColorsHsv(hex1, hex2, t) { const hsv1 = hexToHsv(hex1); const hsv2 = hexToHsv(hex2); - t = clamp(t, 0, 1) + t = clamp(t, 0, 1); // Handle hue interpolation for the shortest path around the color wheel let h1 = hsv1.h; let h2 = hsv2.h; let hueDiff = h2 - h1; if (hueDiff > 180) { - h1 += 360; + h1 += 360; } else if (hueDiff < -180) { - h2 += 360; + h2 += 360; } const h = h1 + t * (h2 - h1); const s = hsv1.s + t * (hsv2.s - hsv1.s); const v = hsv1.v + t * (hsv2.v - hsv1.v); - + return hsvToHex({ h, s, v }); } function hexToRgb(hex) { - let r = 0, g = 0, b = 0; + let r = 0, + g = 0, + b = 0; // Handle 3-digit shorthand if (hex.length === 4) { - r = parseInt(hex[1] + hex[1], 16); - g = parseInt(hex[2] + hex[2], 16); - b = parseInt(hex[3] + hex[3], 16); + r = parseInt(hex[1] + hex[1], 16); + g = parseInt(hex[2] + hex[2], 16); + b = parseInt(hex[3] + hex[3], 16); } else if (hex.length === 7) { - r = parseInt(hex.substring(1, 3), 16); - g = parseInt(hex.substring(3, 5), 16); - b = parseInt(hex.substring(5, 7), 16); + r = parseInt(hex.substring(1, 3), 16); + g = parseInt(hex.substring(3, 5), 16); + b = parseInt(hex.substring(5, 7), 16); } return { r, g, b }; } function rgbToHex(rgb) { - const makeHex = c => Math.round(c).toString(16).padStart(2, '0'); + const makeHex = (c) => Math.round(c).toString(16).padStart(2, "0"); return `#${makeHex(rgb.r)}${makeHex(rgb.g)}${makeHex(rgb.b)}`; } function hsvToRgb(h, s, v) { var r, g, b, i, f, p, q, t; if (arguments.length === 1) { - s = h.s, v = h.v, h = h.h; + ((s = h.s), (v = h.v), (h = h.h)); } h /= 360; s /= 100; @@ -69,44 +72,68 @@ q = v * (1 - f * s); t = v * (1 - (1 - f) * s); switch (i % 6) { - case 0: r = v, g = t, b = p; break; - case 1: r = q, g = v, b = p; break; - case 2: r = p, g = v, b = t; break; - case 3: r = p, g = q, b = v; break; - case 4: r = t, g = p, b = v; break; - case 5: r = v, g = p, b = q; break; + case 0: + ((r = v), (g = t), (b = p)); + break; + case 1: + ((r = q), (g = v), (b = p)); + break; + case 2: + ((r = p), (g = v), (b = t)); + break; + case 3: + ((r = p), (g = q), (b = v)); + break; + case 4: + ((r = t), (g = p), (b = v)); + break; + case 5: + ((r = v), (g = p), (b = q)); + break; } return { - r: round(r * 255), - g: round(g * 255), - b: round(b * 255) + r: round(r * 255), + g: round(g * 255), + b: round(b * 255), }; } function rgbToHsv(r, g, b) { if (arguments.length === 1) { - g = r.g, b = r.b, r = r.r; + ((g = r.g), (b = r.b), (r = r.r)); } - var max = Math.max(r, g, b), min = Math.min(r, g, b), - d = max - min, - h, - s = (max === 0 ? 0 : d / max), - v = max / 255; + var max = Math.max(r, g, b), + min = Math.min(r, g, b), + d = max - min, + h, + s = max === 0 ? 0 : d / max, + v = max / 255; switch (max) { - case min: h = 0; break; - case r: h = (g - b) + d * (g < b ? 6: 0); h /= 6 * d; break; - case g: h = (b - r) + d * 2; h /= 6 * d; break; - case b: h = (r - g) + d * 4; h /= 6 * d; break; + case min: + h = 0; + break; + case r: + h = g - b + d * (g < b ? 6 : 0); + h /= 6 * d; + break; + case g: + h = b - r + d * 2; + h /= 6 * d; + break; + case b: + h = r - g + d * 4; + h /= 6 * d; + break; } return { - h: h * 360, - s: s * 100, - v: v * 100 + h: h * 360, + s: s * 100, + v: v * 100, }; } const hexToHsv = (hex) => rgbToHsv(hexToRgb(hex)); function hsvToHex(h, s, v) { if (arguments.length === 1) { - s = h.s, v = h.v, h = h.h; + ((s = h.s), (v = h.v), (h = h.h)); } return rgbToHex(hsvToRgb(h, s, v)); } @@ -121,27 +148,25 @@ } else { const q = l < 0.5 ? l * (1 + s) : l + s - l * s; const p = 2 * l - q; - r = hueToRgb(p, q, h + 1/3); + r = hueToRgb(p, q, h + 1 / 3); g = hueToRgb(p, q, h); - b = hueToRgb(p, q, h - 1/3); + b = hueToRgb(p, q, h - 1 / 3); } - return {r: round(r * 255), g: round(g * 255), b: round(b * 255)}; + return { r: round(r * 255), g: round(g * 255), b: round(b * 255) }; } function hueToRgb(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; + 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; } function channelToLinear(c) { c /= 255; - return c <= 0.03928 - ? c / 12.92 - : ((c + 0.055) / 1.055) ** 2.4; + return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4; } function relativeLuminance(hex) { @@ -160,8 +185,6 @@ return (light + 0.05) / (dark + 0.05); } - - function distanceBetweenHexColorsDeltaE2000(hex1, hex2) { // convert Hex to RGB const rgb1 = hexToRgb(hex1); @@ -184,9 +207,9 @@ g /= 255; b /= 255; - r = (r > 0.04045) ? ((r + 0.055) / 1.055) ** 2.4 : r / 12.92; - g = (g > 0.04045) ? ((g + 0.055) / 1.055) ** 2.4 : g / 12.92; - b = (b > 0.04045) ? ((b + 0.055) / 1.055) ** 2.4 : b / 12.92; + r = r > 0.04045 ? ((r + 0.055) / 1.055) ** 2.4 : r / 12.92; + g = g > 0.04045 ? ((g + 0.055) / 1.055) ** 2.4 : g / 12.92; + b = b > 0.04045 ? ((b + 0.055) / 1.055) ** 2.4 : b / 12.92; r *= 100; g *= 100; @@ -204,39 +227,44 @@ function xyzToLab(x, y, z) { // D65 white point values const refX = 95.047; - const refY = 100.000; + const refY = 100.0; const refZ = 108.883; x /= refX; y /= refY; z /= refZ; - x = (x > 0.008856) ? x ** (1/3) : (7.787 * x) + 16/116; - y = (y > 0.008856) ? y ** (1/3) : (7.787 * y) + 16/116; - z = (z > 0.008856) ? z ** (1/3) : (7.787 * z) + 16/116; + x = x > 0.008856 ? x ** (1 / 3) : 7.787 * x + 16 / 116; + y = y > 0.008856 ? y ** (1 / 3) : 7.787 * y + 16 / 116; + z = z > 0.008856 ? z ** (1 / 3) : 7.787 * z + 16 / 116; - const L = (116 * y) - 16; + const L = 116 * y - 16; const a = 500 * (x - y); const b = 200 * (y - z); return { L, a, b }; } - // calculates the Delta E 2000 difference between two Lab colors. // based on the implementation notes from Sharma et al.. function deltaE2000(lab1, lab2) { - const kL = 1.0, kC = 1.0, kH = 1.0; // parametric factors often set to 1.0 + const kL = 1.0, + kC = 1.0, + kH = 1.0; // parametric factors often set to 1.0 const deg2rad = Math.PI / 180; const rad2deg = 180 / Math.PI; // extract Lab values - const L1 = lab1.L, a1 = lab1.a, b1 = lab1.b; - const L2 = lab2.L, a2 = lab2.a, b2 = lab2.b; + const L1 = lab1.L, + a1 = lab1.a, + b1 = lab1.b; + const L2 = lab2.L, + a2 = lab2.a, + b2 = lab2.b; // calculate C*ab values (Chroma) - const C1 = sqrt(a1*a1 + b1*b1); - const C2 = sqrt(a2*a2 + b2*b2); + const C1 = sqrt(a1 * a1 + b1 * b1); + const C2 = sqrt(a2 * a2 + b2 * b2); const CBar = (C1 + C2) / 2.0; // calculate G (chroma correction factor) @@ -246,33 +274,35 @@ // calculate a' and C' (lightness corrected a* and new chroma) const a1Prime = a1 * (1 + G); const a2Prime = a2 * (1 + G); - const C1Prime = sqrt(a1Prime*a1Prime + b1*b1); - const C2Prime = sqrt(a2Prime*a2Prime + b2*b2); + const C1Prime = sqrt(a1Prime * a1Prime + b1 * b1); + const C2Prime = sqrt(a2Prime * a2Prime + b2 * b2); const CBarPrime = (C1Prime + C2Prime) / 2.0; const DeltaCPrime = C2Prime - C1Prime; // calculate h' (hue angle) - const h1Prime = (Math.atan2(b1, a1Prime) * rad2deg); - const h2Prime = (Math.atan2(b2, a2Prime) * rad2deg); + const h1Prime = Math.atan2(b1, a1Prime) * rad2deg; + const h2Prime = Math.atan2(b2, a2Prime) * rad2deg; // normalize hue angles to 0-360 range - const normalizedH1 = h1Prime >= 0 ? h1Prime : (h1Prime + 360); - const normalizedH2 = h2Prime >= 0 ? h2Prime : (h2Prime + 360); + const normalizedH1 = h1Prime >= 0 ? h1Prime : h1Prime + 360; + const normalizedH2 = h2Prime >= 0 ? h2Prime : h2Prime + 360; // calculate Delta h' (hue difference) and Delta H' (weighted hue difference) let DeltaHPrime; if (C1Prime * C2Prime === 0) { - DeltaHPrime = 0; // if one chroma is zero, hue difference is meaningless. + DeltaHPrime = 0; // if one chroma is zero, hue difference is meaningless. } else if (abs(normalizedH1 - normalizedH2) <= 180) { - DeltaHPrime = normalizedH2 - normalizedH1; - } else if ((normalizedH2 - normalizedH1) > 180) { - DeltaHPrime = (normalizedH2 - normalizedH1) - 360; - } else { // (h2 - h1) < -180 - DeltaHPrime = (normalizedH2 - normalizedH1) + 360; + DeltaHPrime = normalizedH2 - normalizedH1; + } else if (normalizedH2 - normalizedH1 > 180) { + DeltaHPrime = normalizedH2 - normalizedH1 - 360; + } else { + // (h2 - h1) < -180 + DeltaHPrime = normalizedH2 - normalizedH1 + 360; } - + // convert Delta H' to a metric difference (ΔH') - const DeltaSmallHPrime = 2 * sqrt(C1Prime * C2Prime) * Math.sin(DeltaHPrime * deg2rad / 2.0); + const DeltaSmallHPrime = + 2 * sqrt(C1Prime * C2Prime) * Math.sin((DeltaHPrime * deg2rad) / 2.0); // calculate Delta L' const DeltaLPrime = L2 - L1; @@ -280,36 +310,42 @@ // calculate Average H' (HBarPrime) let HBarPrime; if (C1Prime * C2Prime === 0) { - HBarPrime = normalizedH1 + normalizedH2; // use sum as average if one is indeterminate + HBarPrime = normalizedH1 + normalizedH2; // use sum as average if one is indeterminate } else if (abs(normalizedH1 - normalizedH2) > 180) { - if ((normalizedH1 + normalizedH2) < 360) { - HBarPrime = (normalizedH1 + normalizedH2 + 360) / 2.0; - } else { - HBarPrime = (normalizedH1 + normalizedH2 - 360) / 2.0; - } + if (normalizedH1 + normalizedH2 < 360) { + HBarPrime = (normalizedH1 + normalizedH2 + 360) / 2.0; + } else { + HBarPrime = (normalizedH1 + normalizedH2 - 360) / 2.0; + } } else { - HBarPrime = (normalizedH1 + normalizedH2) / 2.0; + HBarPrime = (normalizedH1 + normalizedH2) / 2.0; } // calculate T (hue weighting function) - const T = 1.0 - 0.17 * Math.cos((HBarPrime - 30.0) * deg2rad) - + 0.24 * Math.cos((2.0 * HBarPrime) * deg2rad) - + 0.32 * Math.cos((3.0 * HBarPrime + 6.0) * deg2rad) - - 0.20 * Math.cos((4.0 * HBarPrime - 63.0) * deg2rad); + const T = + 1.0 - + 0.17 * Math.cos((HBarPrime - 30.0) * deg2rad) + + 0.24 * Math.cos(2.0 * HBarPrime * deg2rad) + + 0.32 * Math.cos((3.0 * HBarPrime + 6.0) * deg2rad) - + 0.2 * Math.cos((4.0 * HBarPrime - 63.0) * deg2rad); // calculate SL, SC, SH (weighting functions) - const SL = 1.0 + ((0.015 * ((HBarPrime - 27.5) ** 2) / (20.0 + ((HBarPrime - 27.5) ** 2)))); + const SL = + 1.0 + + (0.015 * (HBarPrime - 27.5) ** 2) / (20.0 + (HBarPrime - 27.5) ** 2); const SC = 1.0 + 0.045 * CBarPrime; const SH = 1.0 + 0.015 * CBarPrime * T; // calculate RT (rotation term) const CBarPrimePow7 = CBarPrime ** 7; - const RT = -2.0 * Math.sin(HBarPrime * deg2rad) - * sqrt(CBarPrimePow7 / (CBarPrimePow7 + 6103515625.0)); + const RT = + -2.0 * + Math.sin(HBarPrime * deg2rad) * + sqrt(CBarPrimePow7 / (CBarPrimePow7 + 6103515625.0)); // calculate the final Delta E 2000 value - const deltaE =sqrt( - (DeltaLPrime / (kL * SL)) ** 2 + + const deltaE = sqrt( + (DeltaLPrime / (kL * SL)) ** 2 + (DeltaCPrime / (kC * SC)) ** 2 + (DeltaSmallHPrime / (kH * SH)) ** 2 + RT * (DeltaCPrime / (kC * SC)) * (DeltaSmallHPrime / (kH * SH)) @@ -317,429 +353,436 @@ return deltaE; } - - const _spectrumIcon = "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI1NS42NTIwNCIgaGVpZ2h0PSI1NS42NTIwNCIgdmlld0JveD0iMCwwLDU1LjY1MjA0LDU1LjY1MjA0Ij48ZGVmcz48bGluZWFyR3JhZGllbnQgeDE9IjI0Mi4wNjU4NSIgeTE9IjE1Mi4zNjgwMSIgeDI9IjIzNy45MzQxOSIgeTI9IjIwNy42MzE5OSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIGlkPSJjb2xvci0xIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiMzMzNhZmYiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMzMzNhZmYiIHN0b3Atb3BhY2l0eT0iMCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IHgxPSIyMTIuMzY4MDIiIHkxPSIxNzcuOTM0MTciIHgyPSIyNjcuNjMxOTkiIHkyPSIxODIuMDY1ODMiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiBpZD0iY29sb3ItMiI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjMzZmZjMzIiBzdG9wLW9wYWNpdHk9IjAuNzAxOTYiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMzNmZmMzMiIHN0b3Atb3BhY2l0eT0iMCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMTIuMTc0LC0xNTIuMTczOTgpIj48ZyBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiPjxwYXRoIGQ9Ik0yMTIuMjkwOTEsMTgwYzAsLTE1LjMwMzMyIDEyLjQwNTc5LC0yNy43MDkxMSAyNy43MDkxMSwtMjcuNzA5MTFjMTUuMzAzMzIsMCAyNy43MDkxMSwxMi40MDU3OSAyNy43MDkxMSwyNy43MDkxMWMwLDE1LjMwMzMyIC0xMi40MDU3OSwyNy43MDkxMSAtMjcuNzA5MTEsMjcuNzA5MTFjLTE1LjMwMzMyLDAgLTI3LjcwOTExLC0xMi40MDU3OSAtMjcuNzA5MTEsLTI3LjcwOTExeiIgZmlsbD0iI2ZmMzMzMyIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9Ik5hTiIvPjxwYXRoIGQ9Ik0yMTIuMzY4MDMsMTc3LjkzNDE3YzEuMTQwOTMsLTE1LjI2MDczIDE0LjQzNzEsLTI2LjcwNzA5IDI5LjY5NzgyLC0yNS41NjYxNmMxNS4yNjA3MywxLjE0MDkzIDI2LjcwNzA5LDE0LjQzNzEgMjUuNTY2MTYsMjkuNjk3ODJjLTEuMTQwOTMsMTUuMjYwNzMgLTE0LjQzNzEsMjYuNzA3MDkgLTI5LjY5NzgyLDI1LjU2NjE2Yy0xNS4yNjA3MywtMS4xNDA5MyAtMjYuNzA3MDksLTE0LjQzNzEgLTI1LjU2NjE2LC0yOS42OTc4MnoiIGZpbGw9InVybCgjY29sb3ItMSkiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSJOYU4iLz48cGF0aCBkPSJNMjM3LjkzNDE5LDIwNy42MzJjLTE1LjI2MDcyLC0xLjE0MDkzIC0yNi43MDcwOSwtMTQuNDM3MSAtMjUuNTY2MTYsLTI5LjY5NzgzYzEuMTQwOTMsLTE1LjI2MDczIDE0LjQzNzEsLTI2LjcwNzA5IDI5LjY5NzgzLC0yNS41NjYxN2MxNS4yNjA3MywxLjE0MDkzIDI2LjcwNzA5LDE0LjQzNzEgMjUuNTY2MTYsMjkuNjk3ODNjLTEuMTQwOTMsMTUuMjYwNzMgLTE0LjQzNzEsMjYuNzA3MDkgLTI5LjY5NzgzLDI1LjU2NjE3eiIgZmlsbD0idXJsKCNjb2xvci0yKSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9Ik5hTiIvPjxwYXRoIGQ9Ik0yMTMuNDI0LDE4MGMwLC0xNC42Nzc1MyAxMS44OTg0OSwtMjYuNTc2MDIgMjYuNTc2MDIsLTI2LjU3NjAyYzE0LjY3NzUzLDAgMjYuNTc2MDIsMTEuODk4NDkgMjYuNTc2MDIsMjYuNTc2MDJjMCwxNC42Nzc1MyAtMTEuODk4NDksMjYuNTc2MDIgLTI2LjU3NjAyLDI2LjU3NjAyYy0xNC42Nzc1MywwIC0yNi41NzYwMiwtMTEuODk4NDkgLTI2LjU3NjAyLC0yNi41NzYwMnoiIGZpbGw9Im5vbmUiIHN0cm9rZS1vcGFjaXR5PSIwLjQ0MzE0IiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS13aWR0aD0iMi41Ii8+PC9nPjwvZz48L3N2Zz4="; - const blockIcon = "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIxNi41ODk4NSIgaGVpZ2h0PSIxNi41ODk4NSIgdmlld0JveD0iMCwwLDE2LjU4OTg1LDE2LjU4OTg1Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjMxLjcwNTA4LC0xNzEuNzA1MDYpIj48ZyBzdHJva2Utd2lkdGg9IjAuNjI1IiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiPjxwYXRoIGQ9Ik0yMzIuMDQ1LDE4M2MwLC0yLjQ4NTI4IDIuMDE0NzIsLTQuNSA0LjUsLTQuNWMyLjQ4NTI4LDAgNC41LDIuMDE0NzIgNC41LDQuNWMwLDIuNDg1MjggLTIuMDE0NzIsNC41IC00LjUsNC41Yy0yLjQ4NTI4LDAgLTQuNSwtMi4wMTQ3MiAtNC41LC00LjV6IiBmaWxsPSIjNGM5N2ZmIiBzdHJva2U9IiMzMzczY2MiLz48cGF0aCBkPSJNMjQwLjA0NSwxNzIuNWMyLjQ4NTI4LDAgNC41LDIuMDE0NzIgNC41LDQuNWMwLDIuNDg1MjggLTIuMDE0NzIsNC41IC00LjUsNC41Yy0yLjQ4NTI4LDAgLTQuNSwtMi4wMTQ3MiAtNC41LC00LjVjMCwtMi40ODUyOCAyLjAxNDcyLC00LjUgNC41LC00LjV6IiBmaWxsPSIjZmY2NjgwIiBzdHJva2U9IiNmZjMzNTUiLz48cGF0aCBkPSJNMjQzLjQ1NSwxNzguNWMyLjQ4NTI4LDAgNC41LDIuMDE0NzIgNC41LDQuNWMwLDIuNDg1MjggLTIuMDE0NzIsNC41IC00LjUsNC41Yy0yLjQ4NTI4LDAgLTQuNSwtMi4wMTQ3MiAtNC41LC00LjVjMCwtMi40ODUyOCAyLjAxNDcyLC00LjUgNC41LC00LjV6IiBmaWxsPSIjZmZiZjAwIiBzdHJva2U9IiNjYzk5MDAiLz48cGF0aCBkPSJNMjMxLjcwNTA5LDE4OC4yOTQ5MnYtMTYuNTg5ODVoMTYuNTg5ODV2MTYuNTg5ODV6IiBmaWxsPSJub25lIiBzdHJva2U9Im5vbmUiLz48cGF0aCBkPSJNMjQzLjQ1NSwxNzguNWMyLjQ4NTI4LDAgNC41LDIuMDE0NzIgNC41LDQuNWMwLDIuNDg1MjggLTIuMDE0NzIsNC41IC00LjUsNC41Yy0yLjQ4NTI4LDAgLTQuNSwtMi4wMTQ3MiAtNC41LC00LjVjMCwtMi40ODUyOCAyLjAxNDcyLC00LjUgNC41LC00LjV6IiBmaWxsLW9wYWNpdHk9IjAuNTAxOTYiIGZpbGw9IiNmZmJmMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjUwMTk2IiBzdHJva2U9IiNjYzk5MDAiLz48cGF0aCBkPSJNMjMyLjA0NSwxODNjMCwtMi40ODUyOCAyLjAxNDcyLC00LjUgNC41LC00LjVjMi40ODUyOCwwIDQuNSwyLjAxNDcyIDQuNSw0LjVjMCwyLjQ4NTI4IC0yLjAxNDcyLDQuNSAtNC41LDQuNWMtMi40ODUyOCwwIC00LjUsLTIuMDE0NzIgLTQuNSwtNC41eiIgZmlsbC1vcGFjaXR5PSIwLjUwMTk2IiBmaWxsPSIjNGQ5N2ZmIiBzdHJva2Utb3BhY2l0eT0iMC41MDE5NiIgc3Ryb2tlPSIjMzM3M2NjIi8+PHBhdGggZD0iTTI0MC4wNDUsMTcyLjVjMi40ODUyOCwwIDQuNSwyLjAxNDcyIDQuNSw0LjVjMCwyLjQ4NTI4IC0yLjAxNDcyLDQuNSAtNC41LDQuNWMtMi40ODUyOCwwIC00LjUsLTIuMDE0NzIgLTQuNSwtNC41YzAsLTIuNDg1MjggMi4wMTQ3MiwtNC41IDQuNSwtNC41eiIgZmlsbC1vcGFjaXR5PSIwLjUwMTk2IiBmaWxsPSIjZmY2NjgwIiBzdHJva2Utb3BhY2l0eT0iMC41MDE5NiIgc3Ryb2tlPSIjZmYzMzU1Ii8+PC9nPjwvZz48L3N2Zz48IS0tcm90YXRpb25DZW50ZXI6OC4yOTQ5MTUwMDAwMDAwMDM6OC4yOTQ5MzUwMDAwMDAwMS0tPg=="; + + const _spectrumIcon = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSI1NS42NTIwNCIgaGVpZ2h0PSI1NS42NTIwNCIgdmlld0JveD0iMCwwLDU1LjY1MjA0LDU1LjY1MjA0Ij48ZGVmcz48bGluZWFyR3JhZGllbnQgeDE9IjI0Mi4wNjU4NSIgeTE9IjE1Mi4zNjgwMSIgeDI9IjIzNy45MzQxOSIgeTI9IjIwNy42MzE5OSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIGlkPSJjb2xvci0xIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiMzMzNhZmYiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMzMzNhZmYiIHN0b3Atb3BhY2l0eT0iMCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IHgxPSIyMTIuMzY4MDIiIHkxPSIxNzcuOTM0MTciIHgyPSIyNjcuNjMxOTkiIHkyPSIxODIuMDY1ODMiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiBpZD0iY29sb3ItMiI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjMzZmZjMzIiBzdG9wLW9wYWNpdHk9IjAuNzAxOTYiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMzNmZmMzMiIHN0b3Atb3BhY2l0eT0iMCIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMTIuMTc0LC0xNTIuMTczOTgpIj48ZyBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiPjxwYXRoIGQ9Ik0yMTIuMjkwOTEsMTgwYzAsLTE1LjMwMzMyIDEyLjQwNTc5LC0yNy43MDkxMSAyNy43MDkxMSwtMjcuNzA5MTFjMTUuMzAzMzIsMCAyNy43MDkxMSwxMi40MDU3OSAyNy43MDkxMSwyNy43MDkxMWMwLDE1LjMwMzMyIC0xMi40MDU3OSwyNy43MDkxMSAtMjcuNzA5MTEsMjcuNzA5MTFjLTE1LjMwMzMyLDAgLTI3LjcwOTExLC0xMi40MDU3OSAtMjcuNzA5MTEsLTI3LjcwOTExeiIgZmlsbD0iI2ZmMzMzMyIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9Ik5hTiIvPjxwYXRoIGQ9Ik0yMTIuMzY4MDMsMTc3LjkzNDE3YzEuMTQwOTMsLTE1LjI2MDczIDE0LjQzNzEsLTI2LjcwNzA5IDI5LjY5NzgyLC0yNS41NjYxNmMxNS4yNjA3MywxLjE0MDkzIDI2LjcwNzA5LDE0LjQzNzEgMjUuNTY2MTYsMjkuNjk3ODJjLTEuMTQwOTMsMTUuMjYwNzMgLTE0LjQzNzEsMjYuNzA3MDkgLTI5LjY5NzgyLDI1LjU2NjE2Yy0xNS4yNjA3MywtMS4xNDA5MyAtMjYuNzA3MDksLTE0LjQzNzEgLTI1LjU2NjE2LC0yOS42OTc4MnoiIGZpbGw9InVybCgjY29sb3ItMSkiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSJOYU4iLz48cGF0aCBkPSJNMjM3LjkzNDE5LDIwNy42MzJjLTE1LjI2MDcyLC0xLjE0MDkzIC0yNi43MDcwOSwtMTQuNDM3MSAtMjUuNTY2MTYsLTI5LjY5NzgzYzEuMTQwOTMsLTE1LjI2MDczIDE0LjQzNzEsLTI2LjcwNzA5IDI5LjY5NzgzLC0yNS41NjYxN2MxNS4yNjA3MywxLjE0MDkzIDI2LjcwNzA5LDE0LjQzNzEgMjUuNTY2MTYsMjkuNjk3ODNjLTEuMTQwOTMsMTUuMjYwNzMgLTE0LjQzNzEsMjYuNzA3MDkgLTI5LjY5NzgzLDI1LjU2NjE3eiIgZmlsbD0idXJsKCNjb2xvci0yKSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9Ik5hTiIvPjxwYXRoIGQ9Ik0yMTMuNDI0LDE4MGMwLC0xNC42Nzc1MyAxMS44OTg0OSwtMjYuNTc2MDIgMjYuNTc2MDIsLTI2LjU3NjAyYzE0LjY3NzUzLDAgMjYuNTc2MDIsMTEuODk4NDkgMjYuNTc2MDIsMjYuNTc2MDJjMCwxNC42Nzc1MyAtMTEuODk4NDksMjYuNTc2MDIgLTI2LjU3NjAyLDI2LjU3NjAyYy0xNC42Nzc1MywwIC0yNi41NzYwMiwtMTEuODk4NDkgLTI2LjU3NjAyLC0yNi41NzYwMnoiIGZpbGw9Im5vbmUiIHN0cm9rZS1vcGFjaXR5PSIwLjQ0MzE0IiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS13aWR0aD0iMi41Ii8+PC9nPjwvZz48L3N2Zz4="; + const blockIcon = + "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIxNi41ODk4NSIgaGVpZ2h0PSIxNi41ODk4NSIgdmlld0JveD0iMCwwLDE2LjU4OTg1LDE2LjU4OTg1Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjMxLjcwNTA4LC0xNzEuNzA1MDYpIj48ZyBzdHJva2Utd2lkdGg9IjAuNjI1IiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiPjxwYXRoIGQ9Ik0yMzIuMDQ1LDE4M2MwLC0yLjQ4NTI4IDIuMDE0NzIsLTQuNSA0LjUsLTQuNWMyLjQ4NTI4LDAgNC41LDIuMDE0NzIgNC41LDQuNWMwLDIuNDg1MjggLTIuMDE0NzIsNC41IC00LjUsNC41Yy0yLjQ4NTI4LDAgLTQuNSwtMi4wMTQ3MiAtNC41LC00LjV6IiBmaWxsPSIjNGM5N2ZmIiBzdHJva2U9IiMzMzczY2MiLz48cGF0aCBkPSJNMjQwLjA0NSwxNzIuNWMyLjQ4NTI4LDAgNC41LDIuMDE0NzIgNC41LDQuNWMwLDIuNDg1MjggLTIuMDE0NzIsNC41IC00LjUsNC41Yy0yLjQ4NTI4LDAgLTQuNSwtMi4wMTQ3MiAtNC41LC00LjVjMCwtMi40ODUyOCAyLjAxNDcyLC00LjUgNC41LC00LjV6IiBmaWxsPSIjZmY2NjgwIiBzdHJva2U9IiNmZjMzNTUiLz48cGF0aCBkPSJNMjQzLjQ1NSwxNzguNWMyLjQ4NTI4LDAgNC41LDIuMDE0NzIgNC41LDQuNWMwLDIuNDg1MjggLTIuMDE0NzIsNC41IC00LjUsNC41Yy0yLjQ4NTI4LDAgLTQuNSwtMi4wMTQ3MiAtNC41LC00LjVjMCwtMi40ODUyOCAyLjAxNDcyLC00LjUgNC41LC00LjV6IiBmaWxsPSIjZmZiZjAwIiBzdHJva2U9IiNjYzk5MDAiLz48cGF0aCBkPSJNMjMxLjcwNTA5LDE4OC4yOTQ5MnYtMTYuNTg5ODVoMTYuNTg5ODV2MTYuNTg5ODV6IiBmaWxsPSJub25lIiBzdHJva2U9Im5vbmUiLz48cGF0aCBkPSJNMjQzLjQ1NSwxNzguNWMyLjQ4NTI4LDAgNC41LDIuMDE0NzIgNC41LDQuNWMwLDIuNDg1MjggLTIuMDE0NzIsNC41IC00LjUsNC41Yy0yLjQ4NTI4LDAgLTQuNSwtMi4wMTQ3MiAtNC41LC00LjVjMCwtMi40ODUyOCAyLjAxNDcyLC00LjUgNC41LC00LjV6IiBmaWxsLW9wYWNpdHk9IjAuNTAxOTYiIGZpbGw9IiNmZmJmMDAiIHN0cm9rZS1vcGFjaXR5PSIwLjUwMTk2IiBzdHJva2U9IiNjYzk5MDAiLz48cGF0aCBkPSJNMjMyLjA0NSwxODNjMCwtMi40ODUyOCAyLjAxNDcyLC00LjUgNC41LC00LjVjMi40ODUyOCwwIDQuNSwyLjAxNDcyIDQuNSw0LjVjMCwyLjQ4NTI4IC0yLjAxNDcyLDQuNSAtNC41LDQuNWMtMi40ODUyOCwwIC00LjUsLTIuMDE0NzIgLTQuNSwtNC41eiIgZmlsbC1vcGFjaXR5PSIwLjUwMTk2IiBmaWxsPSIjNGQ5N2ZmIiBzdHJva2Utb3BhY2l0eT0iMC41MDE5NiIgc3Ryb2tlPSIjMzM3M2NjIi8+PHBhdGggZD0iTTI0MC4wNDUsMTcyLjVjMi40ODUyOCwwIDQuNSwyLjAxNDcyIDQuNSw0LjVjMCwyLjQ4NTI4IC0yLjAxNDcyLDQuNSAtNC41LDQuNWMtMi40ODUyOCwwIC00LjUsLTIuMDE0NzIgLTQuNSwtNC41YzAsLTIuNDg1MjggMi4wMTQ3MiwtNC41IDQuNSwtNC41eiIgZmlsbC1vcGFjaXR5PSIwLjUwMTk2IiBmaWxsPSIjZmY2NjgwIiBzdHJva2Utb3BhY2l0eT0iMC41MDE5NiIgc3Ryb2tlPSIjZmYzMzU1Ii8+PC9nPjwvZz48L3N2Zz48IS0tcm90YXRpb25DZW50ZXI6OC4yOTQ5MTUwMDAwMDAwMDM6OC4yOTQ5MzUwMDAwMDAwMS0tPg=="; class Colors { getInfo() { return { - id: 'lxColors', - name: Scratch.translate('Colors'), - color1: '#f94c97', + id: "lxColors", + name: Scratch.translate("Colors"), + color1: "#f94c97", menuIconURI: blockIcon, blocks: [ { - opcode: 'newColor', + opcode: "newColor", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate('new color [COL]'), + text: Scratch.translate("new color [COL]"), arguments: { COL: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#a45eff' - } - } + defaultValue: "#a45eff", + }, + }, }, { - opcode: 'newColorRGB', + opcode: "newColorRGB", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate('from RGB [R] [G] [B]'), + text: Scratch.translate("from RGB [R] [G] [B]"), arguments: { R: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '164' + defaultValue: "164", }, G: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '94' + defaultValue: "94", }, B: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '255' - } - } + defaultValue: "255", + }, + }, }, { - opcode: 'newColorHSV', + opcode: "newColorHSV", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate('from HSV [H] [S] [V]'), + text: Scratch.translate("from HSV [H] [S] [V]"), arguments: { H: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '266' + defaultValue: "266", }, S: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '63' + defaultValue: "63", }, V: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '100' - } - } + defaultValue: "100", + }, + }, }, { - opcode: 'newColorHSL', + opcode: "newColorHSL", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate('from HSL [H] [S] [L]'), + text: Scratch.translate("from HSL [H] [S] [L]"), arguments: { H: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '266' + defaultValue: "266", }, S: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '100' + defaultValue: "100", }, L: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '68' - } - } + defaultValue: "68", + }, + }, }, { - opcode: 'newColorDecimal', + opcode: "newColorDecimal", blockType: Scratch.BlockType.REPORTER, text: Scratch.translate({ - default: 'from decimal [DEC]', - description: "From decimal - as in the base system" + default: "from decimal [DEC]", + description: "From decimal - as in the base system", }), arguments: { DEC: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '10772223' - } - } + defaultValue: "10772223", + }, + }, }, - '---', + "---", { - opcode: 'randomColor', + opcode: "randomColor", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate('random color'), - disableMonitor: true + text: Scratch.translate("random color"), + disableMonitor: true, }, - '---', + "---", { - opcode: 'additiveBlend', + opcode: "additiveBlend", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate('[COL1] + [COL2]'), + text: Scratch.translate("[COL1] + [COL2]"), arguments: { COL1: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#a45eff' + defaultValue: "#a45eff", }, COL2: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#eb57ab' - } - } + defaultValue: "#eb57ab", + }, + }, }, { - opcode: 'subtractiveBlend', + opcode: "subtractiveBlend", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate('[COL1] - [COL2]'), + text: Scratch.translate("[COL1] - [COL2]"), arguments: { COL1: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#a45eff' + defaultValue: "#a45eff", }, COL2: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#eb57ab' - } - } + defaultValue: "#eb57ab", + }, + }, }, { - opcode: 'multiplicativeBlend', + opcode: "multiplicativeBlend", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate('[COL1] * [COL2]'), + text: Scratch.translate("[COL1] * [COL2]"), arguments: { COL1: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#a45eff' + defaultValue: "#a45eff", }, COL2: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#eb57ab' - } - } + defaultValue: "#eb57ab", + }, + }, }, { - opcode: 'divisingBlend', + opcode: "divisingBlend", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate('[COL1] / [COL2]'), + text: Scratch.translate("[COL1] / [COL2]"), arguments: { COL1: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#a45eff' + defaultValue: "#a45eff", }, COL2: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#eb57ab' - } - } + defaultValue: "#eb57ab", + }, + }, }, - '---', + "---", { - opcode: 'differenceBlend', + opcode: "differenceBlend", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate('difference of [COL1] - [COL2]'), + text: Scratch.translate("difference of [COL1] - [COL2]"), arguments: { COL1: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#a45eff' + defaultValue: "#a45eff", }, COL2: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#eb57ab' - } - } + defaultValue: "#eb57ab", + }, + }, }, { - opcode: 'screenBlend', + opcode: "screenBlend", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate('screen [COL1] * [COL2]'), + text: Scratch.translate("screen [COL1] * [COL2]"), arguments: { COL1: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#a45eff' + defaultValue: "#a45eff", }, COL2: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#eb57ab' - } - } + defaultValue: "#eb57ab", + }, + }, }, { - opcode: 'overlayBlend', + opcode: "overlayBlend", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate('overlay [COL1] * [COL2]'), + text: Scratch.translate("overlay [COL1] * [COL2]"), arguments: { COL1: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#a45eff' + defaultValue: "#a45eff", }, COL2: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#eb57ab' - } - } + defaultValue: "#eb57ab", + }, + }, }, - '---', + "---", { - opcode: 'invertColor', + opcode: "invertColor", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate('invert [COL]'), + text: Scratch.translate("invert [COL]"), arguments: { COL: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#a45eff' + defaultValue: "#a45eff", }, - } + }, }, { - opcode: 'contrastColor', + opcode: "contrastColor", blockType: Scratch.BlockType.REPORTER, text: Scratch.translate({ - default: 'contrast [COL] by [NUM]', - description: "Contrast - as a verb, comparing to highlight differences" + default: "contrast [COL] by [NUM]", + description: + "Contrast - as a verb, comparing to highlight differences", }), arguments: { COL: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#a45eff' + defaultValue: "#a45eff", }, NUM: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '0.5' + defaultValue: "0.5", }, - } + }, }, { - opcode: 'grayscaleColor', + opcode: "grayscaleColor", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate('grayscale [COL]'), + text: Scratch.translate("grayscale [COL]"), arguments: { COL: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#a45eff' - } - } + defaultValue: "#a45eff", + }, + }, }, { - opcode: 'percentWhite', + opcode: "percentWhite", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate('[NUM] % white'), + text: Scratch.translate("[NUM] % white"), arguments: { NUM: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '75' - } - } + defaultValue: "75", + }, + }, }, - '---', + "---", { - opcode: 'distanceBetweenColors', + opcode: "distanceBetweenColors", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate('distance between [COL1] and [COL2]'), + text: Scratch.translate("distance between [COL1] and [COL2]"), arguments: { COL1: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#a45eff' + defaultValue: "#a45eff", }, COL2: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#eb57ab' - } - } + defaultValue: "#eb57ab", + }, + }, }, { - opcode: 'contrastRatioOfColors', + opcode: "contrastRatioOfColors", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate('contrast ratio of [COL1] and [COL2]'), + text: Scratch.translate("contrast ratio of [COL1] and [COL2]"), arguments: { COL1: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#a45eff' + defaultValue: "#a45eff", }, COL2: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#eb57ab' - } - } + defaultValue: "#eb57ab", + }, + }, }, { - opcode: 'nearEqualColors', + opcode: "nearEqualColors", blockType: Scratch.BlockType.BOOLEAN, - text: Scratch.translate('[COL1] ≈ [COL2] threshold [THR]'), + text: Scratch.translate("[COL1] ≈ [COL2] threshold [THR]"), arguments: { COL1: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#a45eff' + defaultValue: "#a45eff", }, COL2: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#eb57ab' + defaultValue: "#eb57ab", }, THR: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '25' - } - } + defaultValue: "25", + }, + }, }, { - opcode: 'colorFollowsWCAG', + opcode: "colorFollowsWCAG", blockType: Scratch.BlockType.BOOLEAN, - text: Scratch.translate('does [COL1] and [COL2] follow [AAA] for [TXT] text'), + text: Scratch.translate( + "does [COL1] and [COL2] follow [AAA] for [TXT] text" + ), arguments: { COL1: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#a45eff' + defaultValue: "#a45eff", }, COL2: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#eb57ab' + defaultValue: "#eb57ab", }, AAA: { type: Scratch.ArgumentType.STRING, - menu: 'WCAG_MENU' + menu: "WCAG_MENU", }, TXT: { type: Scratch.ArgumentType.STRING, - menu: 'TEXTWCAG_SIZE_MENU' - } + menu: "TEXTWCAG_SIZE_MENU", + }, }, - hideFromPalette: true + hideFromPalette: true, }, - '---', + "---", { - opcode: 'interpolateColors', + opcode: "interpolateColors", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate('interpolate [COL1] to [COL2] ratio [RATIO] using [SPACE]'), + text: Scratch.translate( + "interpolate [COL1] to [COL2] ratio [RATIO] using [SPACE]" + ), arguments: { COL1: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#a45eff' + defaultValue: "#a45eff", }, COL2: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#eb57ab' + defaultValue: "#eb57ab", }, RATIO: { type: Scratch.ArgumentType.NUMBER, - defaultValue: '0.5' + defaultValue: "0.5", }, SPACE: { type: Scratch.ArgumentType.STRING, - menu: 'SPACE_MENU' - } - } + menu: "SPACE_MENU", + }, + }, }, - '---', + "---", { - opcode: 'getChannelFromColor', + opcode: "getChannelFromColor", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate('get [CHN] of [COL]'), + text: Scratch.translate("get [CHN] of [COL]"), arguments: { CHN: { type: Scratch.ArgumentType.STRING, - menu: 'CHANNEL_MENU' + menu: "CHANNEL_MENU", }, COL: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#a45eff' - } - } + defaultValue: "#a45eff", + }, + }, }, { - opcode: 'setChannelOfColor', + opcode: "setChannelOfColor", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate('set [CHN] of [COL] to [SET]'), + text: Scratch.translate("set [CHN] of [COL] to [SET]"), arguments: { CHN: { type: Scratch.ArgumentType.STRING, - menu: 'CHANNEL_MENU' + menu: "CHANNEL_MENU", }, COL: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#a45eff' + defaultValue: "#a45eff", }, SET: { type: Scratch.ArgumentType.NUMBER, - } - } + }, + }, }, { - opcode: 'changeChannelOfColor', + opcode: "changeChannelOfColor", blockType: Scratch.BlockType.REPORTER, - text: Scratch.translate('change [CHN] of [COL] by [SET]'), + text: Scratch.translate("change [CHN] of [COL] by [SET]"), arguments: { CHN: { type: Scratch.ArgumentType.STRING, - menu: 'CHANNEL_MENU' + menu: "CHANNEL_MENU", }, COL: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#a45eff' + defaultValue: "#a45eff", }, SET: { type: Scratch.ArgumentType.NUMBER, - } - } + }, + }, }, - '---', + "---", { - opcode: 'colorToDecimal', + opcode: "colorToDecimal", blockType: Scratch.BlockType.REPORTER, text: Scratch.translate({ - default: '[COL] to decimal', - description: "To decimal - as in the base system" + default: "[COL] to decimal", + description: "To decimal - as in the base system", }), arguments: { COL: { type: Scratch.ArgumentType.COLOR, - defaultValue: '#a45eff' - } - } + defaultValue: "#a45eff", + }, + }, }, ], menus: { @@ -763,7 +806,7 @@ }, WCAG_MENU: { acceptReporters: true, - items: ['A', 'AA', 'AAA'], + items: ["A", "AA", "AAA"], }, TEXTWCAG_SIZE_MENU: { acceptReporters: true, @@ -772,27 +815,34 @@ { text: Scratch.translate("large"), value: "large" }, ], }, - } + }, }; } newColor(args) { return args.COL; } newColorRGB(args) { - return '#' + toFixHex(args.R) + toFixHex(args.G) + toFixHex(args.B); + return "#" + toFixHex(args.R) + toFixHex(args.G) + toFixHex(args.B); } newColorHSV(args) { return hsvToHex(args.H, args.S, args.V); } newColorHSL(args) { let convRGB = hslToRgb(args.H, args.S, args.L); - return '#' + toFixHex(convRGB.r) + toFixHex(convRGB.g) + toFixHex(convRGB.b); + return ( + "#" + toFixHex(convRGB.r) + toFixHex(convRGB.g) + toFixHex(convRGB.b) + ); } newColorDecimal(args) { - return '#' + toHex(args.DEC); + return "#" + toHex(args.DEC); } randomColor() { - return '#' + Math.floor(Math.random() * 0xFFFFFF).toString(16).padStart(6, '0'); + return ( + "#" + + Math.floor(Math.random() * 0xffffff) + .toString(16) + .padStart(6, "0") + ); } additiveBlend(args) { let a = hexToRgb(args.COL1); @@ -801,7 +851,7 @@ return rgbToHex({ r: add(a.r, b.r), g: add(a.g, b.g), - b: add(a.b, b.b) + b: add(a.b, b.b), }); } subtractiveBlend(args) { @@ -811,7 +861,7 @@ return rgbToHex({ r: sub(a.r, b.r), g: sub(a.g, b.g), - b: sub(a.b, b.b) + b: sub(a.b, b.b), }); } multiplicativeBlend(args) { @@ -821,7 +871,7 @@ return rgbToHex({ r: mul(a.r, b.r), g: mul(a.g, b.g), - b: mul(a.b, b.b) + b: mul(a.b, b.b), }); } divisingBlend(args) { @@ -831,7 +881,7 @@ return rgbToHex({ r: div(a.r, b.r), g: div(a.g, b.g), - b: div(a.b, b.b) + b: div(a.b, b.b), }); } differenceBlend(args) { @@ -841,7 +891,7 @@ return rgbToHex({ r: sub(a.r, b.r), g: sub(a.g, b.g), - b: sub(a.b, b.b) + b: sub(a.b, b.b), }); } screenBlend(args) { @@ -851,22 +901,29 @@ return rgbToHex({ r: scr(a.r, b.r), g: scr(a.g, b.g), - b: scr(a.b, b.b) + b: scr(a.b, b.b), }); } overlayBlend(args) { let a = hexToRgb(args.COL1); let b = hexToRgb(args.COL2); - const ove = (c1, c2) => clamp(c1 < 128 ? (2 * c1 * c2) / 255 : 255 - (2 * (255 - c1) * (255 - c2)) / 255, 0, 255); + const ove = (c1, c2) => + clamp( + c1 < 128 + ? (2 * c1 * c2) / 255 + : 255 - (2 * (255 - c1) * (255 - c2)) / 255, + 0, + 255 + ); return rgbToHex({ r: ove(a.r, b.r), g: ove(a.g, b.g), - b: ove(a.b, b.b) + b: ove(a.b, b.b), }); } invertColor(args) { let col = hexToRgb(args.COL); - const inv = c => 255-c; + const inv = (c) => 255 - c; return rgbToHex({ r: inv(col.r), g: inv(col.g), @@ -875,27 +932,29 @@ } contrastColor(args) { let col = hexToRgb(args.COL); - const cnt = c => ((c - 128) * (1 - args.NUM)) + 128; + const cnt = (c) => (c - 128) * (1 - args.NUM) + 128; return rgbToHex({ r: cnt(col.r), g: cnt(col.g), b: cnt(col.b), }); } - grayscaleColor(args) { - let rgb = hexToRgb(args.COL); + grayscaleColor(args) { + let rgb = hexToRgb(args.COL); let gray = Math.round(0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b); - return '#' + toFixHex(gray) + toFixHex(gray) + toFixHex(gray); + return "#" + toFixHex(gray) + toFixHex(gray) + toFixHex(gray); } percentWhite(args) { - let white = round(args.NUM * 2.55) - return '#' + toFixHex(white) + toFixHex(white) + toFixHex(white); + let white = round(args.NUM * 2.55); + return "#" + toFixHex(white) + toFixHex(white) + toFixHex(white); } distanceBetweenColors(args) { return distanceBetweenHexColorsDeltaE2000(args.COL1, args.COL2); } nearEqualColors(args) { - return distanceBetweenHexColorsDeltaE2000(args.COL1, args.COL2) <= args.THR; + return ( + distanceBetweenHexColorsDeltaE2000(args.COL1, args.COL2) <= args.THR + ); } contrastRatioOfColors(args) { return round(contrastRatio(args.COL1, args.COL2) * 100) / 100; @@ -905,76 +964,103 @@ let req = 0; let level = args.AAA; let size = args.TXT; - if (level === 'AA') { - req = (size === 'large') ? 3.0 : 4.5; + if (level === "AA") { + req = size === "large" ? 3.0 : 4.5; } - if (level === 'AAA') { - req = (size === 'large') ? 4.5 : 7.0; + if (level === "AAA") { + req = size === "large" ? 4.5 : 7.0; } return ratio >= req; } interpolateColors(args) { - if (args.SPACE == 'RGB') { + if (args.SPACE == "RGB") { let a = hexToRgb(args.COL1); let b = hexToRgb(args.COL2); let t = args.RATIO; return rgbToHex({ r: lerp(a.r, b.r, t), g: lerp(a.g, b.g, t), - b: lerp(a.b, b.b, t) + b: lerp(a.b, b.b, t), }); } else { - return interpolateHexColorsHsv(args.COL1, args.COL2, args.RATIO) + return interpolateHexColorsHsv(args.COL1, args.COL2, args.RATIO); } } getChannelFromColor(args) { - if (['red', 'green', 'blue'].includes(args.CHN)) { - let channel = ['red', 'green', 'blue'].indexOf(args.CHN); - let letter = ['red', 'green', 'blue'][channel][0]; + if (["red", "green", "blue"].includes(args.CHN)) { + let channel = ["red", "green", "blue"].indexOf(args.CHN); + let letter = ["red", "green", "blue"][channel][0]; return hexToRgb(args.COL)[letter]; - } else if (['hue', 'saturation', 'value'].includes(args.CHN)) { + } else if (["hue", "saturation", "value"].includes(args.CHN)) { let hsv = hexToHsv(args.COL); switch (args.CHN) { - case 'hue': return round(hsv.h); - case 'saturation': return round(hsv.s); - case 'value': return round(hsv.v); + case "hue": + return round(hsv.h); + case "saturation": + return round(hsv.s); + case "value": + return round(hsv.v); } } } setChannelOfColor(args) { - if (['red', 'green', 'blue'].includes(args.CHN)) { + if (["red", "green", "blue"].includes(args.CHN)) { let rgb = hexToRgb(args.COL); switch (args.CHN) { - case 'red': rgb.r = args.SET; break; - case 'green': rgb.g = args.SET; break; - case 'blue': rgb.b = args.SET; break; + case "red": + rgb.r = args.SET; + break; + case "green": + rgb.g = args.SET; + break; + case "blue": + rgb.b = args.SET; + break; } - return '#' + toFixHex(rgb.r) + toFixHex(rgb.g) + toFixHex(rgb.b); - } else if (['hue', 'saturation', 'value'].includes(args.CHN)) { + return "#" + toFixHex(rgb.r) + toFixHex(rgb.g) + toFixHex(rgb.b); + } else if (["hue", "saturation", "value"].includes(args.CHN)) { let hsv = hexToHsv(args.COL); switch (args.CHN) { - case 'hue': hsv.h = args.SET; break; - case 'saturation': hsv.s = args.SET; break; - case 'value': hsv.v = args.SET; break; + case "hue": + hsv.h = args.SET; + break; + case "saturation": + hsv.s = args.SET; + break; + case "value": + hsv.v = args.SET; + break; } return hsvToHex(hsv.h, hsv.s, hsv.v); } } changeChannelOfColor(args) { - if (['red', 'green', 'blue'].includes(args.CHN)) { + if (["red", "green", "blue"].includes(args.CHN)) { let rgb = hexToRgb(args.COL); switch (args.CHN) { - case 'red': rgb.r = clamp(rgb.r + args.SET, 0, 255); break; - case 'green': rgb.g = clamp(rgb.g + args.SET, 0, 255); break; - case 'blue': rgb.b = clamp(rgb.b + args.SET, 0, 255); break; + case "red": + rgb.r = clamp(rgb.r + args.SET, 0, 255); + break; + case "green": + rgb.g = clamp(rgb.g + args.SET, 0, 255); + break; + case "blue": + rgb.b = clamp(rgb.b + args.SET, 0, 255); + break; } - return '#' + toFixHex(rgb.r) + toFixHex(rgb.g) + toFixHex(rgb.b); - } else if (['hue', 'saturation', 'value'].includes(args.CHN)) { + return "#" + toFixHex(rgb.r) + toFixHex(rgb.g) + toFixHex(rgb.b); + } else if (["hue", "saturation", "value"].includes(args.CHN)) { let hsv = hexToHsv(args.COL); switch (args.CHN) { - case 'hue': hsv.h = (hsv.h + args.SET) % 360; break; - case 'saturation': hsv.s = clamp(hsv.s + args.SET, 0, 100); break; - case 'value': hsv.v = clamp(hsv.v + args.SET, 0, 100); break; + case "hue": + hsv.h = (hsv.h + args.SET) % 360; + break; + case "saturation": + hsv.s = clamp(hsv.s + args.SET, 0, 100); + break; + case "value": + hsv.v = clamp(hsv.v + args.SET, 0, 100); + break; } return hsvToHex(hsv.h, hsv.s, hsv.v); }