Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a function for interpolating between multiple colors #6959

Closed
1 of 17 tasks
RandomGamingDev opened this issue Apr 11, 2024 · 14 comments · Fixed by #6960
Closed
1 of 17 tasks

Add a function for interpolating between multiple colors #6959

RandomGamingDev opened this issue Apr 11, 2024 · 14 comments · Fixed by #6960

Comments

@RandomGamingDev
Copy link
Contributor

Increasing access

While interpolating between 2 colors is easy with the lerpColor() function, interpolating between multiple can be annoying, and very difficult for beginners, especially for how commonly used and popular this feature is.

Most appropriate sub-area of p5.js?

  • Accessibility
  • Color
  • Core/Environment/Rendering
  • Data
  • DOM
  • Events
  • Image
  • IO
  • Math
  • Typography
  • Utilities
  • WebGL
  • Build process
  • Unit testing
  • Internationalization
  • Friendly errors
  • Other (specify if possible)

Feature request details

Create a function that accepts a list of colors and a value to interpolate between all of them.

@davepagurek
Copy link
Contributor

Sorry for the delay on this! I'm going to tag the color stewards, what are your thoughts? @paulaxisabel, @SoundaryaKoutharapu, @mrbrack, @TJ723, @Zarkv, @SkylerW99, @ramya202000, @hannahvy, @robin-haxx, @hiddenenigma

Also @limzykenneth, since you've been looking into color module things

@limzykenneth
Copy link
Member

I'm thinking it may be more useful to have a gradient implementation that gives extra flexibility for the placement of intermediate colors. With lerp, it is not really defined how lerping with multiple colors should work so I wouldn't necessarily try to invite something here. I had a look at Unity and it also only does the straightforward two colors lerp only, would be great to see how other tools/libraries handle this if they do it at all.

@davepagurek
Copy link
Contributor

One piece of inspiration: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/createLinearGradient

They don't have an API for getting a specific color along the gradient, so this API is for something somewhat different, but they have one for specifying color stops at different locations.

@RandomGamingDev
Copy link
Contributor Author

I'm thinking it may be more useful to have a gradient implementation that gives extra flexibility for the placement of intermediate colors

If the user's already specifying specific positions for each of the colors then using an if ... else ... chain would be more than easily enough for their task. The main issue that paletteLerp's trying to solve is when people want them equally spaced out without having to type out a bunch of extra code just to achieve the same result, especially if they don't have much coding experience and want a solution that works with a list of any size. I think that, while less flexible, paletteLerp still provides significant advantages and the users that require a more flexible solution already have that easily taken care of as well, with even beginners able to do it.

@davepagurek
Copy link
Contributor

I think stops are at least worth considering for the future. At least personally, probably half of the gradients I've done with more than two colors have had non-evenly-spaced stops in order to get the visual effect I'm looking for, and it's probably a concept familiar to people who start in digital design software and then get into coding via p5.

If the colors aren't evenly spaced out, e.g. white at 0, red at 5%, green at 25%, blue at 100%, doing a lookup could look something like this:

let c
if (mix < 0.5) {
  c = lerpColor(white, red, map(mix, 0, 0.05, 0, 1, true))
} else if (mix < 0.25) {
  c = lerpColor(red, green, map(mix, 0.05, 0.25, 0, 1, true))
} else {
  c = lerpColor(green, blue, map(mix, 0.25, 1, 0, 1, true))
}

I could see how having to jump from just lerpPalette([white, red, green, blue], mix) to the above would be a big jump for some, where people might request the ability to specify color stops. I think it's not a bad idea to think ahead about whether or not there could be an easy way in the future to extend it to that so that we aren't locking ourselves in.

One way could be to have overloads like:

lerpPalette(palette: p5.Color[], mix: number)
lerpPalette(palette: p5.Color[], stops: number[], mix: number)

...and default to an even spacing. Do you think something like that would be a good balance of achieving the evenly-spaced use case while letting us grow into the non-even use case? If so, we could start with the simpler one, and move into the latter whenever someone has the bandwidth to implement it.

@limzykenneth
Copy link
Member

Also in terms of implementation, instead of a new function, perhaps we can just implement overloading on lerpColor instead so if the first argument is an array, it lerps between all of them.

@RandomGamingDev
Copy link
Contributor Author

Do you think something like that would be a good balance of achieving the evenly-spaced use case while letting us grow into the non-even use case? If so, we could start with the simpler one, and move into the latter whenever someone has the bandwidth to implement it.

That sounds like a great idea although we might want to play around with the parameters being used a bit since it might be annoying for beginners to have 2 separate arrays that are connected to one another only through their index values especially since it can result in hard to debug bugs.

Also in terms of implementation, instead of a new function, perhaps we can just implement overloading on lerpColor instead so if the first argument is an array, it lerps between all of them.

That could be another possibility, but that would depend on which one p5.js prefers. Personally, I prefer having a separate function made for doing this, but making it an overload instead would be fine as well.

@davepagurek
Copy link
Contributor

That sounds like a great idea although we might want to play around with the parameters being used a bit since it might be annoying for beginners to have 2 separate arrays that are connected to one another only through their index values especially since it can result in hard to debug bugs.

Agreed about keeping the two array indices in sync being hard. One option could be to allow something like:

