From a9f5afa04ebaf72129b9407b61f2e9f94b8754c8 Mon Sep 17 00:00:00 2001 From: dontwanttothink Date: Tue, 28 Jul 2026 17:51:37 -0500 Subject: [PATCH 1/5] Interpolate colours in the current colorMode Or in another specified space --- src/color/creating_reading.js | 16 +++++++++++++++- src/color/p5.Color.js | 24 ++++++++---------------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/color/creating_reading.js b/src/color/creating_reading.js index 2cf40ce596..68b7206350 100644 --- a/src/color/creating_reading.js +++ b/src/color/creating_reading.js @@ -1756,8 +1756,22 @@ function creatingReading(p5, fn) { * @return {p5.Color} interpolated color. */ fn.lerpColor = function (c1, c2, amt) { + let outputMode = this._renderer.states.colorMode; + let lerpMode = outputMode; + + if (typeof amt === 'object') { + // Passing in options object + outputMode = amt.outputMode ?? outputMode; + lerpMode = amt.lerpMode ?? lerpMode; + amt = amt.amount; + } + // p5._validateParameters('lerpColor', arguments); - return c1.lerp(c2, amt, this._renderer.states.colorMode); + return c1.lerp(c2, { + amount: amt, + lerpMode, + outputMode + }); }; /** diff --git a/src/color/p5.Color.js b/src/color/p5.Color.js index ea852ac80a..2fedb173fb 100644 --- a/src/color/p5.Color.js +++ b/src/color/p5.Color.js @@ -319,25 +319,17 @@ class Color { return this._array; } - lerp(color, amt, mode) { - // Find the closest common ancestor color space - let spaceIndex = -1; - while ( - (spaceIndex + 1 < this._color.space.path.length || - spaceIndex + 1 < color._color.space.path.length) && - this._color.space.path[spaceIndex + 1] === - color._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'); + lerp(color, amt, mode){ + let lerpMode = mode; + if (typeof amt === 'object') { + // Passing in options object + mode = amt.outputMode; + lerpMode = amt.lerpMode; + amt = amt.amount; } const obj = range(this._color, color._color, { - space: this._color.space.path[spaceIndex].id + space: Color.colorMap[lerpMode] })(amt); return new Color(obj, mode || this.mode); From 7a4716769eeb2c2b94ca3af72bc407d9098293cf Mon Sep 17 00:00:00 2001 From: dontwanttothink Date: Tue, 28 Jul 2026 17:58:51 -0500 Subject: [PATCH 2/5] Add documentation --- src/color/creating_reading.js | 69 ++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/src/color/creating_reading.js b/src/color/creating_reading.js index 68b7206350..11999c3617 100644 --- a/src/color/creating_reading.js +++ b/src/color/creating_reading.js @@ -1681,7 +1681,7 @@ function creatingReading(p5, fn) { * interval [0, 1] will produce strange and unexpected colors. * * The way that colors are interpolated depends on the current - * colorMode(). + * colorMode() or on the passed `options` object. * * ```js example * function setup() { @@ -1720,6 +1720,63 @@ function creatingReading(p5, fn) { * } * ``` * + * Normally, colors are interpolated according to the current `colorMode()`. But you can override this by + * specifying a different color space to perform the interpolation in. Different color spaces will blend + * colors differently. + * + * ```js example + * function setup() { + * createCanvas(100, 100); + * + * background(200); + * + * // Create p5.Color objects to interpolate between. + * colorMode(HSL); + * let from = color(240, 100, 25); + * let to = color('white'); + * + * // Create intermediate colors + * let interA = lerpColor(from, to, 0.33); + * let interB = lerpColor(from, to, 0.66); + * + * let inter1 = lerpColor(from, to, { + * amount: 0.33, + * lerpMode: HSB // interpolate in the HSB color space + * }); + * let inter2 = lerpColor(from, to, { + * amount: 0.66, + * lerpMode: HSB + * }); + * + * // Draw the left rectangle. + * noStroke(); + * fill(from); + * rect(10, 20, 20, 60); + * + * // Draw the left-center rectangles. + * fill(interA); + * rect(30, 20, 20, 30); + * + * fill(inter1); + * rect(30, 50, 20, 30); + * + * // Draw the right-center rectangles. + * fill(interB); + * rect(50, 20, 20, 30); + * + * fill(inter2); + * rect(50, 50, 20, 30); + * + * // Draw the right rectangle. + * fill(to); + * rect(70, 20, 20, 60); + * + * describe( + * 'Two strips of four colors each. From left to right, the top strip begins with dark blue, followed by a lighter blue, a light blue and finally white. Meanwhile, the bottom strip begins with a dark blue, followed by purple, then pink and finally white.' + * ); + * } + * ``` + * * In p5.strands shader callbacks, `lerpColor()` interpolates between * `vec4` colors and returns a normalized `vec4` with RGBA components in * the 0–1 range. `colorMode()` has no effect inside shader callbacks. @@ -1755,6 +1812,16 @@ function creatingReading(p5, fn) { * @param {Number} amt number between 0 and 1. * @return {p5.Color} interpolated color. */ + /** + * @method lerpColor + * @param {p5.Color} c1 interpolate from this color. + * @param {p5.Color} c2 interpolate to this color. + * @param {Object} options interpolation options. + * @param {number} [options.amount] a number between 0 and 1. + * @param {RGB|HSB|HSL|RGBP3|HWB|LAB|LCH|OKLAB|OKLCH} [options.outputMode] the desired output color mode. + * @param {RGB|HSB|HSL|RGBP3|HWB|LAB|LCH|OKLAB|OKLCH} [options.lerpMode] the color mode (space) to perform the interpolation in. + * @return {p5.Color} interpolated color. + */ fn.lerpColor = function (c1, c2, amt) { let outputMode = this._renderer.states.colorMode; let lerpMode = outputMode; From 72837d2269a6d09b304c4ff69ca6e588588e593d Mon Sep 17 00:00:00 2001 From: dontwanttothink Date: Tue, 28 Jul 2026 19:00:45 -0500 Subject: [PATCH 3/5] Update generated parameter data --- docs/parameterData.json | 205 ++++++++++++++++++++-------------------- 1 file changed, 105 insertions(+), 100 deletions(-) diff --git a/docs/parameterData.json b/docs/parameterData.json index bee74cb5d6..4f6e2f7413 100644 --- a/docs/parameterData.json +++ b/docs/parameterData.json @@ -243,7 +243,7 @@ [] ] }, - "mult": { + "multiply": { "overloads": [ [ "p5.Quat?" @@ -766,15 +766,6 @@ ] ] }, - "saveGif": { - "overloads": [ - [ - "String", - "Number", - "Object?" - ] - ] - }, "saveCanvas": { "overloads": [ [ @@ -788,6 +779,15 @@ ] ] }, + "saveGif": { + "overloads": [ + [ + "String", + "Number", + "Object?" + ] + ] + }, "resetMatrix": { "overloads": [ [] @@ -1050,29 +1050,6 @@ ] ] }, - "color": { - "overloads": [ - [ - "Number", - "Number?" - ], - [ - "Number", - "Number", - "Number", - "Number?" - ], - [ - "String" - ], - [ - "Number[]" - ], - [ - "p5.Color" - ] - ] - }, "atan": { "overloads": [ [ @@ -1153,6 +1130,29 @@ ] ] }, + "color": { + "overloads": [ + [ + "Number", + "Number?" + ], + [ + "Number", + "Number", + "Number", + "Number?" + ], + [ + "String" + ], + [ + "Number[]" + ], + [ + "p5.Color" + ] + ] + }, "createDiv": { "overloads": [ [ @@ -1356,13 +1356,6 @@ ] ] }, - "red": { - "overloads": [ - [ - "p5.Color|Number[]|String" - ] - ] - }, "createSpan": { "overloads": [ [ @@ -1415,6 +1408,13 @@ [] ] }, + "red": { + "overloads": [ + [ + "p5.Color|Number[]|String" + ] + ] + }, "cos": { "overloads": [ [ @@ -1535,13 +1535,6 @@ ] ] }, - "green": { - "overloads": [ - [ - "p5.Color|Number[]|String" - ] - ] - }, "bezierVertex": { "overloads": [ [ @@ -1757,6 +1750,13 @@ ] ] }, + "green": { + "overloads": [ + [ + "p5.Color|Number[]|String" + ] + ] + }, "windowResized": { "overloads": [ [ @@ -1814,13 +1814,6 @@ ] ] }, - "blue": { - "overloads": [ - [ - "p5.Color|Number[]|String" - ] - ] - }, "createSlider": { "overloads": [ [ @@ -1931,13 +1924,6 @@ ] ] }, - "alpha": { - "overloads": [ - [ - "p5.Color|Number[]|String" - ] - ] - }, "endShape": { "overloads": [ [ @@ -2018,6 +2004,13 @@ ] ] }, + "blue": { + "overloads": [ + [ + "p5.Color|Number[]|String" + ] + ] + }, "updatePixels": { "overloads": [ [ @@ -2087,13 +2080,6 @@ ] ] }, - "hue": { - "overloads": [ - [ - "p5.Color|Number[]|String" - ] - ] - }, "unhex": { "overloads": [ [ @@ -2179,6 +2165,13 @@ [] ] }, + "alpha": { + "overloads": [ + [ + "p5.Color|Number[]|String" + ] + ] + }, "getURLPath": { "overloads": [ [] @@ -2369,18 +2362,18 @@ ] ] }, - "saturation": { + "model": { "overloads": [ [ - "p5.Color|Number[]|String" + "p5.Geometry", + "Number?" ] ] }, - "model": { + "hue": { "overloads": [ [ - "p5.Geometry", - "Number?" + "p5.Color|Number[]|String" ] ] }, @@ -2497,13 +2490,6 @@ ] ] }, - "brightness": { - "overloads": [ - [ - "p5.Color|Number[]|String" - ] - ] - }, "translate": { "overloads": [ [ @@ -2850,6 +2836,13 @@ [] ] }, + "saturation": { + "overloads": [ + [ + "p5.Color|Number[]|String" + ] + ] + }, "noLights": { "overloads": [ [] @@ -2862,13 +2855,6 @@ ] ] }, - "lightness": { - "overloads": [ - [ - "p5.Color|Number[]|String" - ] - ] - }, "mouseReleased": { "overloads": [ [ @@ -2938,15 +2924,6 @@ ] ] }, - "lerpColor": { - "overloads": [ - [ - "p5.Color", - "p5.Color", - "Number" - ] - ] - }, "createAudio": { "overloads": [ [ @@ -2965,18 +2942,17 @@ ] ] }, - "paletteLerp": { + "sqrt": { "overloads": [ [ - "[p5.Color|String|Number|Number[], Number][]", "Number" ] ] }, - "sqrt": { + "brightness": { "overloads": [ [ - "Number" + "p5.Color|Number[]|String" ] ] }, @@ -3074,6 +3050,13 @@ [] ] }, + "lightness": { + "overloads": [ + [ + "p5.Color|Number[]|String" + ] + ] + }, "doubleClicked": { "overloads": [ [ @@ -3151,6 +3134,20 @@ ] ] }, + "lerpColor": { + "overloads": [ + [ + "p5.Color", + "p5.Color", + "Number" + ], + [ + "p5.Color", + "p5.Color", + "Object" + ] + ] + }, "loadNormalShader": { "overloads": [ [ @@ -3165,6 +3162,14 @@ [] ] }, + "paletteLerp": { + "overloads": [ + [ + "[p5.Color|String|Number|Number[], Number][]", + "Number" + ] + ] + }, "baseNormalShader": { "overloads": [ [] From 081d05e8b4626c63fc707ef4184d2a6db70d517f Mon Sep 17 00:00:00 2001 From: dontwanttothink Date: Sun, 9 Aug 2026 10:21:42 -0500 Subject: [PATCH 4/5] Update colour interpolation unit tests I ran this script: ```ts import Color from "colorjs.io"; const $ = console.log; // The tests go: // fromColor = mockP5Prototype.color(218, 165, 32); // toColor = mockP5Prototype.color(72, 61, 139); // so create the same colours const fromColor = new Color("rgb(218 165 32)"); const toColor = new Color("rgb(72 61 139)"); $("HSL lerp test:"); $(String(fromColor.range(toColor, { space: "hsl", outputSpace: "hsl" })(0.33))); $(String(fromColor.range(toColor, { space: "hsl", outputSpace: "hsl" })(0.66))); $(); $("HSB lerp test:"); $(String(fromColor.range(toColor, { space: "hsv", // see https://en.wikipedia.org/wiki/HSL_and_HSV#:~:text=HSV%20stands%20for%20hue%2C%20saturation%2C%20and%20value%2C%20and%20is%20also%20often%20called%20HSB })(0.33))); $(String(fromColor.range(toColor, { space: "hsv", })(0.66))); const fromColorA = new Color(`rgb(218 165 32 / ${49 / 255})`); const toColorA = new Color(`rgb(72 61 139 / ${200 / 255})`); $(); $("HSLA lerp test:"); $(String(fromColorA.range(toColorA, { space: "hsl", })(0.33))); $(String(fromColorA.range(toColorA, { space: "hsl", })(0.66))); $(); $("HSBA lerp test"); $(String(fromColorA.range(toColorA, { space: "hsv", })(0.33))); $(String(fromColorA.range(toColorA, { space: "hsv", })(0.66))); ``` to get this output: ```css HSL lerp test: hsl(351.94 62.718% 45.784%) hsl(300.97 51.036% 42.549%) HSB lerp test: color(--hsv 351.94 75.683% 75.267%) color(--hsv 300.97 66.045% 65.043%) HSLA lerp test: hsl(351.94 62.718% 45.784% / 0.38757) hsl(300.97 51.036% 42.549% / 0.58298) HSBA lerp test color(--hsv 351.94 75.683% 75.267% / 0.38757) color(--hsv 300.97 66.045% 65.043% / 0.58298) ``` --- test/unit/color/creating_reading.js | 46 ++++++++++++++--------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/test/unit/color/creating_reading.js b/test/unit/color/creating_reading.js index 50ca86501f..90b6343622 100644 --- a/test/unit/color/creating_reading.js +++ b/test/unit/color/creating_reading.js @@ -79,12 +79,12 @@ suite('color/CreatingReading', function () { var interA = mockP5Prototype.lerpColor(fromColor, toColor, 0.33); var interB = mockP5Prototype.lerpColor(fromColor, toColor, 0.66); - assert.closeTo(interA._color.coords[0], 37, 1); - assert.closeTo(interA._color.coords[1], 43, 1); + assert.closeTo(interA._color.coords[0], 352, 1); + assert.closeTo(interA._color.coords[1], 63, 1); assert.closeTo(interA._color.coords[2], 46, 1); - assert.closeTo(interB._color.coords[0], 345, 1); - assert.closeTo(interB._color.coords[1], 12, 1); + assert.closeTo(interB._color.coords[0], 301, 1); + assert.closeTo(interB._color.coords[1], 51, 1); assert.closeTo(interB._color.coords[2], 43, 1); }); @@ -94,13 +94,13 @@ suite('color/CreatingReading', function () { var interA = mockP5Prototype.lerpColor(fromColor, toColor, 0.33); var interB = mockP5Prototype.lerpColor(fromColor, toColor, 0.66); - assert.closeTo(interA._color.coords[0], 37, 1); - assert.closeTo(interA._color.coords[1], 60, 1); - assert.closeTo(interA._color.coords[2], 66, 1); + assert.closeTo(interA._color.coords[0], 352, 1); + assert.closeTo(interA._color.coords[1], 76, 1); + assert.closeTo(interA._color.coords[2], 75, 1); - assert.closeTo(interB._color.coords[0], 345, 1); - assert.closeTo(interB._color.coords[1], 20, 1); - assert.closeTo(interB._color.coords[2], 47, 1); + assert.closeTo(interB._color.coords[0], 301, 1); + assert.closeTo(interB._color.coords[1], 66, 1); + assert.closeTo(interB._color.coords[2], 65, 1); }); test.todo('should not extrapolate', function () { @@ -139,14 +139,14 @@ suite('color/CreatingReading', function () { var interA = mockP5Prototype.lerpColor(fromColor, toColor, 0.33); var interB = mockP5Prototype.lerpColor(fromColor, toColor, 0.66); - assert.closeTo(interA._color.coords[0], 37, 1); - assert.closeTo(interA._color.coords[1], 43, 1); + assert.closeTo(interA._color.coords[0], 352, 1); + assert.closeTo(interA._color.coords[1], 63, 1); assert.closeTo(interA._color.coords[2], 46, 1); - assert.closeTo(interA._color.alpha, 0.38, 0.01); + assert.closeTo(interA._color.alpha, 0.39, 0.01); - assert.closeTo(interB._color.coords[0], 345, 1); - assert.closeTo(interB._color.coords[1], 11, 1); - assert.closeTo(interB._color.coords[2], 42, 1); + assert.closeTo(interB._color.coords[0], 301, 1); + assert.closeTo(interB._color.coords[1], 51, 1); + assert.closeTo(interB._color.coords[2], 43, 1); assert.closeTo(interB._color.alpha, 0.58, 0.01); }); @@ -155,14 +155,14 @@ suite('color/CreatingReading', function () { var interA = mockP5Prototype.lerpColor(fromColor, toColor, 0.33); var interB = mockP5Prototype.lerpColor(fromColor, toColor, 0.66); - assert.closeTo(interA._color.coords[0], 37, 1); - assert.closeTo(interA._color.coords[1], 60, 1); - assert.closeTo(interA._color.coords[2], 66, 1); - assert.closeTo(interA._color.alpha, 0.38, 0.01); + assert.closeTo(interA._color.coords[0], 352, 1); + assert.closeTo(interA._color.coords[1], 76, 1); + assert.closeTo(interA._color.coords[2], 75, 1); + assert.closeTo(interA._color.alpha, 0.39, 0.01); - assert.closeTo(interB._color.coords[0], 345, 1); - assert.closeTo(interB._color.coords[1], 20, 1); - assert.closeTo(interB._color.coords[2], 47, 1); + assert.closeTo(interB._color.coords[0], 301, 1); + assert.closeTo(interB._color.coords[1], 66, 1); + assert.closeTo(interB._color.coords[2], 65, 1); assert.closeTo(interB._color.alpha, 0.58, 0.01); }); From 6144964ab29125b7a73a707d8c9417a0621334f6 Mon Sep 17 00:00:00 2001 From: dontwanttothink Date: Sun, 9 Aug 2026 10:25:08 -0500 Subject: [PATCH 5/5] Clarify fallback logic --- src/color/creating_reading.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/color/creating_reading.js b/src/color/creating_reading.js index 11999c3617..33aed4f46f 100644 --- a/src/color/creating_reading.js +++ b/src/color/creating_reading.js @@ -1823,8 +1823,10 @@ function creatingReading(p5, fn) { * @return {p5.Color} interpolated color. */ fn.lerpColor = function (c1, c2, amt) { - let outputMode = this._renderer.states.colorMode; - let lerpMode = outputMode; + const defaultMode = this._renderer.states.colorMode; + + let outputMode = defaultMode; + let lerpMode = defaultMode; if (typeof amt === 'object') { // Passing in options object