Added untested hsla background mapping.

Font color mapping now uses color format the styling currently uses.
This commit is contained in:
Harrison Deng 2022-04-21 20:17:48 -05:00
parent 4dc34ef2f9
commit 7ac924f934
3 changed files with 266 additions and 19 deletions

View File

@ -1,4 +1,4 @@
import { parseColor, rgbaToHexRgba } from "../support/colors.js"; import { hslaToCssHsla, hslaToRgba, parseColorToHsla, parseColorToRgba, rgbaToCssRgba, rgbaToHexRgba, rgbaToHsla } from "../support/colors.js";
import VisUpdateRouter from "../visualization/VisUpdateRouter.js"; import VisUpdateRouter from "../visualization/VisUpdateRouter.js";
import { numerical } from "./numeric.js"; import { numerical } from "./numeric.js";
@ -10,7 +10,7 @@ import { numerical } from "./numeric.js";
* *
* @param {object} conf The configuration of the mapping. * @param {object} conf The configuration of the mapping.
* @param {HTMLElement} conf.element The element whose text's red value should be mapped. * @param {HTMLElement} conf.element The element whose text's red value should be mapped.
* @param {number} conf.color Where r for red, g, for green, b for blue, and a for alpha. * @param {string} conf.color Where r for red, g, for green, b for blue, and a for alpha.
* @param {number} conf.lowerBin The lower bound of the bins to be mapped. * @param {number} conf.lowerBin The lower bound of the bins to be mapped.
* @param {VisUpdateRouter} conf.visUpdateRouter The visualizer update manager associated with the audio playback you would like the mapping with. * @param {VisUpdateRouter} conf.visUpdateRouter The visualizer update manager associated with the audio playback you would like the mapping with.
* @param {Function} conf.interpolator The interpolation function to use. * @param {Function} conf.interpolator The interpolation function to use.
@ -26,12 +26,20 @@ export function backgroundColorRgba({ element, color, lowerBin, visUpdateRouter,
if (color < 0) throw new Error("Invalid color parameter provided."); if (color < 0) throw new Error("Invalid color parameter provided.");
const getter = () => { const getter = () => {
if (!element.style.backgroundColor) element.style.backgroundColor = "rgba(0, 0, 0, 255)"; if (!element.style.backgroundColor) element.style.backgroundColor = "rgba(0, 0, 0, 255)";
return parseColor(element.style.backgroundColor)[color]; return parseColorToRgba(element.style.backgroundColor)[color];
}; };
const setter = (value) => { const setter = (value) => {
const changed = parseColor(element.style.backgroundColor); const changed = parseColorToRgba(element.style.backgroundColor);
changed[color] = value; changed[color] = value;
element.style.backgroundColor = rgbaToHexRgba(changed); if (element.style.backgroundColor.startsWith("rgb")) {
element.style.backgroundColor = rgbaToCssRgba(changed);
} else if (element.style.backgroundColor.startsWith("hsl")) {
element.style.backgroundColor = hslaToCssHsla(rgbaToHsla(changed));
} else if (element.style.backgroundColor.startsWith("#")) {
element.style.backgroundColor = rgbaToHexRgba(changed);
} else {
element.style.backgroundColor = rgbaToHexRgba(changed);
}
}; };
upperVal = Math.min(Math.max(0, upperVal), 255); upperVal = Math.min(Math.max(0, upperVal), 255);
lowerVal = Math.min(Math.max(0, lowerVal), upperVal); lowerVal = Math.min(Math.max(0, lowerVal), upperVal);
@ -49,12 +57,71 @@ export function backgroundColorRgba({ element, color, lowerBin, visUpdateRouter,
return numerical(conf); return numerical(conf);
} }
/**
* Maps a color component of an element background color to a certain range of frequency bins.
*
* @param {object} conf The configuration of the mapping.
* @param {HTMLElement} conf.element The element whose text's red value should be mapped.
* @param {number} conf.select Where h for hue, s, for saturation, l for lightness, and a for alpha.
* @param {number} conf.lowerBin The lower bound of the bins to be mapped.
* @param {VisUpdateRouter} conf.visUpdateRouter The visualizer update manager associated with the audio playback you would like the mapping with.
* @param {Function} conf.interpolator The interpolation function to use.
* @param {number} [conf.upperBin=undefined] The upper bound of the bins to be mapped. If left undefined, then only the bin defined by the min value is used.
* @param {boolean} [conf.reversed=true] If true, then the quieter, the greater the red value.
* @param {number} [conf.lowerVal=0] The lower boundary of possible values for the color component (0 to upperVal inclusive).
* @param {number} [conf.upperVal=0] The upper boundary of possible values for the color component (0 to 360 if conf.color is "h", 1 if conf.color is "s" or "l", and 255 if conf.color is "a").
* @returns {{lower: number, upper: number, listener: VisUpdateRouter~visualizerRangedUpdateListener}} The ranged listener that was added.
*/
export function backgroundColorHsla({ element, select, lowerBin, visUpdateRouter, interpolator, upperBin = undefined, reversed = false, lowerVal = 0, upperVal = undefined }) {
const rgbaStr = "hsla";
select = rgbaStr.indexOf(select);
if (select < 0) throw new Error("Invalid color parameter provided.");
const getter = () => {
if (!element.style.backgroundColor) element.style.backgroundColor = "hsl(0, 50%, 50%, 255)";
return parseColorToHsla(element.style.backgroundColor)[select];
};
const setter = (value) => {
const changed = parseColorToHsla(element.style.backgroundColor);
changed[select] = value;
if (element.style.backgroundColor.startsWith("hsl")) {
element.style.backgroundColor = hslaToCssHsla(changed);
} else if (element.style.backgroundColor.startsWith("rgb")) {
element.style.backgroundColor = rgbaToCssRgba(hslaToRgba(changed));
} else if (element.style.backgroundColor.startsWith("#")) {
element.style.backgroundColor = rgbaToHexRgba(hslaToRgba(changed));
} else {
element.style.backgroundColor = rgbaToHexRgba(hslaToRgba(changed)); // If we don't recognize the currently used color function, then just use hex.
}
};
let upperBound = 360;
if (select === "s" || select === "l") {
upperBound = 1;
} else if (select === "a") {
upperBound = 255;
}
upperVal = Math.min(Math.max(0, upperVal), upperBound);
lowerVal = Math.min(Math.max(0, lowerVal), upperVal);
const conf = {
minVal: lowerVal,
maxVal: upperVal,
lowerBin: lowerBin,
upperBin: upperBin,
getter: getter,
setter: setter,
interpolator: interpolator,
visUpdateRouter: visUpdateRouter,
reversed: reversed
};
return numerical(conf);
}
/** /**
* Maps a color component of the text color to a certain range of frequency bins. * Maps a color component of the text color to a certain range of frequency bins.
* *
* @param {object} conf The configuration of the mapping. * @param {object} conf The configuration of the mapping.
* @param {HTMLElement} conf.element The element whose text's red value should be mapped. * @param {HTMLElement} conf.element The element whose text's red value should be mapped.
* @param {number} conf.color Where r for red, g, for green, b for blue, and a for alpha. * @param {string} conf.color Where r for red, g, for green, b for blue, and a for alpha.
* @param {number} conf.lowerBin The lower bound of the bins to be mapped. * @param {number} conf.lowerBin The lower bound of the bins to be mapped.
* @param {VisUpdateRouter} conf.visUpdateRouter The visualizer update manager associated with the audio playback you would like the mapping with. * @param {VisUpdateRouter} conf.visUpdateRouter The visualizer update manager associated with the audio playback you would like the mapping with.
* @param {Function} conf.interpolator The interpolation function to use. * @param {Function} conf.interpolator The interpolation function to use.
@ -70,12 +137,20 @@ export function fontColorRgba({ element, color, lowerBin, visUpdateRouter, inter
if (color < 0) throw new Error("Invalid color parameter provided."); if (color < 0) throw new Error("Invalid color parameter provided.");
const getter = () => { const getter = () => {
if (!element.style.color) element.style.color = "rgba(0, 0, 0, 255)"; if (!element.style.color) element.style.color = "rgba(0, 0, 0, 255)";
return parseColor(element.style.color)[color]; return parseColorToRgba(element.style.color)[color];
}; };
const setter = (value) => { const setter = (value) => {
const changed = parseColor(element.style.color); const changed = parseColorToRgba(element.style.color);
changed[color] = value; changed[color] = value;
element.style.color = rgbaToHexRgba(changed); if (element.style.color.startsWith("rgb")) {
element.style.color = rgbaToCssRgba(changed);
} else if (element.style.color.startsWith("hsl")) {
element.style.color = hslaToCssHsla(rgbaToHsla(changed));
} else if (element.style.color.startsWith("#")) {
element.style.color = rgbaToHexRgba(changed);
} else {
element.style.color = rgbaToHexRgba(changed);
}
}; };
upperVal = Math.min(Math.max(0, upperVal), 255); upperVal = Math.min(Math.max(0, upperVal), 255);
lowerVal = Math.min(Math.max(0, lowerVal), upperVal); lowerVal = Math.min(Math.max(0, lowerVal), upperVal);

View File

@ -45,7 +45,7 @@ export function rgbHexToRgba(hex) {
*/ */
export function cssRgbaToRgba(rgba) { export function cssRgbaToRgba(rgba) {
rgba = rgba.replaceAll(" ", ""); rgba = rgba.replaceAll(" ", "");
const cssRgbaRegex = /rgba\((\d+),(\d+),(\d+),(\d+)\)/; const cssRgbaRegex = /rgba\((\d+)[, ] ?(\d+)[, ] ?(\d+)[, ] ?(\d+)\)/;
try { try {
const matches = rgba.match(cssRgbaRegex); const matches = rgba.match(cssRgbaRegex);
return [parseInt(matches[1]), parseInt(matches[2]), parseInt(matches[3]), parseInt(matches[4])]; return [parseInt(matches[1]), parseInt(matches[2]), parseInt(matches[3]), parseInt(matches[4])];
@ -61,8 +61,7 @@ export function cssRgbaToRgba(rgba) {
* @returns {number[]} the rgba components. * @returns {number[]} the rgba components.
*/ */
export function cssRgbToRgba(rgb) { export function cssRgbToRgba(rgb) {
rgb = rgb.replaceAll(" ", ""); const cssRgbRegex = /rgb\((\d+)[, ] ?(\d+)[, ] ?(\d+)\)/;
const cssRgbRegex = /rgb\((\d+),(\d+),(\d+)\)/;
try { try {
const matches = rgb.match(cssRgbRegex); const matches = rgb.match(cssRgbRegex);
return [parseInt(matches[1]), parseInt(matches[2]), parseInt(matches[3]), 255]; return [parseInt(matches[1]), parseInt(matches[2]), parseInt(matches[3]), 255];
@ -84,14 +83,14 @@ export function rgbaToHexRgba(rgba) {
/** /**
* Converts a css rgb, rgba, hex, or rgba hex to a rgba array. * Converts a css rgb, rgba, hex, hsl(a), or rgba hex to a rgba array.
* *
* For hex, we assume there is no alpha channel unless the hex value is not minimized. * For hex, we assume there is no alpha channel unless the hex value is not minimized.
* *
* @param {string} color The string that contains the color information. * @param {string} color The string that contains the color information.
* @returns {number[]} an array that contains the r, g, b and a components. * @returns {number[]} an array that contains the r, g, b and a components.
*/ */
export function parseColor(color) { export function parseColorToRgba(color) {
if (color.startsWith("rgba(")) { if (color.startsWith("rgba(")) {
return cssRgbaToRgba(color); return cssRgbaToRgba(color);
} else if (color.startsWith("rgb(")) { } else if (color.startsWith("rgb(")) {
@ -102,6 +101,124 @@ export function parseColor(color) {
} else { } else {
return rgbHexToRgba(color); return rgbHexToRgba(color);
} }
} else if (color.startsWith("hsl(")) {
return hslaToRgba(cssHslaToHsla(color));
} }
throw new Error("Could not parse to an rgba value."); throw new Error("Could not parse to an rgba value.");
} }
/**
* Converts a css rgb, rgba, hex, hsl(a), or rgba hex to an hsla array.
*
* @param {string} color The string that contains the color information.
* @returns {number[]} An array that contains the h, s, l and alpha channel components.
*/
export function parseColorToHsla(color) {
if (color.startsWith("rgba(")) {
return rgbaToHsla(cssRgbaToRgba(color));
} else if (color.startsWith("rgb(")) {
return rgbaToHsla(cssRgbToRgba(color));
} else if (color.startsWith("#")) {
if (color.length === 9) {
return rgbaToHsla(rgbaHexToRgba(color));
} else {
return rgbaToHsla(rgbHexToRgba(color));
}
} else if (color.startsWith("hsl(")) {
return cssHslaToHsla(color);
}
}
/**
* Converts a given rgb value to hsla. Alpha value is simply passed through.
*
* @param {number[]} rgba An array that contains numbers representing the r, g, b, and a values respectively.
* @returns {number[]} An array that contains the h, s, l, and a values.
*/
export function rgbaToHsla(rgba) { // Used GeeksForGeeks implementation
let r = rgba[0] / 255;
let g = rgba[1] / 255;
let b = rgba[2] / 255;
const cmax = Math.max(r, g, b);
const cmin = Math.min(r, g, b);
const delta = cmax - cmin;
let h = 0;
if (delta === 0) {
h = 0;
} else if (cmax === r) {
h = 60 * (((g - b) / delta) % 6);
} else if (cmax === g) {
h = 60 * (2 + (b - r) / delta);
} else if (cmax === b) {
h = 60 * (4 + (r - g) / delta);
}
let l = (cmax + cmin) / 2;
let s = 0;
if (delta === 0) {
s = 0;
} else {
s = delta / (1 - Math.abs(2 * l - 1));
}
return [h, s, l, rgba[3]];
}
/**
* Converts a given hsv value to rgb. The alpha channel value is just passed through.
*
* @param {number[]} hsla The HSL and alpha component array.
* @returns {number[]} An array comprised of r, g, b and a values converted from the given HSL.
*/
export function hslaToRgba(hsla) {
const h = hsla[0];
const s = hsla[1];
const l = hsla[2];
const fn = (n) => {
const k = ((n + (h / 30)) % 12);
const a = s * Math.min(l, 1 - l);
return l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
};
return [Math.round(fn(0) * 255), Math.round(fn(8) * 255), Math.round(fn(4) * 255), hsla[3]];
}
/**
* Parses a CSS color property coded in hsl.
*
* @param {string} cssHsla The style property value in HSL(A).
* @returns {number[]} An array that contains the parsed H, S, L and alpha channel.
*/
export function cssHslaToHsla(cssHsla) {
const cssHslaRegex = /hsl\((\d+)[, ] ?(\d+)%[, ] ?(\d+)% ?[, /]? ?(\d+)?\)/;
try {
const matches = cssHsla.match(cssHslaRegex);
return [parseInt(matches[1]), parseInt(matches[2]) / 100, parseInt(matches[3]) / 100, isNaN(matches[4]) ? 255 : parseInt(matches[4])];
} catch (error) {
throw new Error("Could not parse the given HSL(a) value: " + error.message);
}
}
/**
* Converts an array containing the H, S, L and alpha values to a css hsl function call.
*
* @param {number[]} hsla An array containing the H, S, L and alpha channel values.
* @returns {string} The css hsl function call.
*/
export function hslaToCssHsla(hsla) {
return "hsl(" + hsla[0] + ", " + (hsla[1] * 100) + "%, " + (hsla[2] * 100) + "%, " + hsla[3];
}
/**
* Converts an array containing the R, G, B and alpha values to a css rgba function call.
*
* @param {number[]} rgba An array whose elements are the rgba components to encode into a css rgba function call.
* @returns {string} The css rgba function call.
*/
export function rgbaToCssRgba(rgba) {
return "rgba(" + Math.round(rgba[0]) + ", " + Math.round(rgba[1]) + ", " + Math.round(rgba[2]) + ", " + Math.round(rgba[3]) + ")";
}

View File

@ -1,7 +1,7 @@
/* eslint-disable no-undef */ /* eslint-disable no-undef */
import { expect } from "chai"; import { expect } from "chai";
import { describe } from "mocha"; import { describe } from "mocha";
import { cssRgbaToRgba, cssRgbToRgba, rgbaHexToRgba, parseColor, rgbaToHexRgba, rgbHexToRgba } from "../src/support/colors.js"; import { cssRgbaToRgba, cssRgbToRgba, rgbaHexToRgba, parseColorToRgba, rgbaToHexRgba, rgbHexToRgba, rgbaToHsla, hslaToRgba, cssHslaToHsla, hslaToCssHsla } from "../src/support/colors.js";
describe("Color utilities", function () { describe("Color utilities", function () {
describe("the hex to rgba conversion function", function () { describe("the hex to rgba conversion function", function () {
@ -83,6 +83,11 @@ describe("Color utilities", function () {
const rgba = cssRgbToRgba("rgb(1,1,1)"); const rgba = cssRgbToRgba("rgb(1,1,1)");
expect(rgba).to.have.ordered.members([1, 1, 1, 255]); expect(rgba).to.have.ordered.members([1, 1, 1, 255]);
}); });
it("returns 1, 1 and 1 for r, g and b given \"rgb(1, 1, 1)\" (contains spaces)", function () {
const rgba = cssRgbToRgba("rgb(1, 1, 1)");
expect(rgba).to.have.ordered.members([1, 1, 1, 255]);
});
}); });
describe("The function to convert r, g, b, and a represented as an array to hexadecimals", function () { describe("The function to convert r, g, b, and a represented as an array to hexadecimals", function () {
@ -106,22 +111,72 @@ describe("Color utilities", function () {
describe("The function that automatically converts a string containing an rgb(a) value to an rgba array.", function () { describe("The function that automatically converts a string containing an rgb(a) value to an rgba array.", function () {
it("returns 1, 1, 1 and 1 for r, g, b and a given \"rgba(1,1,1,1)\"", function () { it("returns 1, 1, 1 and 1 for r, g, b and a given \"rgba(1,1,1,1)\"", function () {
const rgba = parseColor("rgba(1,1,1,1)"); const rgba = parseColorToRgba("rgba(1,1,1,1)");
expect(rgba).to.have.ordered.members([1, 1, 1, 1]); expect(rgba).to.have.ordered.members([1, 1, 1, 1]);
}); });
it("returns 0, 1, 0 and 1 for r, g, b and a given \"#00010001\"", function () { it("returns 0, 1, 0 and 1 for r, g, b and a given \"#00010001\"", function () {
const rgb = parseColor("#00010001"); const rgb = parseColorToRgba("#00010001");
expect(rgb).to.have.ordered.members([0, 1, 0, 1]); expect(rgb).to.have.ordered.members([0, 1, 0, 1]);
}); });
it("returns 1, 0, 1 and 255 for r, g, b and a given \"#10001\"", function () { it("returns 1, 0, 1 and 255 for r, g, b and a given \"#10001\"", function () {
const rgb = parseColor("#10001"); const rgb = parseColorToRgba("#10001");
expect(rgb).to.have.ordered.members([1, 0, 1, 255]); expect(rgb).to.have.ordered.members([1, 0, 1, 255]);
}); });
it("returns 1, 1, 1 and 255 for r, g, b and a given \"rgb(1,1,1)\"", function () { it("returns 1, 1, 1 and 255 for r, g, b and a given \"rgb(1,1,1)\"", function () {
const rgba = parseColor("rgb(1,1,1)"); const rgba = parseColorToRgba("rgb(1,1,1)");
expect(rgba).to.have.ordered.members([1, 1, 1, 255]); expect(rgba).to.have.ordered.members([1, 1, 1, 255]);
}); });
it("returns 1, 1, 1 and 1 for r, g, b and a given \"rgba(1, 1, 1, 1)\" (contains spaces)", function () {
const rgba = parseColorToRgba("rgba(1, 1, 1, 1)");
expect(rgba).to.have.ordered.members([1, 1, 1, 1]);
});
}); });
describe("The function that converts a rgba array to hsl and a values.", function () {
it("returns (approx.) 193, 100% and 50% for H, S, and V components given [0, 200, 255, 255]", function () {
const [h, s, v, a] = rgbaToHsla([0, 200, 255, 255]);
expect(h).to.be.closeTo(193, 0.1);
expect(s).to.equal(1);
expect(v).to.equal(0.5);
expect(a).to.equal(255);
});
it("returns (approx.) 120, 100% and 70% for H, S, and V components given [102, 255, 102, 255]", function () {
const [h, s, v, a] = rgbaToHsla([102, 255, 102, 255]);
expect(h).to.be.closeTo(120, 0.1);
expect(s).to.be.closeTo(1, 0.0001);
expect(v).to.equal(0.7);
expect(a).to.equal(255);
});
});
describe("The function that converts a hsva array to rgba values.", function () {
it("Returns (approx.) 0, 200, 255 and 255 for r, g, b and a given 193, 100%, 50% and 255.", function () {
const [r, g, b, a] = hslaToRgba([193, 1, 0.5, 255]);
expect(r).to.be.closeTo(0, 1);
expect(g).to.be.closeTo(200, 1);
expect(b).to.be.closeTo(255, 1);
expect(a).to.equal(255);
});
it("Returns 61, 71, 77 and 255 for r, g, b and a given 200, 20%, 30% and 255.", function () {
const [r, g, b, a] = hslaToRgba([203, 0.116, 0.271, 255]);
expect(r).to.be.closeTo(61, 0.2);
expect(g).to.be.closeTo(71, 0.2);
expect(b).to.be.closeTo(77, 0.2);
expect(a).to.equal(255);
});
});
describe("The function that converts an css hsl function call to an array containing the individual h, s, l and a components.", function () {
it("Returns [1, 0.01, 0.01, 1] given \"hsl(1, 1%, 1%, 1)\"", function () {
const hsla = cssHslaToHsla("hsl(1, 1%, 1%, 1)");
expect(hsla).to.have.ordered.members([1, 0.01, 0.01, 1]);
});
});
describe("The function that converts an array representing hsla values to a css hsl function call.", function () {
it("Returns \"hsl(1, 1%, 1%, 1)\" given [1, 0.01, 0.01, 1].", function () {
const cssHsla = hslaToCssHsla([1, 0.01, 0.01, 1]);
expect(cssHsla).to.equal("hsl(1, 1%, 1%, 1");
});
});
}); });