// evenly spaced
lerpPalette([white, red, green, blue], mix)

// with stops
lerpPalette([
  { color: white, stop: 0 },
  { color: red, stop: 0.05 },
  { color: green, stop: 0.25 },
  { color: blue, stop: 1 }
], mix)

I don't think we have array-of-object parameters for anything else just yet? but for this maybe it's worth trading the argument complexity for the potential bugs in keeping two arrays in sync?

@RandomGamingDev
Copy link
Contributor Author

That sounds like a great idea although we might want to play around with the parameters being used a bit since it might be annoying for beginners to have 2 separate arrays that are connected to one another only through their index values especially since it can result in hard to debug bugs.

Agreed about keeping the two array indices in sync being hard. One option could be to allow something like:

// evenly spaced
lerpPalette([white, red, green, blue], mix)

// with stops
lerpPalette([
  { color: white, stop: 0 },
  { color: red, stop: 0.05 },
  { color: green, stop: 0.25 },
  { color: blue, stop: 1 }
], mix)

I don't think we have array-of-object parameters for anything else just yet? but for this maybe it's worth trading the argument complexity for the potential bugs in keeping two arrays in sync?

I think that it'd probably be better to do something like this:

lerpPalette(
  red, 0.1,
  orange, 0.2,
  yellow, 0.4,
  green, 0.8,
  blue, 1.0,
  lerpVal
);

What do you think?

@davepagurek
Copy link
Contributor

Hey @nickmcintyre, in other issues you've been thinking about how p5 (and JS in general) deals with array parameters, what do you think of this one? is this a pattern that we've seen elsewhere?

@nickmcintyre
Copy link
Member

If I understand the original proposal, the core library probably doesn't need a way to mix multiple colors. I could be wrong, so it'd be interesting to see some example sketches.

+1 for considering color stops similar to createLinearGradient. Evenly-spaced stops seem very limited if they're the only option, so I suggest focusing on the more general case.

@davepagurek Regarding array parameters, different libraries take different approaches. For example, synced arrays for plotting, arrays-of-objects for data wrangling, and so on.

@RandomGamingDev The last syntax you suggested seems like the simplest. For discussion's sake, I've included several options for color stops here so we can consider tradeoffs and/or overloads.

lerpPalette(
  white, 0,
  red, 0.05,
  green, 0.25,
  blue, 1,
  amt);

lerpPalette([
  white, 0,
  red, 0.05,
  green, 0.25,
  blue, 1
], amt);

lerpPalette([
  [white, 0],
  [red, 0.05],
  [green, 0.25],
  [blue, 1]
], amt);

lerpPalette([white, red, green, blue], [0, 0.05, 0.25, 1], amt);

lerpPalette([
  { color: white, stop: 0 },
  { color: red, stop: 0.05 },
  { color: green, stop: 0.25 },
  { color: blue, stop: 1 }
], amt);

This feature could make sense as an overload for lerpColor(), though I'm wary of over-overloading.

@davepagurek
Copy link
Contributor

I think the biggest current technical limitation for interleaving colors and stops (the first two examples above) is just that our current jsdoc-based FES system isn't flexible enough to handle it. That just means that this method would need custom error handling (which also would need to respect the case when FES is disabled and avoid checking to save CPU cycles.)

Do you think we can write the docs in the format they'd appear on the site as a first step and see if that's readable enough? Ideally I would want the parameters section to still be useful. I think I'd be ok with interleaving if we can make it fit into our docs format nicely.

Otherwise, options 3 and 5 above are within what our documented type formats support, but do force the user to provide more structure themselves, so I'd use those as a compromise.

@RandomGamingDev
Copy link
Contributor Author

I think the biggest current technical limitation for interleaving colors and stops (the first two examples above) is just that our current jsdoc-based FES system isn't flexible enough to handle it. That just means that this method would need custom error handling (which also would need to respect the case when FES is disabled and avoid checking to save CPU cycles.)

Do you think we should wait until the upgraded FES with p5.js 2.0? I think that implementing it manually shouldn't be too much of an issue, especially if we move amt to be the first argument.

Do you think we can write the docs in the format they'd appear on the site as a first step and see if that's readable enough? Ideally I would want the parameters section to still be useful. I think I'd be ok with interleaving if we can make it fit into our docs format nicely.

I think that formatting it similarly to commands that can accept multiple points like bezier (https://p5js.org/reference/p5/bezier/), but ending with a ... and then n- parameter section would be good enough especially since the examples should be more than clear enough. For example: 1st color, 1st placement, 2nd color, 2nd placement, ..., nth color, nth placement.

Otherwise, options 3 and 5 above are within what our documented type formats support, but do force the user to provide more structure themselves, so I'd use those as a compromise.

Yeah, it might be worth it to go with a compromise here, but it'd be best if we could get a general consensus before making it. If we can't settle for the solution I suggested due to current FES issues and don't want to wait until the updated FES, or if the updated FES doesn't support this either, I'd choose option 5 since it's the cleanest one that still works with the current FES.

@RandomGamingDev
Copy link
Contributor Author

We've decided to go ahead with:

lerpPalette([
  [white, 0],
  [red, 0.05],
  [green, 0.25],
  [blue, 1]
], amt);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants