processing / p5.js

p5.js is a client-side JS platform that empowers artists, designers, students, and anyone to learn to code and express themselves creatively on the web. It is based on the core principles of Processing. http://twitter.com/p5xjs —
http://p5js.org/
GNU Lesser General Public License v2.1
21.4k stars 3.28k forks source link

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

Open davepagurek opened 7 months ago

davepagurek commented 7 months ago

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?

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:

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.

perminder-17 commented 7 months ago

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.

davepagurek commented 7 months ago

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

hiddenenigma commented 7 months ago

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?

davepagurek commented 7 months ago

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.

limzykenneth commented 6 months ago

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.

hiddenenigma commented 4 months ago

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.