Spaces:
Sleeping
Sleeping
const levelText = require('./text leveler'); | |
class Color { | |
/** | |
* @typedef {object} RGBObject - An object representing a color in RGB format. | |
* @property {number} r - the red component, in the range [0, 255]. | |
* @property {number} g - the green component, in the range [0, 255]. | |
* @property {number} b - the blue component, in the range [0, 255]. | |
*/ | |
/** | |
* @typedef {object} HSVObject - An object representing a color in HSV format. | |
* @property {number} h - hue, in the range [0-359). | |
* @property {number} s - saturation, in the range [0,1]. | |
* @property {number} v - value, in the range [0,1]. | |
*/ | |
/** @type {RGBObject} */ | |
static get RGB_BLACK () { | |
return {r: 0, g: 0, b: 0}; | |
} | |
/** @type {RGBObject} */ | |
static get RGB_WHITE () { | |
return {r: 255, g: 255, b: 255}; | |
} | |
/** | |
* Convert a Scratch decimal color to a hex string, #RRGGBB. | |
* @param {number} decimal RGB color as a decimal. | |
* @return {string} RGB color as #RRGGBB hex string. | |
*/ | |
static decimalToHex (decimal) { | |
const rgb = this.decimalToRgb(decimal); | |
const alphaOrNone = rgb.a !== 255 | |
? rgb.a.toString(16) | |
: ''; | |
const r = levelText(rgb.r.toString(16), 2, '0'); | |
const g = levelText(rgb.g.toString(16), 2, '0'); | |
const b = levelText(rgb.b.toString(16), 2, '0'); | |
return `#${r}${g}${b}${alphaOrNone}`; | |
} | |
/** | |
* Convert a Scratch decimal color to an RGB color object. | |
* @param {number} decimal RGB color as decimal. | |
* @return {RGBObject} rgb - {r: red [0,255], g: green [0,255], b: blue [0,255]}. | |
*/ | |
static decimalToRgb (decimal) { | |
const alpha = ((decimal >> 24) & 0xFF) !== 0x00; | |
let r = (decimal >> 16) & 0xFF, | |
g = (decimal >> 8) & 0xFF, | |
b = decimal & 0xFF, | |
a = 0; | |
if (alpha) { | |
a = (decimal >> 24) & 0xFF; | |
r = (decimal >> 16) & 0xFF; | |
g = (decimal >> 8) & 0xFF; | |
b = decimal & 0xFF; | |
} | |
return {r: r, g: g, b: b, a: 255 - a}; | |
} | |
/** | |
* Convert a hex color (e.g., F00, #03F, #0033FF) to an RGB color object. | |
* @param {!string} hex Hex representation of the color. | |
* @return {RGBObject} null on failure, or rgb: {r: red [0,255], g: green [0,255], b: blue [0,255]}. | |
*/ | |
static hexToRgb (hex) { | |
if (hex.startsWith('#')) { | |
hex = hex.substring(1); | |
} | |
const parsed = parseInt(hex, 16); | |
if (isNaN(parsed)) { | |
return null; | |
} | |
if (hex.length === 6) { | |
return { | |
r: (parsed >> 16) & 0xff, | |
g: (parsed >> 8) & 0xff, | |
b: parsed & 0xff | |
}; | |
} else if (hex.length === 8) { | |
return { | |
r: (parsed >> 24) & 0xff, | |
g: (parsed >> 16) & 0xff, | |
b: (parsed >> 8) & 0xff, | |
a: parsed & 0xff | |
}; | |
} else if (hex.length === 3) { | |
const r = ((parsed >> 8) & 0xf); | |
const g = ((parsed >> 4) & 0xf); | |
const b = parsed & 0xf; | |
return { | |
r: (r << 4) | r, | |
g: (g << 4) | g, | |
b: (b << 4) | b | |
}; | |
} | |
return null; | |
} | |
/** | |
* Convert an RGB color object to a hex color. | |
* @param {RGBObject} rgb - {r: red [0,255], g: green [0,255], b: blue [0,255]}. | |
* @return {!string} Hex representation of the color. | |
*/ | |
static rgbToHex (rgb) { | |
return Color.decimalToHex(Color.rgbToDecimal(rgb)); | |
} | |
/** | |
* Convert an RGB color object to a Scratch decimal color. | |
* @param {RGBObject} rgb - {r: red [0,255], g: green [0,255], b: blue [0,255]}. | |
* @return {!number} Number representing the color. | |
*/ | |
static rgbToDecimal (rgb) { | |
if (typeof rgb.a === 'number') { | |
return ((255 - rgb.a) << 24) + (rgb.r << 16) + (rgb.g << 8) + rgb.b; | |
} | |
return (rgb.r << 16) + (rgb.g << 8) + rgb.b; | |
} | |
/** | |
* Convert a hex color (e.g., F00, #03F, #0033FF) to a decimal color number. | |
* @param {!string} hex Hex representation of the color. | |
* @return {!number} Number representing the color. | |
*/ | |
static hexToDecimal (hex) { | |
return Color.rgbToDecimal(Color.hexToRgb(hex)); | |
} | |
/** | |
* Convert an HSV color to RGB format. | |
* @param {HSVObject} hsv - {h: hue [0,360), s: saturation [0,1], v: value [0,1]} | |
* @return {RGBObject} rgb - {r: red [0,255], g: green [0,255], b: blue [0,255]}. | |
*/ | |
static hsvToRgb (hsv) { | |
let h = hsv.h % 360; | |
if (h < 0) h += 360; | |
const s = Math.max(0, Math.min(hsv.s, 1)); | |
const v = Math.max(0, Math.min(hsv.v, 1)); | |
const i = Math.floor(h / 60); | |
const f = (h / 60) - i; | |
const p = v * (1 - s); | |
const q = v * (1 - (s * f)); | |
const t = v * (1 - (s * (1 - f))); | |
let r; | |
let g; | |
let b; | |
switch (i) { | |
default: | |
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: Math.floor(r * 255), | |
g: Math.floor(g * 255), | |
b: Math.floor(b * 255) | |
}; | |
} | |
/** | |
* Convert an RGB color to HSV format. | |
* @param {RGBObject} rgb - {r: red [0,255], g: green [0,255], b: blue [0,255]}. | |
* @return {HSVObject} hsv - {h: hue [0,360), s: saturation [0,1], v: value [0,1]} | |
*/ | |
static rgbToHsv (rgb) { | |
const r = rgb.r / 255; | |
const g = rgb.g / 255; | |
const b = rgb.b / 255; | |
const x = Math.min(Math.min(r, g), b); | |
const v = Math.max(Math.max(r, g), b); | |
// For grays, hue will be arbitrarily reported as zero. Otherwise, calculate | |
let h = 0; | |
let s = 0; | |
if (x !== v) { | |
const f = (r === x) ? g - b : ((g === x) ? b - r : r - g); | |
const i = (r === x) ? 3 : ((g === x) ? 5 : 1); | |
h = ((i - (f / (v - x))) * 60) % 360; | |
s = (v - x) / v; | |
} | |
return {h: h, s: s, v: v}; | |
} | |
/** | |
* Linear interpolation between rgb0 and rgb1. | |
* @param {RGBObject} rgb0 - the color corresponding to fraction1 <= 0. | |
* @param {RGBObject} rgb1 - the color corresponding to fraction1 >= 1. | |
* @param {number} fraction1 - the interpolation parameter. If this is 0.5, for example, mix the two colors equally. | |
* @return {RGBObject} the interpolated color. | |
*/ | |
static mixRgb (rgb0, rgb1, fraction1) { | |
if (fraction1 <= 0) return rgb0; | |
if (fraction1 >= 1) return rgb1; | |
const fraction0 = 1 - fraction1; | |
return { | |
r: (fraction0 * rgb0.r) + (fraction1 * rgb1.r), | |
g: (fraction0 * rgb0.g) + (fraction1 * rgb1.g), | |
b: (fraction0 * rgb0.b) + (fraction1 * rgb1.b) | |
}; | |
} | |
} | |
module.exports = Color; | |