p5.js icon indicating copy to clipboard operation
p5.js copied to clipboard

Ability to change global opacity (similar to tint(255, alpha) for images, but for all shapes)

Open davepagurek opened this issue 1 year ago • 4 comments

Increasing access

We provide a tint() function that can be used to change the opacity images are drawn at. This functionality is a bit hidden since tinting the alpha channel is perhaps not the first interpretation of the word "tint" to users. It also only applies to images, so it may be unclear at first whether or not it is possible to apply it across the board from the docs.

Most appropriate sub-area of p5.js?

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

Feature enhancement details

This does not change existing functionality, but just extends it, so it does not necessarily need to be built into the 2.0 release.

Some questions that I think would need to be answered:

  • One can set the alpha of a fill color already. Is it necessary to have a separate function? I would think there's a benefit to it: if you have a function that draws a complex shape with multiple internal colors, to adapt it to draw it semi-transparently, one would have to create an alpha argument and multiply it in with the alpha of every color within the function. A global control would make that scenario easier.
  • drawingContext.globalAlpha exists, should users use that instead? For now, that is likely the easiest way to achieve this in 2D mode. However, it doesn't work in WebGL mode, and we don't document native JS canvas features, so it's not as discoverable as p5's own functionality.
  • Should tint() be extended to apply to all colors instead of just images? Maybe! I personally would be ok if this is preferred by other maintainers, but I also think that tint would not be the first place users would look when they are interested in changing opacity. Additionally, having a setting just for opacity opens the possibility of a faster 2D mode implementation that doesn't require drawing to a separate canvas before drawing to the main canvas.

A possible API would be:

opacity(): number // Returns the current opacity
opacity(newOpacity: number): void // Sets the current opacity, using the range set by `colorMode`, by default 0-255

In 2D mode this could directly set drawingContext.globalAlpha. In WebGL mode, this would have to be backed by a new uniform that gets sent to shaders and gets multiplied with other colors.

davepagurek avatar Jan 21 '24 16:01 davepagurek

Thank you so much @davepagurek for bringing this up. Honestly, I hadn't considered this before, but it sounds interesting. So, to recap and correct me if I'm wrong in understanding the feature, the plan is to introduce a global function called opacity(). This function would handle opacity for all shapes, including images. Since many users may not be familiar with using tint() with an alpha channel, we currently have drawingContext.globalAlpha, which aligns with what we want. However, the limitation is that we can't document drawingContext.globalAlpha, and it only works for non-WebGL, which makes sense logically. If i am not wrong, after this function we could have something like or please correct me if I am wrong:-

opacity(128);
image(yourImage, 0, 0); // anything you draw now will be semi transparent
opacity(255); // now anything drawn after this is opaque again

Now, the question for me is whether we should still keep the alpha channel in the fill(R, G, B, Alpha) and tint() functions after introducing the opacity() function. My opinion is that we should keep it because users who are familiar and comfortable with the previous alpha channel should still find it useful.

perminder-17 avatar Jan 21 '24 21:01 perminder-17

I think the existing fill/tint methods can be left as-is since they do slightly different things. Opacity would apply to a whole object at a time, so you'd still maybe want the ability to have a per-vertex fill where some vertices are semi transparent while others are opaque. I think it maybe still makes sense to keep around the opacity in tint() just for backwards compatibility to avoid also having to implement this as part of p5 2.0? I'm open to opinions though.

Another thing to determine is whether opacity overwrites the previous value, like in your code snippet, or whether the opacity multiplies with the existing opacity. For the latter, to return something to its original opacity, one would have to surround the opacity() call with push() and pop(). The benefit would be that one can apply opacity to a whole block of code that itself may internally set the opacity too. The drawback is that it would deviate from the behaviour of drawingContext.globalAlpha.

One final thought: maybe it also makes sense for simplicity to detach the range of values from the color mode and just use a 0-1 fraction?

cc the color stewards: @paulaxisabel, @SoundaryaKoutharapu, @mrbrack, @TJ723, @Zarkv, @SkylerW99, @ramya202000, @hannahvy, @robin-haxx, @hiddenenigma

davepagurek avatar Jan 23 '24 15:01 davepagurek

Thank you for your thoughtful input @davepagurek and @perminder-17 for recapping Dave's suggestions. To me it makes sense that opacity() would overwrite the previous value. The way I see it, it's another function that is changing the appearance of a visual. Similar to how fill() gets applied to a block of code until there's another fill().

Interesting point about using a 0-1 range. At first I thought, this could help users easily visualize how the opacity would be applied in their sketch. In addition, since p5js is living on the web, it would align with how CSS sets opacity. However, correct me if I'm wrong, alpha values in p5js is a range of 0-255. So does it make sense to stick with this since this is what users are familiar with?

hiddenenigma avatar Jan 23 '24 15:01 hiddenenigma

Currently the default alpha value is 255 for colors, although it can be set to something else via colorMode(mode, max1, max2, max3, maxA): https://p5js.org/reference/#/p5/colorMode So for maximum compatibility, we'd maybe try to be consistent with that.

davepagurek avatar Jan 23 '24 21:01 davepagurek

I'm still largely undecided about whether this global opacity will replace or multiply with other opacity values set by fill() and tint() etc. My feeling is to multiply and not replace but it can make controlling opacity difficult when the sketch get complicated enough. Replacing though feel not entirely intuitive as well with different functions changing the same property, ie. fill is currently only controlled by fill() and noFill() but with opacity() affecting it as well may bring some confusion around current status of fills at different point of the code.

limzykenneth avatar Mar 01 '24 12:03 limzykenneth

Hi Ken, was looking at CSS to see how they handle opacity and alpha to see how this problem is handled elsewhere. When there's opacity and alpha present, the transparency gets multiplied.

To Dave's point about calling opacity() at different points of the sketch though, I would expect the behaviour of the latest opacity value be the current value.

hiddenenigma avatar Apr 12 '24 17:04 hiddenenigma