diff --git a/package-lock.json b/package-lock.json index d4b2181b8f..fe4b75259a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "acorn": "^8.12.1", "acorn-walk": "^8.3.4", "colorjs.io": "^0.5.2", + "culori": "^4.0.1", "file-saver": "^1.3.8", "gifenc": "^1.0.3", "libtess": "^1.2.2", @@ -3764,6 +3765,15 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/culori": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/culori/-/culori-4.0.1.tgz", + "integrity": "sha512-LSnjA6HuIUOlkfKVbzi2OlToZE8OjFi667JWN9qNymXVXzGDmvuP60SSgC+e92sd7B7158f7Fy3Mb6rXS5EDPw==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/data-uri-to-buffer": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", diff --git a/package.json b/package.json index 37c0804100..92409ef831 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "acorn": "^8.12.1", "acorn-walk": "^8.3.4", "colorjs.io": "^0.5.2", + "culori": "^4.0.1", "file-saver": "^1.3.8", "gifenc": "^1.0.3", "libtess": "^1.2.2", @@ -84,4 +85,4 @@ "pre-commit": "lint-staged" } } -} \ No newline at end of file +} diff --git a/preview/index.html b/preview/index.html index deb1e21e11..41ae6acf1c 100644 --- a/preview/index.html +++ b/preview/index.html @@ -1,41 +1,56 @@ - - - P5 test - - - - - - - - - - - - \ No newline at end of file + new p5(sketch); + + + diff --git a/src/color/creating_reading.js b/src/color/creating_reading.js index cda1d9bfcc..dbb75b93e4 100644 --- a/src/color/creating_reading.js +++ b/src/color/creating_reading.js @@ -6,10 +6,11 @@ * @requires constants */ -import './p5.Color'; -import { range } from 'colorjs.io/fn'; +import "./p5.Color"; +import { range } from "colorjs.io/fn"; +import { interpolate, parse, converter } from "culori"; -function creatingReading(p5, fn){ +function creatingReading(p5, fn) { /** * Gets the alpha (transparency) value of a color. * @@ -106,8 +107,8 @@ function creatingReading(p5, fn){ * * */ - fn.alpha = function(c) { - p5._validateParameters('alpha', arguments); + fn.alpha = function (c) { + p5._validateParameters("alpha", arguments); return this.color(c)._getAlpha(); }; @@ -242,8 +243,8 @@ function creatingReading(p5, fn){ * * */ - fn.blue = function(c) { - p5._validateParameters('blue', arguments); + fn.blue = function (c) { + p5._validateParameters("blue", arguments); return this.color(c)._getBlue(); }; @@ -387,8 +388,8 @@ function creatingReading(p5, fn){ * * */ - fn.brightness = function(c) { - p5._validateParameters('brightness', arguments); + fn.brightness = function (c) { + p5._validateParameters("brightness", arguments); return this.color(c)._getBrightness(); }; @@ -676,8 +677,8 @@ function creatingReading(p5, fn){ * @param {p5.Color} color * @return {p5.Color} */ - fn.color = function(...args) { - p5._validateParameters('color', args); + fn.color = function (...args) { + p5._validateParameters("color", args); if (args[0] instanceof p5.Color) { return args[0]; // Do nothing if argument is already a color object. } @@ -817,8 +818,8 @@ function creatingReading(p5, fn){ * * */ - fn.green = function(c) { - p5._validateParameters('green', arguments); + fn.green = function (c) { + p5._validateParameters("green", arguments); return this.color(c)._getGreen(); }; @@ -938,8 +939,8 @@ function creatingReading(p5, fn){ * * */ - fn.hue = function(c) { - p5._validateParameters('hue', arguments); + fn.hue = function (c) { + p5._validateParameters("hue", arguments); return this.color(c)._getHue(); }; @@ -1002,34 +1003,120 @@ function creatingReading(p5, fn){ * * */ - fn.lerpColor = function(c1, c2, amt) { - p5._validateParameters('lerpColor', arguments); - // Find the closest common ancestor color space - let spaceIndex = -1; - while( - ( - spaceIndex+1 < c1.color.space.path.length || - spaceIndex+1 < c2.color.space.path.length - ) && - c1.color.space.path[spaceIndex+1] === c2.color.space.path[spaceIndex+1] - ){ - spaceIndex += 1; - } + // fn.lerpColor = function(c1, c2, amt) { + // p5._validateParameters('lerpColor', arguments); + + // // Find the closest common ancestor color space + // let spaceIndex = -1; + // while( + // ( + // spaceIndex+1 < c1.color.space.path.length || + // spaceIndex+1 < c2.color.space.path.length + // ) && + // c1.color.space.path[spaceIndex+1] === c2.color.space.path[spaceIndex+1] + // ){ + // spaceIndex += 1; + // } + + // if (spaceIndex === -1) { + // // This probably will not occur in practice + // throw new Error('Cannot lerp colors. No common color space found'); + // } + + // // Get lerp value as a color in the common ancestor color space + // const lerpColor = range(c1.color, c2.color, { + // space: c1.color.space.path[spaceIndex].id + // })(amt); + + // return new p5.Color(lerpColor, this._colorMode, this._colorMaxes); + // }; + + fn.lerpColor = function (c1, c2, amt) { + p5._validateParameters("lerpColor", arguments); + + // Check if both colors are in the same color space + const mode1 = c1.color.mode; + console.log("ColorMode 1 is:", mode1); + const mode2 = c2.color.mode; + console.log("ColorMode 2 is:", mode2); + + let interpolator; - if (spaceIndex === -1) { - // This probably will not occur in practice - throw new Error('Cannot lerp colors. No common color space found'); + if (mode1 === mode2) { + // If they are in the same color space, interpolate directly in that space + interpolator = interpolate([c1.color, c2.color], mode1); + } else { + // If not in the same space, convert `c2` to `c1`'s color space for interpolation + const convertedC2 = converter(mode1)(c2.color); + interpolator = interpolate([c1.color, convertedC2], mode1); } - // Get lerp value as a color in the common ancestor color space - const lerpColor = range(c1.color, c2.color, { - space: c1.color.space.path[spaceIndex].id - })(amt); + // Interpolate the color based on amt + const lerpColor = interpolator(amt); + // Return the new p5.Color object with the interpolated color return new p5.Color(lerpColor, this._colorMode, this._colorMaxes); }; + /** + * Blends multiple colors to find a color between them. + * + * The `amt` parameter specifies the amount to interpolate between the color + * stops which are colors at each `amt` value "location" with `amt` values + * that are between 2 color stops interpolating between them based on its relative + * distance to both. + * + * The way that colors are interpolated depends on the current + * colorMode(). + * + * @method paletteLerp + * @param {[p5.Color, Number][]} colors_stops color stops to interpolate from + * @param {Number} amt number to use to interpolate relative to color stops + * @return {p5.Color} interpolated color. + * + * @example + *
+ * + * function setup() { + * createCanvas(400, 400); + * } + * + * function draw() { + * // The background goes from white to red to green to blue fill + * background(paletteLerp([ + * ['white', 0], + * ['red', 0.05], + * ['green', 0.25], + * ['blue', 1] + * ], millis() / 10000 % 1)); + * } + * + *
+ */ + + fn.paletteLerp = function (color_stops, amt) { + const first_color_stop = color_stops[0]; + console.log ("This is first color stop", first_color_stop); + + if (amt < first_color_stop[1]) + return this.color(first_color_stop[0]); + + for (let i = 1; i < color_stops.length; i++) { + const color_stop = color_stops[i]; + if (amt < color_stop[1]) { + const prev_color_stop = color_stops[i - 1]; + return this.lerpColor( + this.color(prev_color_stop[0]), + this.color(color_stop[0]), + (amt - prev_color_stop[1]) / (color_stop[1] - prev_color_stop[1]) + ); + } + } + + return this.color(color_stops[color_stops.length - 1][0]); + }; + /** * Gets the lightness value of a color. * @@ -1170,8 +1257,8 @@ function creatingReading(p5, fn){ * * */ - fn.lightness = function(c) { - p5._validateParameters('lightness', arguments); + fn.lightness = function (c) { + p5._validateParameters("lightness", arguments); return this.color(c)._getLightness(); }; @@ -1306,8 +1393,8 @@ function creatingReading(p5, fn){ * * */ - fn.red = function(c) { - p5._validateParameters('red', arguments); + fn.red = function (c) { + p5._validateParameters("red", arguments); return this.color(c)._getRed(); }; @@ -1483,14 +1570,14 @@ function creatingReading(p5, fn){ * * */ - fn.saturation = function(c) { - p5._validateParameters('saturation', arguments); + fn.saturation = function (c) { + p5._validateParameters("saturation", arguments); return this.color(c)._getSaturation(); }; } export default creatingReading; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== "undefined") { creatingReading(p5, p5.prototype); } diff --git a/src/color/p5.Color.js b/src/color/p5.Color.js index ab4b3f13e6..f31729c8c6 100644 --- a/src/color/p5.Color.js +++ b/src/color/p5.Color.js @@ -7,139 +7,182 @@ * @requires color_conversion */ -import * as constants from '../core/constants'; +import * as constants from "../core/constants"; +// import { +// ColorSpace, +// to, +// // toGamut, +// serialize, +// parse, +// // range, + +// XYZ_D65, +// sRGB_Linear, +// sRGB, +// HSL, +// HSV, +// HWB, + +// XYZ_D50, +// Lab, +// LCH, + +// OKLab, +// OKLCH, + +// P3_Linear, +// P3, + +// A98RGB_Linear, +// A98RGB +// } from 'colorjs.io/fn'; + +//for CSS string parsing +//import 'culori/css'; + +//importing culori/fn to optimize bundle size and +//'opting into' the tree-shakable version +// handle operations and conversions within color spaces import { - ColorSpace, - to, - // toGamut, - serialize, - parse, - // range, - - XYZ_D65, - sRGB_Linear, - sRGB, - HSL, - HSV, - HWB, - - XYZ_D50, - Lab, - LCH, - - OKLab, - OKLCH, - - P3_Linear, - P3, - - A98RGB_Linear, - A98RGB -} from 'colorjs.io/fn'; -import HSB from './color_spaces/hsb.js'; - -ColorSpace.register(XYZ_D65); -ColorSpace.register(sRGB_Linear); -ColorSpace.register(sRGB); -ColorSpace.register(HSL); -ColorSpace.register(HSV); -ColorSpace.register(HWB); -ColorSpace.register(HSB); - -ColorSpace.register(XYZ_D50); -ColorSpace.register(Lab); -ColorSpace.register(LCH); - -ColorSpace.register(OKLab); -ColorSpace.register(OKLCH); + //converter, // string or color objects -> return object + //parse, // string -> return object + useMode, + modeHsl, + modeHwb, + modeHsv, + modeOklab, + modeOklch, + modeP3, + modeRgb, +} from "culori/fn"; -ColorSpace.register(P3_Linear); -ColorSpace.register(P3); - -ColorSpace.register(A98RGB_Linear); -ColorSpace.register(A98RGB); +import { + converter, + parse, + formatHex, + formatHex8, + formatRgb, + formatHsl, + formatCss, +} from "culori"; + +//Registering color spaces with useMode() +const hsl = useMode(modeHsl); +const hwb = useMode(modeHwb); +const oklab = useMode(modeOklab); +const oklch = useMode(modeOklch); +const p3 = useMode(modeP3); +const rgb = useMode(modeRgb); +const hsv = useMode(modeHsv); class Color { color; maxes; mode; - constructor(vals, colorMode='rgb', colorMaxes={rgb: [255, 255, 255, 255]}) { - // This changes with the sketch's setting - // NOTE: Maintaining separate maxes for different color space is awkward. - // Consider just one universal maxes. - // this.maxes = pInst._colorMaxes; - this.maxes = colorMaxes; - // This changes with the color object - // this.mode = pInst._colorMode; + constructor( + vals, + colorMode = "rgb", + colorMaxes = { rgb: [255, 255, 255, 255] , hsb: [360, 100, 100, 1] } + ) { + //storing default rgb color mode and maxes this.mode = colorMode; + this.maxes = colorMaxes; - if (typeof vals === 'object' && !Array.isArray(vals) && vals !== null){ - this.color = vals; - } else if(typeof vals[0] === 'string') { - try{ - // NOTE: this will not necessarily have the right color mode - this.color = parse(vals[0]); - }catch(err){ + //if vals is an object or a non-array, non-null object + if (typeof vals === "object" && !Array.isArray(vals) && vals !== null) { + this.color = vals; //color-compatible format + console.log("Color object detected:", this.color); + console.log("Current color mode:", this.color.mode); + } else if (typeof vals[0] === "string") { + try { + // parse the string + //this.color = parse(vals[0]); + let colorString = vals[0].trim(); + console.log("Original color string:", colorString); // Debugging + // Preprocess string to replace 'hsb' or 'hsba' with 'hsv' or 'hsva' for Culori compatibility + if (colorString.toLowerCase().startsWith("hsba(")) { + colorString = colorString.replace(/hsba/i, "hsva"); + } else if (colorString.toLowerCase().startsWith("hsb(")) { + colorString = colorString.replace(/hsb/i, "hsv"); + } + + console.log("Modified color string for parsing:", colorString); // Debugging + // Parse the modified string + this.color = parse(colorString); + console.log("Color object detected:", this.color); + console.log("Current color mode:", this.color.mode); + console.log("Current alpha:", this.color.alpha); + } catch (err) { // TODO: Invalid color string - console.error('Invalid color string'); + console.error("Invalid color string format:", vals[0], "\nError:", err); } - - }else{ + } else { let alpha; - if(vals.length === 4){ - alpha = vals[vals.length-1]; - }else if (vals.length === 2){ + if (vals.length === 4) { + alpha = vals[vals.length - 1]; + } else if (vals.length === 2) { alpha = vals[1]; vals = [vals[0], vals[0], vals[0]]; - }else if(vals.length === 1){ + } else if (vals.length === 1) { vals = [vals[0], vals[0], vals[0]]; } - alpha = alpha !== undefined - ? alpha / this.maxes[this.mode][3] - : 1; + alpha = alpha !== undefined ? alpha / this.maxes[this.mode][3] : 1; + + console.log("Color object detected:", vals); + console.log("Current color mode:", this.mode); // _colorMode can be 'rgb', 'hsb', or 'hsl' - // These should map to color.js color space - let space = 'srgb'; - let coords = vals; - switch(this.mode){ - case 'rgb': - space = 'srgb'; - coords = [ - vals[0] / this.maxes[this.mode][0], - vals[1] / this.maxes[this.mode][1], - vals[2] / this.maxes[this.mode][2] - ]; + // Color representation for Culori.js + + switch (this.mode) { + case "rgb": + this.color = { + mode: "rgb", + r: vals[0] / this.maxes[this.mode][0], // R /255 -> Range [0 , 1] + g: vals[1] / this.maxes[this.mode][1], // G /255 + b: vals[2] / this.maxes[this.mode][2], // B /255 + alpha: alpha, + }; break; - case 'hsb': - // TODO: need implementation - space = 'hsb'; - coords = [ - vals[0] / this.maxes[this.mode][0] * 360, - vals[1] / this.maxes[this.mode][1] * 100, - vals[2] / this.maxes[this.mode][2] * 100 - ]; + case "hsv": + this.color = { + mode: "hsv", // Culori uses "hsv" instead of "hsb" + h: (vals[0] / this.maxes[this.mode][0]) * 360, //(H/360) * 360 -> Range [0, 360] + s: vals[1] / this.maxes[this.mode][1], //S /100 [0,1] + v: vals[2] / this.maxes[this.mode][2], //V /100 [0,1] + alpha: alpha, + }; + break; - case 'hsl': - space = 'hsl'; - coords = [ - vals[0] / this.maxes[this.mode][0] * 360, - vals[1] / this.maxes[this.mode][1] * 100, - vals[2] / this.maxes[this.mode][2] * 100 - ]; + case "hsl": + this.color = { + mode: "hsl", + h: (vals[0] / this.maxes[this.mode][0]) * 360, //(H/360) * 360 ->Range [0, 360] + s: vals[1] / this.maxes[this.mode][1], //S / 100 [0,1] + l: vals[2] / this.maxes[this.mode][2], //L / 100 [0,1] + alpha: alpha, + }; break; default: - console.error('Invalid color mode'); + console.error("Invalid color mode"); } - const color = { - space, - coords, - alpha - }; - this.color = to(color, space); + // // Use converter to ensure compatibility with Culori's color space conversion + //const convertToSpace = converter(this.mode); + //this.color = convertToSpace(this.color); // Convert color to specified space + + // Convert 'hsb' to 'hsv' and 'hsba' to 'hsva' for compatibility with Culori + const convertToSpace = converter( + this.mode === "hsb" || this.mode === "hsba" + ? this.mode === "hsb" + ? "hsv" + : "hsva" + : this.mode + ); + this.color = convertToSpace(this.color); // Convert color to specified space + //console.log("Converted color object:", this.color); } } @@ -184,11 +227,32 @@ class Color { * * */ + // toString(format) { + // // NOTE: memoize + // return serialize(this.color, { + // format + // }); + // } + + //Culori.js doesn't have a single serialize function + //it rather has the following methods to serialize + //colours to strings in various formats toString(format) { - // NOTE: memoize - return serialize(this.color, { - format - }); + switch (format) { + case "hex": //cover cases #rgb | #rgba | #rrggbb + return formatHex(this.color); + case "hex8": //cover case #rrggbbaa + return formatHex8(this.color); + case "rgb": //cover cases rgb and rgba + return formatRgb(this.color); + case "hsl": //cover case hsl and hsla + return formatHsl(this.color); + case "css": //returns color mode and normalized color values + return formatCss(this.color); + default: + // Fallback to a default format, like RGB + return formatRgb(this.color); + } } /** @@ -228,15 +292,20 @@ class Color { * */ setRed(new_red) { - const red_val = new_red / this.maxes[constants.RGB][0]; - if(this.mode === constants.RGB){ - this.color.coords[0] = red_val; - }else{ - // Will do an imprecise conversion to 'srgb', not recommended - const space = this.color.space.id; - const representation = to(this.color, 'srgb'); - representation.coords[0] = red_val; - this.color = to(representation, space); + const red_val = new_red / this.maxes[constants.RGB][0]; //normalize red value + if (this.mode === constants.RGB) { + //this.color.coords[0] = red_val; + this.color.r = red_val; + } else { + // Handling Non-RGB Color Modes + //const space = this.color.space.id; + //const representation = to(this.color, 'srgb'); + //representation.coords[0] = red_val; + //this.color = to(representation, space); + const space = this.color.mode; + const representation = converter("rgb")(this.color); //temporarily convert to RGB + representation.r = red_val; //update red value + this.color = converter(space)(representation); //convert back to original space } } @@ -278,14 +347,19 @@ class Color { **/ setGreen(new_green) { const green_val = new_green / this.maxes[constants.RGB][1]; - if(this.mode === constants.RGB){ - this.color.coords[1] = green_val; - }else{ + if (this.mode === constants.RGB) { + //this.color.coords[1] = green_val; + this.color.g = green_val; + } else { // Will do an imprecise conversion to 'srgb', not recommended - const space = this.color.space.id; - const representation = to(this.color, 'srgb'); - representation.coords[1] = green_val; - this.color = to(representation, space); + //const space = this.color.space.id; + //const representation = to(this.color, 'srgb'); + //representation.coords[1] = green_val; + //this.color = to(representation, space); + const space = this.color.mode; + const representation = converter("rgb")(this.color); //temporarily convert to RGB + representation.g = green_val; //update green value + this.color = converter(space)(representation); //convert back to original space } } @@ -327,14 +401,19 @@ class Color { **/ setBlue(new_blue) { const blue_val = new_blue / this.maxes[constants.RGB][2]; - if(this.mode === constants.RGB){ - this.color.coords[2] = blue_val; - }else{ + if (this.mode === constants.RGB) { + //this.color.coords[2] = blue_val; + this.color.b = blue_val; + } else { // Will do an imprecise conversion to 'srgb', not recommended - const space = this.color.space.id; - const representation = to(this.color, 'srgb'); - representation.coords[2] = blue_val; - this.color = to(representation, space); + //const space = this.color.space.id; + //const representation = to(this.color, 'srgb'); + //representation.coords[2] = blue_val; + //this.color = to(representation, space); + const space = this.color.mode; + const representation = converter("rgb")(this.color); //temporarily convert to RGB + representation.b = blue_val; //update red value + this.color = converter(space)(representation); //convert back to original space } } @@ -377,32 +456,63 @@ class Color { **/ setAlpha(new_alpha) { this.color.alpha = new_alpha / this.maxes[this.mode][3]; + console.log("Updated alpha:", this.color.alpha); } _getRed() { - if(this.mode === constants.RGB){ - return this.color.coords[0] * this.maxes[constants.RGB][0]; - }else{ + if (this.mode === constants.RGB) { + console.log("The color mode is RGB"); + //Check if the color is in RGB format; otherwise, convert it to RGB + if (typeof this.color.r === "undefined") { + const rgbColor = converter("rgb")(this.color); //Convert to RGB + return rgbColor.r * this.maxes[constants.RGB][0]; //Access red channel and scale + } else { + //return this.color.coords[0] * this.maxes[constants.RGB][0]; + return this.color.r * this.maxes[constants.RGB][0]; + } + } else { // Will do an imprecise conversion to 'srgb', not recommended - return to(this.color, 'srgb').coords[0] * this.maxes[constants.RGB][0]; + //return to(this.color, 'srgb').coords[0] * this.maxes[constants.RGB][0]; + const rgbColor = converter("rgb")(this.color); //Convert to RGB + return rgbColor.r * this.maxes[constants.RGB][0]; //Access red channel and scale } } _getGreen() { - if(this.mode === constants.RGB){ - return this.color.coords[1] * this.maxes[constants.RGB][1]; - }else{ - // Will do an imprecise conversion to 'srgb', not recommended - return to(this.color, 'srgb').coords[1] * this.maxes[constants.RGB][1]; + if (this.mode === constants.RGB) { + console.log("The color mode is RGB"); + //Check if the color is in RGB format; otherwise, convert it to RGB + if (typeof this.color.g === "undefined") { + //Convert to RGB first, if `g` (green channel) is not directly accessible + const rgbColor = converter("rgb")(this.color); + return rgbColor.g * this.maxes[constants.RGB][1]; + } else { + //if color is already in RGB, just use it directly + return this.color.g * this.maxes[constants.RGB][1]; + } + } else { + console.log("The color mode is something else"); + const rgbColor = converter("rgb")(this.color); //Convert to RGB + return rgbColor.g * this.maxes[constants.RGB][1]; //Access green channel and scale } } _getBlue() { - if(this.mode === constants.RGB){ - return this.color.coords[2] * this.maxes[constants.RGB][2]; - }else{ - // Will do an imprecise conversion to 'srgb', not recommended - return to(this.color, 'srgb').coords[2] * this.maxes[constants.RGB][2]; + if (this.mode === constants.RGB) { + console.log("The color mode is RGB"); + // Check if the color is in RGB format; otherwise, convert it to RGB + if (typeof this.color.b === "undefined") { + // Convert to RGB first, if `b` (blue channel) is not directly accessible + const rgbColor = converter("rgb")(this.color); // Convert to RGB + return rgbColor.b * this.maxes[constants.RGB][2]; + } else { + // If color is already in RGB, just use it directly + return this.color.b * this.maxes[constants.RGB][2]; + } + } else { + console.log("The color mode is something else"); + const rgbColor = converter("rgb")(this.color); //Convert to RGB + return rgbColor.b * this.maxes[constants.RGB][2]; //Access blue channel and scale } } @@ -425,11 +535,19 @@ class Color { * otherwise. */ _getHue() { - if(this.mode === constants.HSB || this.mode === constants.HSL){ - return this.color.coords[0] / 360 * this.maxes[this.mode][0]; - }else{ + if (this.mode === constants.HSB || this.mode === constants.HSL) { + if (typeof this.color.h === "undefined"){ + const hslColor = converter("hsl")(this.color); + return (hslColor.h / 360) * this.maxes[this.mode][0]; + } else { + return (this.color.h / 360) * this.maxes[this.mode][0]; + } + } else { // Will do an imprecise conversion to 'HSL', not recommended - return to(this.color, 'hsl').coords[0] / 360 * this.maxes[this.mode][0]; + //return to(this.color, 'hsl').coords[0] / 360 * this.maxes[this.mode][0]; + const hslColor = converter("hsl")(this.color); //Convert to HSL + console.log("If color mode isn't HSB||HSL", hslColor); + return (hslColor.h / 360) * this.maxes[this.mode][0]; //Access Hue channel and scale } } @@ -438,43 +556,88 @@ class Color { * the HSB saturation when supplied with an HSB color object, but will default * to the HSL saturation otherwise. */ + + /**According to Culori.js The hue is identical across all color models in this + * family; however, the saturaton is computed differently in each. + * The saturation in HSL is not interchangeable with the saturation + * from HSV, nor HSI. */ + _getSaturation() { - if(this.mode === constants.HSB || this.mode === constants.HSL){ - return this.color.coords[1] / 100 * this.maxes[this.mode][1]; - }else{ + if (this.mode === constants.HSB || this.mode === constants.HSL) { + if (typeof this.color.s == "undefined"){ + //HSL or HSB colorMode, but input color is of different color space + //default to HSL colorMode for now + const hslColor = converter("hsl")(this.color); + return hslColor.s * this.maxes[this.mode][1]; + } else { + console.log("The color mode is HSL or HSB") + return this.color.s * this.maxes[this.mode][1]; + } + } else { // Will do an imprecise conversion to 'HSL', not recommended - return to(this.color, 'hsl').coords[1] / 100 * this.maxes[this.mode][1]; + const hslColor = converter("hsl")(this.color); //Convert to HSL + console.log("If color mode isn't HSB||HSL", hslColor); + return hslColor.s * this.maxes[this.mode][1]; //Access Hue channel and scale } } + //HSB/ HSV - Brightness channel _getBrightness() { - if(this.mode === constants.HSB){ - return this.color.coords[2] / 100 * this.maxes[this.mode][2]; - }else{ + if (this.mode === constants.HSB) { + //return this.color.coords[2] / 100 * this.maxes[this.mode][2]; + return this.color.v * this.maxes[this.mode][2]; + } else { // Will do an imprecise conversion to 'HSB', not recommended - return to(this.color, 'hsb').coords[2] / 100 * this.maxes[this.mode][2]; + //return to(this.color, 'hsb').coords[2] / 100 * this.maxes[this.mode][2]; + const hsbColor = converter("hsv")(this.color); //Convert to HSV + return hsbColor.v * this.maxes[this.mode][2]; //Access Brightness channel and scale } } + //HSL - Lightness channel _getLightness() { - if(this.mode === constants.HSL){ - return this.color.coords[2] / 100 * this.maxes[this.mode][2]; - }else{ - // Will do an imprecise conversion to 'HSB', not recommended - return to(this.color, 'hsl').coords[2] / 100 * this.maxes[this.mode][2]; + if (this.mode === constants.HSL) { + //return this.color.coords[2] / 100 * this.maxes[this.mode][2]; + console.log("The color mode is HSL", this.color) + return (this.color.l) * this.maxes[this.mode][2]; + } else { + // Will do an imprecise conversion to 'HSL', not recommended + //return to(this.color, 'hsl').coords[2] / 100 * this.maxes[this.mode][2]; + console.log("The color mode is not HSL") + const hslColor = converter("hsl")(this.color); //Convert to HSL + return hslColor.l * this.maxes[this.mode][2]; //Access Lightness channel and scale } } + // get _array() { + // return [...this.color.coords, this.color.alpha]; + // } + + //Since Culori.js uses plain objects with named properties, + //get_array would need to adjust to this structure. get _array() { - return [...this.color.coords, this.color.alpha]; + // For RGB mode, check for `r`, `g`, and `b`; for HSL, check for `h`, `s`, and `l`, etc. + const colorComponents = []; + + if (this.color.mode === "rgb") { + colorComponents.push(this.color.r, this.color.g, this.color.b); + } else if (this.color.mode === "hsl") { + colorComponents.push(this.color.h, this.color.s, this.color.l); + } else if (this.color.mode === "hsv") { + colorComponents.push(this.color.h, this.color.s, this.color.v); + } + // Add alpha, if present, or default to 1 if alpha is undefined + colorComponents.push(this.color.alpha !== undefined ? this.color.alpha : 1); + + return colorComponents; } get levels() { - return this._array.map(v => v * 255); + return this._array.map((v) => v * 255); } } -function color(p5, fn){ +function color(p5, fn) { /** * A class to describe a color. * @@ -506,8 +669,8 @@ function color(p5, fn){ } export default color; -export { Color } +export { Color }; -if(typeof p5 !== 'undefined'){ +if (typeof p5 !== "undefined") { color(p5, p5.prototype); }