Skip to content

Commit

Permalink
[css-images-3] Pull in gradient midpoints from L4, per #1284. Also do…
Browse files Browse the repository at this point in the history
… significant editorial cleanup to match L4's incorportion of the new text, and generally improve things.
  • Loading branch information
tabatkins committed Jul 19, 2019
1 parent c1e4f53 commit c03b2ea
Showing 1 changed file with 208 additions and 120 deletions.
328 changes: 208 additions & 120 deletions css-images-3/Overview.bs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ Gradients {#gradients}

A gradient is an image that smoothly fades from one color to another.
These are commonly used for subtle shading in background images, buttons, and many other things.
The gradient notations described in this section allow an author to specify such an image in a terse syntax,
The <dfn export lt="gradient function">gradient functions</dfn> described in this section allow an author to specify such an image in a terse syntax,
so that the UA can generate the image automatically when rendering the page.
The syntax of a <<gradient>> is:

Expand Down Expand Up @@ -766,93 +766,236 @@ Repeating Gradients: the ''repeating-linear-gradient()'' and ''repeating-radial-
-->


Gradient Color-Stops {#color-stop-syntax}
-----------------------------------------
Defining Gradient Color {#gradient-colors}
-------------------------

<pre class=prod>
<dfn>&lt;color-stop-list></dfn> = <<color-stop>>#{2,}
<dfn>&lt;color-stop></dfn> = <<color>> <<length-percentage>>?
</pre>
The colors in gradients are specified using <dfn export lt="color stop">color stops</dfn>
(a <<color>> and a corresponding position on the [=gradient line=])
and <dfn export lt="color transition hint" local-lt="transition hint">color transition hints</dfn>
(a position between two [=color stops=]
representing the halfway point in the color transition)
which are placed on the <a>gradient line</a>,
defining the color at every point of the line.
(Each [=gradient function=] defines the shape and length of the <a>gradient line</a>,
along with its <a>starting point</a> and <a>ending point</a>;
see above.)

<h4 id=color-stop-syntax>
Color Stop Lists</h4>

The colors in gradients are specified using <a>color stops</a>.
A <dfn export>color stop</dfn> is a combination of a color and a position.
<!-- UNCOMMENT FOR L4
Depending on the type of gradient, that position can be a length, angle, or percentage.
-->
While every color stop conceptually has a position,
the position can be omitted in the syntax,
in which case it gets automatically filled in by the user agent;
see below for details.

The color stops for a gradient are specified

<a>Color stops</a> and [=transition hints=] are specified
in a <dfn export>color stop list</dfn>,
which is a list of two or more <a>color stops</a>,
specified in geometric order.
which is a list of two or more [=color stops=]
interleaved with optional [=transition hints=]:

<a>Color stops</a> are placed on a <a>gradient line</a>,
which defines the colors at every point of a gradient.
The gradient function defines the shape and length of the <a>gradient line</a>,
along with its <a>starting point</a> and <a>ending point</a>.
<pre class=prod>
<dfn>&lt;color-stop-list></dfn> =
<<linear-color-stop>> , [ <<linear-color-hint>>? , <<linear-color-stop>> ]#
<dfn>&lt;linear-color-stop></dfn> = <<color>> && <<length-percentage>>?
<dfn>&lt;linear-color-hint></dfn> = <<length-percentage>>
</pre>

Percentages refer to the length of the <a>gradient line</a>
Percentages are resolved against the length of the <a>gradient line</a>
between the <a>starting point</a> and <a>ending point</a>,
with 0% being at the starting point
and 100% being at the ending point.
Lengths are measured along the <a>gradient line</a>
from the <a>starting point</a>
in the direction of the <a>ending point</a>.
<!-- UNCOMMENT FOR L4
Angles are measured with 0deg pointing up,
and positive angles corresponding to clockwise rotations from there.
-->

<a>Color stops</a> are usually placed between
[=Color stop=] and [=transition hint=] positions
are usually placed between
the <a>starting point</a> and <a>ending point</a>,
but that's not required:
the gradient line extends infinitely in both directions,
and a <a>color stop</a> can be placed at any position on the <a>gradient line</a>.

When the position of a <a>color stop</a> is omitted,
it is positioned automatically
halfway between the two surrounding stops.
and positions can be specified anywhere
on the <a>gradient line</a>.

When the position of a [=color stop=] is omitted,
it is automatically assigned a position.
The first or last [=color stop=] in the [=color stop list=]
is assigned
the [=gradient line’s=] [=starting point=] or [=ending point=]
(respectively).
Otherwise,
it's assigned the position halfway between the two surrounding stops.
If multiple stops in a row lack a position,
they space themselves out equally.

The following steps must be applied <em>in order</em> to process the list of <a>color stops</a>.
After applying these rules,
all <a>color stops</a> will have a definite position and color
and they will be in ascending order:

1. If the first <a>color stop</a> does not have a position,
set its position to 0%.
If the last <a>color stop</a> does not have a position,
set its position to 100%.
they space themselves out equally
between the surrounding positioned stops.
See [[#color-stop-fixup]] for details.

2. If a <a>color stop</a> has a position that is less than
the specified position of any <a>color stop</a> before it in the list,
set its position to be equal to the largest specified position of any <a>color stop</a> before it.

3. If any <a>color stop</a> still does not have a position,
then, for each run of adjacent <a>color stops</a> without positions,
set their positions so that they are evenly spaced between the preceding and following <a>color stops</a> with positions.
<h4 id=coloring-gradient-line>
Coloring the Gradient Line</h4>

At each <a>color stop</a> position,
the line is the color of the <a>color stop</a>.
Between two <a>color stops</a>,
the line's color is linearly interpolated between the colors of the two <a>color stops</a>,
with the interpolation taking place in premultiplied RGBA space.
the [=gradient line=] is the color of the <a>color stop</a>.
Before the first <a>color stop</a>,
the line is the color of the first <a>color stop</a>.
After the last <a>color stop</a>, the line is the color of the last <a>color stop</a>.
the [=gradient line=] is the color of the first <a>color stop</a>,
and after the last <a>color stop</a>,
the [=gradient line=] is the color of the last <a>color stop</a>.
Between two <a>color stops</a>,
the [=gradient line’s=] color is interpolated between the colors of the two <a>color stops</a>,
with the interpolation taking place in <a href="#premultiplied">premultiplied RGBA space</a>.

By default,
this interpolation is linear--
at 25%, 50%, or 75% of the distance between two <a>color stops</a>,
the color is a 25%, 50%, or 75% blend of the colors of the two stops.

However, if a [=transition hint=] was provided between two <a>color stops</a>,
the interpolation is non-linear,
and controlled by the hint:

<div algorithm="interpolate with a color hint">
<ol>
<li>
Determine the location of the [=transition hint=] as a percentage of the distance between the two <a>color stops</a>,
denoted as a number between 0 and 1,
where 0 indicates the hint is placed right on the first <a>color stop</a>,
and 1 indicates the hint is placed right on the second <a>color stop</a>.
Let this percentage be <var>H</var>.

<li>
For any given point between the two color stops,
determine the point's location as a percentage of the distance between the two <a>color stops</a>,
in the same way as the previous step.
Let this percentage be <var>P</var>.

<li>
Let <var>C</var>, the color weighting at that point,
be equal to <code><var>P</var><sup>log<sub><var>H</var></sub>(.5)</sup></code>.

<li>
The color at that point is then a linear blend between the colors of the two <a>color stops</a>,
blending <code>(1 - <var>C</var>)</code> of the first stop and <var>C</var> of the second stop.
</ol>
</div>

Note: The [=transition hint=] specifies where the “halfway color”--
the 50% blend between the colors of the two surrounding color stops--
should be placed.
When the hint is exactly halfway between the two surrounding color stops,
the above interpolation algorithm
happens to produce the ordinary linear interpolation.
If the hint is placed anywhere else,
it produces a smooth exponential curve
between the surrounding color stops,
with the “halfway color” occuring exactly where the hint specifies.

Issue: Add a visual example of a color hint being used.

If multiple <a>color stops</a> have the same position,
they produce an infinitesimal transition from the one specified first in the rule
they produce an infinitesimal transition from the one specified first in the list
to the one specified last.
In effect, the color suddenly changes at that position rather than smoothly transitioning.

<details class=note id=premultiplied>
<summary>What does “pre-multiplied” mean?</summary>

A “pre-multiplied” color
is written in a form
where the alpha channel
is multiplied into the color channels,
rather than being processed independently.
For example, a partially-transparent blue may be given as <code class=lang-css><nobr>rgba(0, 0, 255, .5)</nobr></code>,
which would then be expressed as <code><nobr>[0, 0, 127.5, .5]</nobr></code> in its premultiplied representation.

Interpolating colors using the premultiplied representations
rather than the plain rgba representations
tends to produce more attractive transitions,
particularly when transitioning from a fully opaque color to fully transparent.

Note that transitions where either the transparency or the color are held constant
(for example, transitioning between <code class=lang-css><nobr>rgba(255, 0, 0, 100%)</nobr></code> (opaque red)
and <code class=lang-css><nobr>rgba(0,0,255,100%)</nobr></code> (opaque blue),
or <code class=lang-css><nobr>rgba(255,0,0,100%)</nobr></code> (opaque red)
and <code class=lang-css><nobr>rgba(255,0,0,0%)</nobr></code> (transparent red))
have identical results whether the color interpolation is done in premultiplied or non-premultiplied color-space.
Differences only arise when <em>both</em> the color and transparency differ between the two endpoints.

<div class=example>
The following example illustrates the difference between
a gradient transitioning in pre-multiplied sRGBA
and one transitioning (incorrectly) in non-premultiplied.
In both of these example,
the gradient is drawn over a white background.
Both gradients could be written with the following value:

<pre>linear-gradient(90deg, red, transparent, blue)</pre>

With premultiplied colors,
transitions to or from "transparent" always look nice:

<object data="images/gradient2.svg" width="200"height="100">(Image requires SVG)</object>

On the other hand,
if a gradient were to incorrectly transition in non-premultiplied space,
the center of the gradient would be a noticeably grayish color,
because "transparent" is actually a shorthand for ''rgba(0,0,0,0)'', or transparent black,
meaning that the red transitions to a black
as it loses opacity,
and similarly with the blue's transition:

<object data="images/gradient3.svg" width="200"height="100">(Image requires SVG)</object>
</div>

</details>



<h4 id=color-stop-fixup>
Color Stop “Fixup”</h4>

When resolving the [=used value|used=] positions of each [=color stop=],
the following steps must be applied <em>in order</em>:

<ol>
<li>
If the first <a>color stop</a> does not have a position,
set its position to 0%.
If the last <a>color stop</a> does not have a position,
set its position to 100%.

<li>
If a <a>color stop</a> or [=transition hint=] has a position
that is less than the specified position
of any <a>color stop</a> or [=transition hint=] before it in the list,
set its position to be equal to the largest specified position
of any <a>color stop</a> or [=transition hint=] before it.

<li>
If any <a>color stop</a> still does not have a position,
then, for each run of adjacent <a>color stops</a> without positions,
set their positions so that they are evenly spaced
between the preceding and following <a>color stops</a> with positions.
</ol>

After applying these rules,
all [=color stops=] and [=transition hints=] will have a definite position and color
and they will be in ascending order.

Note: It is recommended that authors exercise caution
when mixing different types of units,
such as px, em, or %,
as this can cause a <a>color stop</a> to unintentionally try to move before an earlier one.
For example,
the rule ''background-image: linear-gradient(yellow 100px, blue 50%)''
wouldn't trigger any fix-up while the background area is at least ''200px'' tall.
If it was ''150px'' tall, however,
the blue <a>color stop's</a> position would be equivalent to ''75px'',
which precedes the yellow <a>color stop</a>,
and would be corrected to a position of ''100px''.
Additionally, since the relative ordering of such color stops
cannot be determined without performing layout,
they will not interpolate smoothly in
<a href="http://www.w3.org/TR/css-animations/">animations</a>
or <a href="http://www.w3.org/TR/css-transitions/">transitions</a>.

<div class=example>
Below are several pairs of gradients.
The latter of each pair is a manually "fixed-up" version of the former,
The latter of each pair is a manually fixed-up version of the former,
obtained by applying the above rules.
For each pair, both gradients will render identically.
<span class='note'>The numbers in each arrow specify which fixup steps are invoked in the transformation.</span>
Expand All @@ -863,86 +1006,31 @@ Gradient Color-Stops {#color-stop-syntax}
linear-gradient(red 0%, white 20%, blue 100%)

2. linear-gradient(red 40%, white, black, blue)
=13=>
=1,3=>
linear-gradient(red 40%, white 60%, black 80%, blue 100%)

3. linear-gradient(red -50%, white, blue)
=13=>
=1,3=>
linear-gradient(red -50%, white 25%, blue 100%)

4. linear-gradient(red -50px, white, blue)
=13=>
=1,3=>
linear-gradient(red -50px, white calc(-25px + 50%), blue 100%)

5. linear-gradient(red 20px, white 0px, blue 40px)
=2=>
linear-gradient(red 20px, white 20px, blue 40px)

6. linear-gradient(red, white -50%, black 150%, blue)
=12=>
=1,2=>
linear-gradient(red 0%, white 0%, black 150%, blue 150%)

7. linear-gradient(red 80px, white 0px, black, blue 100px)
=23=>
=2,3=>
linear-gradient(red 80px, white 80px, black 90px, blue 100px)
</pre>
</div>

<div class=example>
The following example illustrates the difference between
a gradient transitioning in pre-multiplied sRGBA
and one transitioning (incorrectly) in non-premultiplied.
In both of these example,
the gradient is drawn over a white background.
Both gradients could be written with the following value:

<pre>linear-gradient(90deg, red, transparent, blue)</pre>

In premultiplied space,
transitions to or from "transparent" always look nice:

<object data="images/gradient2.svg" width="200"height="100">(Image requires SVG)</object>

On the other hand,
if a gradient were to incorrectly transition in non-premultiplied space,
the colors near "transparent" would noticeably darken to a grayish color,
because "transparent" is actually a shorthand for ''rgba(0,0,0,0)'', or transparent black:

<object data="images/gradient3.svg" width="200"height="100">(Image requires SVG)</object>
</div>

Note: It is recommended that authors not mix different types of units,
such as px, em, or %,
in a single rule,
as this can cause a <a>color stop</a> to unintentionally try to move before an earlier one.
For example, the rule ''background-image: linear-gradient(yellow 100px, blue 50%)''
wouldn't require any fix-up as long as the background area is at least 200px tall.
If it was 150px tall, however,
the blue <a>color stop's</a> position would be equivalent to "75px",
which precedes the yellow <a>color stop</a>,
and would be corrected to a position of 100px.
Additionally, since the relative ordering of such color stops
cannot be determined without performing layout,
they will not interpolate smoothly in
<a href="http://www.w3.org/TR/css-animations/">animations</a>
or <a href="http://www.w3.org/TR/css-transitions/">transitions</a>.

Note: The definition and implications of "premultiplied" color spaces are given elsewhere in the technical literature,
but a quick primer is given here to illuminate the process.
Given a color expressed as an rgba() 4-tuple,
one can convert this to a premultiplied representation
by multiplying the red, green, and blue components by the alpha component.
For example, a partially-transparent blue may be given as rgba(0,0,255,.5),
which would then be expressed as [0, 0, 127.5, .5] in its premultiplied representation.
Interpolating colors using the premultiplied representations
rather than the plain rgba representations
tends to produce more attractive transitions,
particularly when transitioning from a fully opaque color to fully transparent.
Note that transitions where either the transparency or the color are held constant
(for example, transitioning between rgba(255,0,0,100%) and rgba(0,0,255,100%),
or rgba(255,0,0,100%) and rgba(255,0,0,0%))
have identical results whether the color interpolation is done in premultiplied or non-premultiplied color-space.
Differences only arise when both the color and transparency differ between the two endpoints.


<!--
Expand Down

0 comments on commit c03b2ea

Please sign in to comment.