w3c / fxtf-drafts

Mirror of https://hg.fxtf.org/drafts
https://drafts.fxtf.org/
Other
69 stars 49 forks source link

[filter-effects-2] Add a shorthand color-channel adjustment function similar to feComponentTransfer #256

Open AmeliaBR opened 6 years ago

AmeliaBR commented 6 years ago

As discussed in #178, and supported in the resolution to close it.

It would be useful to have shorthand functions that can:

In other words, something similar to <feComponentTransfer>, but hopefully without so much mathematical terminology.

I'd suggest that the design of this shorthand should be as consistent as possible with the syntax of the Colors 4 color-mod() functions. I think there is still some debate about what the final color-mod syntax will finally be, but right now it is color-mod(<starting-color-or-hue> <color-adjuster-list>).

For a filter function, the starting value would be the input layer to the filter (aka, the output from the previous filter in the chain, or the painted element layer). A color adjuster list would then be applied for each pixel in the layer.

A few of the adjuster functions duplicate existing shorthand filter functions, but I don't see that as a problem.

Compared to <feComponentTransfer>, the color-adjuster functions would currently only recreate the linear effects, not gamma curves or discrete effects—but again, I don't see that as a problem. Maybe in the future those calculations could be added to color-mod() if there is a demand. In the meantime, there is the longhand SVG filter option.

Proposed syntax:

Add new <filter-function> <color-adjust()>, where:

color-adjust() = color-adjust( <color-adjuster>* )

Example uses:

/* increase drop-shadow opacity while maintaining spread from the blur
   (assuming original element had strict 0 or 1 opacity) */
filter: drop-shadow(navy 5px 5px 3px) color-adjust(alpha(*1.5)); 

/* the gooey effect
   (merges nearby shapes and colors & rounds corners, while recreating sharp edges) */
filter: blur(10px) color-adjust(alpha(-0.5) alpha(*400%));

/* asymmetrical contrast adjustment,
   clamping almost-black to black without losing detail in almost-white areas */
filter: color-adjust(rgb(-2%) rgb(*calc(100%/98)) );

/* white-balance adjustment, to create warmer hues */
filter: color-adjust(red(*110%) blue(*95%));
AmeliaBR commented 6 years ago

One issue: the contrast() color adjustment function for guaranteeing adequate contrast probably does not make sense when applied to a full image layer. (It's quite different from the contrast() filter function, which changes pixel colors to move them closer/farther from medium gray). However, as I've argued in https://github.com/w3c/csswg-drafts/issues/1627#issuecomment-318437824, I think this WCAG contrast color-calculation feature would be better served by creating a separate function for it, instead of trying to include it in color-mod().

And while I'm here, a few more examples of how the other color adjusters would be helpful when applied as filters:

/* Increase lightness, not just brightness (which has no effect on saturated colors) */
filter: color-adjust(lightness(200%));

/* Darken or add a color tint to a content image, 
   without needing a pseudoelement overlay */
filter: color-adjust(shade(30%));
filter: color-adjust(blend(orchid 15% hsl));
tabatkins commented 6 years ago

Don't try and be "consistent" with color-mod() - we're removing it, and when we add back its functionality, it will have a different syntax anyway. (I've finally taken the time to actually remove it from the spec - I should have done it months ago, since it only took about 10 seconds.)

tabatkins commented 6 years ago

As such, I have a different suggested syntax:

component(<transfer-func>#)

<transfer-func> = <comp> <op> <amount>

<comp> = red | blue | green | alpha | rgb

<op> = '+' | '-' | '*' | '/'

<amount> = <number>

So your examples would be:

/* increase drop-shadow opacity while maintaining spread from the blur
   (assuming original element had strict 0 or 1 opacity) */
filter: drop-shadow(navy 5px 5px 3px) component(alpha * 1.5); 

/* the gooey effect
   (merges nearby shapes and colors & rounds corners, while recreating sharp edges) */
filter: blur(10px) component(alpha - 0.5, alpha * 4);

/* asymmetrical contrast adjustment,
   clamping almost-black to black without losing detail in almost-white areas */
filter: component(rgb - .02, rgb * calc(100 / 98));

/* white-balance adjustment, to create warmer hues */
filter: component(red * 1.1, blue * .95);
AmeliaBR commented 6 years ago

I'd still argue for being consistent with color-mod() or its successor. Conceptually, it's the same math, it's just a matter of whether we're doing it for a single color or for each pixel in an image.

But if the syntax for this functionality in Colors 4 is completely up in the air, then perhaps syntax discussion should be made considering both use cases simultaneously.

AmeliaBR commented 6 years ago

(And I do like your suggested syntax, @tabatkins, since it significantly reduces the amount of nested parentheses. Not so keen on component() as the function name, though.)

karip commented 5 years ago

I like the idea of adjusting color channels using shorthand filters. Here's my proposal how the syntax could look like.

I would call the function levels() because it is similar to levels adjustments in photo editors (for instance, Gimp, Photoshop, Apple Motion).

In photo editors, levels adjustments have an input range with gamma and an output range. This is similar, but the range wouldn't be restricted to [0, 1] like in photo editors. It has an input range, optional gamma and optional output range:

levels(<transfer-func>#)

<transfer-func> = <comp> <in-min> <in-max> <gamma>? <out-min>? <out-max>?

<comp> = red | blue | green | alpha | rgb

<in-min> = <number> | <percentage>, default value 0

<in-max> = <number> | <percentage>, default value 1

<gamma> = <number>, default value 1 (no effect)

<out-min> = <number> | <percentage>, default value 0

<out-max> = <number> | <percentage>, default value 1

The function maps the input range [in-min, in-max] to the given output range [out-min, out-max]. If the same component is given many times, then the last one is used (just like feFuncR/G/B elements under feComponentTransfer).

Here's why I like my proposal :)

Here are the use cases written using levels(). They can use numbers or percentages. It is possible to describe the use cases by giving the input and output range, but it is shorter to just give the input range. Both are given as examples.

/* increase drop-shadow opacity while maintaining spread from the blur
   (assuming original element had strict 0 or 1 opacity) */
filter: drop-shadow(navy 5px 5px 3px) levels(alpha 0 1 1 0 1.5);
filter: drop-shadow(navy 5px 5px 3px) levels(alpha 0% 100% 1 0% 150%);
filter: drop-shadow(navy 5px 5px 3px) levels(alpha 0% 66%); /* input only version */

/* the gooey effect
   (merges nearby shapes and colors & rounds corners, while recreating sharp edges) */
filter: blur(10px) levels(alpha 0 1 1 -0.5 2);
filter: blur(10px) levels(alpha 0% 100% 1 -50% 200%);
filter: blur(10px) levels(alpha 50% 75%); /* input only version */

/* asymmetrical contrast adjustment,
   clamping almost-black to black without losing detail in almost-white areas */
filter: levels(alpha 0 1 1 -0.02 1);
filter: levels(alpha 0% 100% 1 -2% 100%);
filter: levels(alpha 2% 100%); /* input only version */

/* white-balance adjustment, to create warmer hues */
filter: levels(red 0 1 1 0 1.1, blue 0 1 1 0 0.95);
filter: levels(red 0% 100% 1 0% 110%, blue 0% 100% 1 0% 95%);
filter: levels(red 0% 90%, blue 0% 105%); /* input only version */

/* additional use cases */

/* make everything brighter */
filter: levels(rgb 0% 100% 1 25% 100%);
filter: levels(rgb -25% 100%); /* input only version */

/* inverted alpha */
filter: levels(alpha 100% 0%);

/* make midtones brighter by increasing gamma. */
filter: levels(rgb 0% 100% 1.5);
BigBadaboom commented 5 years ago

@karip I like this syntax. I'm suspect, though, that gamma correction might be a lot more common than input range adjustment. So you could have gamma first and make the input range optional instead.