Title: CSS Images Module Level 4 Status: ED Work Status: Exploring Shortname: css-images Level: 4 Group: csswg ED: https://drafts.csswg.org/css-images-4/ TR: https://www.w3.org/TR/css4-images/ Editor: Tab Atkins Jr., Google, http://xanthir.com/contact/, w3cid 42199 Editor: Elika J. Etemad / fantasai, Invited Expert, http://fantasai.inkedblade.net/contact, w3cid 35400 Editor: Lea Verou, Invited Expert, http://lea.verou.me/about, w3cid 52258 Abstract: This module contains the features of CSS level 4 relating to the <> type and replaced elements. It includes and extends the functionality of CSS level 2 [[CSS21]] and in the previous level of this specification [[css3-images]]. The main extensions compared to "CSS Images Module Level 3" [[css3-images]] are several additions to the < > type, such as the ''image()'' notation, the ''element()'' notation, and conic gradients. Issue Tracking: Tracker http://www.w3.org/Style/CSS/Tracker/products/27 Previous Version: https://www.w3.org/TR/2012/WD-css4-images-20120911/ Ignored Terms: , background positioning area, border image area, , , Map, , invalid image, invalid images, concrete object size, linear-gradient(), radial-gradient(), intrinsic dimensions, default object size, CSS Ignored Vars: H, P Include Can I Use Panels: yes
spec: css-images-3; type: function; text: image-set(); text: cross-fade();Introduction {#intro} ===================== This section is not normative. This module introduces additional ways of representing 2D images, for example as a URL with color fallback, as conic gradients, or as the rendering of another element in the document. 2D Image Values: the <
<image> = <An <> | < > | < > | < > | < >
image-set() = image-set( <Issue: We should add "w" and "h" dimensions as a possibility, and a "format()" function, to match the functionality of HTML's picture. The ''image-set()'' function can not be nested inside of itself, either directly or indirectly (as an argument to another <># ) <image-set-option> = [ < > | < > ] < >
background-image: image-set( "foo.png" 1x, "foo-2x.png" 2x, "foo-print.png" 600dpi );
image() = image( <A <>? [ < >? , < >? ]! ) <image-tags> = [ ltr | rtl ] <image-src> = [ < > | < > ]
body { color: black; background: white; } p.special { color: white; background: url("dark.png") black; }When the image doesn't load, the background color is still there to ensure that the white text is readable. However, if the image has some transparency, the black will be visible behind it, which is probably not desired. The ''image()'' function addresses this:
body { color: black; background: white; } p.special { color: white; background: image("dark.png", black); }Now, the black won't show at all if the image loads, but if for whatever reason the image fails, it'll pop in and prevent the white text from being set against a white background.
background-image: image('sprites.svg#xywh=40,0,20,20')...the background of the element will be the portion of the image that starts at (40px,0px) and is 20px wide and tall, which is just the circle with a quarter filled in.
xywh=#,#,#,#
form of media fragment identifiers
for images specified via ''image()''. [[!MEDIA-FRAGS]]
background-image: url('swirl.png'); /* old UAs */ background-image: image('sprites.png#xywh=10,30,60,20'); /* new UAs */
background-image: image(rgba(0,0,255,.5)), url("bg-image.png");'background-color' does not work for this, as the solid color it generates always lies beneath all the background images.
<image-src>s
,
the author may specify a directionality for the image,
similar to adding a dir
attribute to an element in HTML.
If a directional image is used on or in an element with opposite direction,
the image must be flipped in the inline direction
(as if it was transformed by, e.g., scaleX(-1)
, if the inline direction is the X axis).
Note: Absent this declaration,
images default to no directionality at all,
and thus don't care about the directionality of the surrounding element.
<ul style="list-style-image: image(ltr 'arrow.png');"> <li dir='ltr'>My bullet is on the left!</li> <li dir='rtl'>MY BULLET IS ON THE RIGHT!</li> </ul>This should render something like:
⇒ My bullet is on the left! !THGIR EHT NO SI TELLUB YM ⇐In LTR list items, the image will be used as-is. In the RTL list items, however, it will be flipped in the inline direction, so it still points into the content.
cross-fade() = cross-fade( <The function represents an image generated by combining two or more images. The <># ) <cf-image> = < >? && [ < > | < > ]
dissolve
operation to the source images.
Wikipedia defines dissolve
as a stochastic operation,
with the result pixels independently randomly chosen from the source images’ corresponding pixels
according to their source images’ weights,
but as pixels shrink to infinitely small,
this converges to doing color-averaging in pre-multiplied color space.
In particular, this means that `cross-fade(white 50%, transparent 50%)`
will produce a partially-transparent solid white image.
(Rather than a partially-transparent gray,
which is what you'd get if you averaged the opaque white and transparent black pixels
in non-premultiplied space.)
As converting to pre-multiplied does entail some loss of precision,
and graphics libraries may or may not support this operation natively,
as per usual any method can be used so long as it achieves the specified effect.
For example, one can instead rebalance the percentages
according to the alphas of each pixel,
then do the color-channel averages in non-premultiplied space.
E.g., to render ''cross-fade(rgb(255 0 0 / 1) 40%, rgb(0 255 0 / .5) 20%, rgb(0 0 255 / 0) 40%)'',
rebalancing the percentages according to the 1 / .5 / 0 alphas
would produce 40% / 10% / 0%
(which renormalizes to 80% / 20% / 0%),
at which point you can average the raw color channel values
and end up with an ''rgb(204 51 0 / .5)'' image.
(Note that the alpha channel is still averaged using the original percentages,
not the rebalanced ones.)
element() = element( <where <> )
Do we need to be able to refer to elements in external documents (such as SVG paint servers)? Or is it enough to just use url() for this?
This name conflicts with a somewhat similar function in GCPM. This needs to be resolved somehow.
Want the ability to do "reflections" of an element, either as a background-image on the element or in a pseudo-element. This needs to be specially-handled to avoid triggering the cycle-detection.
When we have overflow:paged, how can we address a single page in the view? The ''element()'' function references the element matched by its argument. The ID is first looked up in the elementSources map, as described in that section. If it's not found, it's then matched against the document. If multiple elements are matched, the function references the first such element. The image represented by the ''element()'' function can vary based on whether the element is visible in the document:
Requiring some degree of stacking context on the element appears to be required for an efficient implementation. Do we need a full stacking context, or just a pseudo-stacking context? Should it need to be a stacking context normally, or can we just render it as a stacking context when rendering it to element()? If the referenced element has a transform applied to it or an ancestor, the transform must be ignored when rendering the element as an image. [[!CSS3-TRANSFORMS]] If the referenced element is broken across pages, the element is displayed as if the page content areas were joined flush in the pagination direction, with pages' edges corresponding to the initial containing block's start edge aligned. Elements broken across lines or columns are just rendered with their decorated bounding box. Implementations may either re-use existing bitmap data generated for the referenced element or regenerate the display of the element to maximize quality at the image's size (for example, if the implementation detects that the referenced element is an SVG fragment); in the latter case, the layout of the referenced element in the image must not be changed by the regeneration process. That is, the image must look identical to the referenced element, modulo rasterization quality.
<style> #src { color: white; background: lime; width: 300px; height: 40px; position: relative; } #dst { color: black; background: element(#src); padding: 20px; margin: 20px 0; } </style> <p id='src'>I'm an ordinary element!</p> <p id='dst'>I'm using the previous element as my background!</p>
<pattern>
element in an HTML document:
<!DOCTYPE html> <svg> <defs> <pattern id='pattern1'> <path d='...'> </pattern> </defs> </svg> <p style="background: element(#pattern1)"> I'm using the pattern as a background! If the pattern is changed or animated, my background will be updated too! </p>HTML also defines that a handful of elements, such as <{canvas}>, <{img}>, and <{video}>, provide a paint source. This means that CSS can, for example, reference a canvas that's being drawn into, but not displayed in the page:
<!DOCTYPE html> <script> var canvas = document.querySelector('#animated-bullet'); canvas.width = 20; canvas.height = 20; drawAnimation(canvas); </script> <canvas id='animated-bullet' style='display:none'></canvas> <ul style="list-style-image: element(#animated-bullet);"> <li>I'm using the canvas as a bullet!</li> <li>So am I!</li> <li>As the canvas is changed over time with Javascript, we'll all update our bullet image with it!</li> </ul>
<!DOCTYPE html> <p id='one' style="display:none; position: relative;">one</p> <iframe src="http://example.com"> <p id='two' style="position: relative;">I'm fallback content!</p> </iframe> <ul> <li style="background: element(#one);"> A display:none element isn't rendered, and a P element doesn't provide a paint source. </li> <li style="background: element(#two);"> The descendants of a replaced element like an IFRAME can't be used in element() either. </li> <li style="background: element(#three);"> There's no element with an id of "three", so this also gets rendered as a transparent image. </li> </ul>
<defs>
element is considered to be not rendered.
<!DOCTYPE html> <script> function navigateSlides() { var currentSlide = ...; document.querySelector('#prev-slide').id = ''; document.querySelector('#next-slide').id = ''; currentSlide.previousElementSibling.id = 'prev-slide'; currentSlide.nextElementSibling.id = 'next-slide'; } </script> <style> .slide { /* Need to be a stacking context to be element()-able. */ position: relative; } #prev-preview, #next-preview { position: fixed; ... } #prev-preview { background: element(#prev-slide); } #next-preview { background: element(#next-slide); } </style> <a id='prev-preview'>Previous Slide</a> <a id='next-preview'>Next Slide</a> <section class='slide'>...</section> <section class='slide current-slide'>...</section> ...In this example, the
navigateSlides
function updates the ids of the next and previous slides,
which are then displayed in small floating boxes alongside the slides.
Since you can't interact with the slides through the ''element()'' function (it's just an image),
you could even use click
handlers on the preview boxes to help navigate through the page.
<linearGradient>
,
<radialGradient>
,
and <pattern>
elements
provide paint sources.
They are drawn as described in the spec,
with the coordinate systems defined as follows:
ElementSources
interfacepartial namespace CSS { [SameObject] readonly attribute any elementSources; };Any entries in the elementSources map with a string key and a value that is an object providing a paint source are made available to the ''element()'' function. Whenever ''element()'' uses an <
#
character)
is first looked up in the elementSources map:
This reuse of the ID selector matches Moz behavior.
I'm trying to avoid slapping a <
Usually in conic gradients the sharp transition at 0deg is undesirable, which is typically avoided by making sure the first and last color stops are the same color. Perhaps it would be useful to have a keyword for automatically achieving this.
Would a radius (inner & outer) for clipping the gradient be useful? If so, we could also support lengths in color stop positions, since we now have a specific radius.
Are elliptical conic gradients useful? Do graphics libraries support them?
<script>
var bg = document.createElement('canvas');
bg.height = 200;
bg.width = 1000;
drawFancyBackground(bg);
CSS.elementSources.set('fancy', bg);
</script>
<style>
h1 {
background-image: element(#fancy);
}
</style>
As the "fancy" canvas is drawn into and animated,
the backgrounds of all the H1 elements will automatically update in tandem.
Note that the elementSources map is consulted before the document
to match the ID selector,
so even if there's an element in the document that would match ''#fancy'',
the backgrounds will still predictably come from the elementSources value instead.
Cycle Detection
The ''element()'' function can produce nonsensical circular relationships,
such as an element using itself as its own background.
These relationships can be easily and reliably detected and resolved, however,
by keeping track of a dependency graph and using common cycle-detection algorithms.
The dependency graph consists of edges such that:
If the graph contains a cycle,
any ''element()'' functions participating in the cycle are invalid images.
<use>
element depends on the element it referenced.
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 functions 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> = [
<
background: linear-gradient(white, gray);
list-style-image: radial-gradient(circle, #006, #00a 90%, #0000af 100%, white 100%)
Conic Gradients: the ''conic-gradient()'' notation
A conic gradient starts by specifying the center of a circle,
similar to radial gradients,
except that conic gradient color-stops are placed around the circumference of the circle,
rather than on a line emerging from the center,
causing the color to smoothly transition as you spin around the center,
rather than as you progress outward from the center.
A conic gradient is specified by indicating a rotation angle, the center of the gradient,
and then specifying a list of color-stops.
Unlike linear and radial gradients,
whose color-stops are placed by specifying a <
''conic-gradient()'' Syntax
The syntax for a conic gradient is:
conic-gradient() = conic-gradient(
[ from <
The arguments are defined as follows:
Placing Color Stops
Color stops are placed on a gradient line that curves around the gradient center in a circle,
with both the 0% and 100% locations at 0deg.
Just like linear gradients,
0deg points to the top of the page,
and increasing angles correspond to clockwise movement around the circle.
Note: It may be more helpful to think of the gradient line as forming a spiral,
where only the segment from 0deg to 360deg is rendered.
This avoids any confusion about "overlap" when you have angles outside of the rendered region.
A color-stop can be placed at a location before 0% or after 100%;
though these regions are never directly consulted for rendering,
color stops placed there can affect the color of color-stops within the rendered region
through interpolation or repetition (see repeating gradients).
For example, ''conic-gradient(red -50%, yellow 150%)'' produces a conic gradient
that starts with a reddish-orange color at 0deg (specifically, #f50),
and transitions to an orangish-yellow color at 360deg (specifically, #fa0).
The color of the gradient at any point is determined by first finding the unique ray
anchored at the center of the gradient that passes through the given point.
The point's color is then the color of the gradient line at the location where this ray intersects it.
Conic Gradient Examples
All of the following ''conic-gradient()'' examples are presumed to be applied to a box that is 300px wide and 200px tall, unless otherwise specified.
background: conic-gradient(#f06, gold);
background: conic-gradient(at 50% 50%, #f06, gold);
background: conic-gradient(from 0deg, #f06, gold);
background: conic-gradient(from 0deg at center, #f06, gold);
background: conic-gradient(#f06 0%, gold 100%);
background: conic-gradient(#f06 0deg, gold 1turn);
background: conic-gradient(white -50%, black 150%);
background: conic-gradient(white -180deg, black 540deg);
background: conic-gradient(hsl(0,0%,75%), hsl(0,0%,25%));
background: conic-gradient(from 45deg, white, black, white);
background: conic-gradient(hsl(0,0%,75%), white 45deg, black 225deg, hsl(0,0%,75%));
Note that offsetting every color stop by the rotation angle instead would not work and produces an entirely different gradient:
background: conic-gradient(white 45deg, black 225deg, white 405deg);
background: radial-gradient(gray, transparent),
conic-gradient(red, magenta, blue, aqua, lime, yellow, red);
border-radius: 50%;
width: 200px; height: 200px;
background: conic-gradient(yellowgreen 40%, gold 0deg 75%, #f06 0deg);
border-radius: 50%;
width: 200px; height: 200px;
Repeating Gradients: the ''repeating-linear-gradient()'', ''repeating-radial-gradient()'', and ''repeating-conic-gradient()'' notations
In addition to ''linear-gradient()'', ''radial-gradient()'', and ''conic-gradient()'',
this specification defines repeating-linear-gradient(),
repeating-radial-gradient(),
and repeating-conic-gradient() values.
These notations take the same values
and are interpreted the same
as their respective non-repeating siblings defined previously.
background: repeating-conic-gradient(gold, #f06 20deg);
background: repeating-conic-gradient(
hsla(0,0%,100%,.2) 0deg 15deg,
hsla(0,0%,100%,0) 0deg 30deg
) #0ac;
background: repeating-conic-gradient(black 0deg 25%, white 0deg 50%);
background-size: 60px 60px;
The same checkerboard can be created via non-repeating conic gradients:
background: conic-gradient(black 25%, white 0deg 50%, black 0deg 75%, white 0deg);
background-size: 60px 60px;
Color Stop Lists
Color stops and [=transition hints=] are specified
in a color stop list,
which is a list of two or more [=color stops=]
interleaved with optional [=transition hints=]:
<color-stop-list> =
<
N:
Coloring the Gradient Line
At each color stop position,
the [=gradient line=] is the color of the color stop.
Before the first color stop,
the [=gradient line=] is the color of the first color stop,
and after the last color stop,
the [=gradient line=] is the color of the last color stop.
Between two color stops,
the [=gradient line’s=] color is interpolated between the colors of the two color stops,
with the interpolation taking place in premultiplied RGBA space.
By default,
this interpolation is linear--
at 25%, 50%, or 75% of the distance between two color stops,
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 color stops,
the interpolation is non-linear,
and controlled by the hint:
PlogH(.5)
.
(1 - C)
of the first stop and C of the second stop.
What does “pre-multiplied” mean?
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
,
which would then be expressed as
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
(opaque red)
and
(opaque blue),
or
(opaque red)
and
(transparent red))
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.
linear-gradient(90deg, red, transparent, blue)
With premultiplied colors,
transitions to or from "transparent" always look nice:
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:
Color Stop “Fixup”
When resolving the [=used value|used=] positions of each [=color stop=],
the following steps must be applied in order:
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 color stop 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 color stop's position would be equivalent to ''75px'',
which precedes the yellow color stop,
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
animations
or transitions.
1. linear-gradient(red, white 20%, blue)
=1=>
linear-gradient(red 0%, white 20%, blue 100%)
2. linear-gradient(red 40%, white, black, blue)
=1,3=>
linear-gradient(red 40%, white 60%, black 80%, blue 100%)
3. linear-gradient(red -50%, white, blue)
=1,3=>
linear-gradient(red -50%, white 25%, blue 100%)
4. linear-gradient(red -50px, white, blue)
=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)
=1,2=>
linear-gradient(red 0%, white 0%, black 150%, blue 150%)
7. linear-gradient(red 80px, white 0px, black, blue 100px)
=2,3=>
linear-gradient(red 80px, white 80px, black 90px, blue 100px)
Name: object-fit
Value: fill | none | [contain | cover] || scale-down
Initial: fill
Applies to: replaced elements
Inherited: no
Percentages: n/a
Computed value: specified keyword(s)
Animation type: discrete
The 'object-fit' property specifies how the contents of a replaced element
should be fitted to the box established by its used height and width.
If the content does not completely fill the replaced element's content box,
the unfilled space shows the replaced element's background.
Since replaced elements always clip their contents to the content box,
the content will never overflow.
See the 'object-position' property for positioning the object with respect to the content box.
fit
attribute in [[SMIL10]]
and the <preserveAspectRatio
attribute in [[SVG11]].
Note: Per the object size negotiation algorithm,
the concrete object size
(or, in this case, the size of the content)
does not directly scale the object itself -
it is merely passed to the object as information about the size of the visible canvas.
How to then draw into that size is up to the image format.
In particular, raster images always scale to the given size,
while SVG uses the given size as the size of the "SVG Viewport"
(a term defined by SVG)
and then uses the values of several attributes on the root <svg>
element to determine how to draw itself.
Image Processing {#image-processing}
====================================
Overriding Image Resolutions: the 'image-resolution' property {#the-image-resolution}
-------------------------------------------------------------------------------------
The image resolution is defined as
the number of image pixels per unit length,
e.g., pixels per inch.
Some image formats can record information about the resolution of images.
This information can be helpful when determining the actual size of the image in the formatting process.
However, the information can also be wrong,
in which case it should be ignored.
By default, CSS assumes a resolution of one image pixel per CSS ''px'' unit;
however, the 'image-resolution' property allows using some other resolution.
Name: image-resolution
Value: [ from-image || <
Issue: The ''image-set()'' notation can alter the intrinsic resolution of an image,
which ideally would be automatically honored without having to set this property.
How should we best address this?
Change the initial value to
As vector formats such as SVG do not have an intrinsic resolution,
this property has no effect on vector images.
img.high-res {
image-resolution: 300dpi;
}
With this set, an image meant to be 5 inches wide at 300dpi
will actually display as 5in wide;
without this set,
the image would display as approximately 15.6in wide
since the image is 15000 image pixels across,
and by default CSS displays 96 image pixels per inch.
img { image-resolution: from-image }
These rules both specify that the UA should use the image resolution found in the image itself,
but if the image has no resolution,
the resolution is set to ''300dpi'' instead of the default ''1dppx''.
img { image-resolution: from-image 300dpi }
img { image-resolution: 300dpi from-image }
img { image-resolution: 300dpi }
This rule, on the other hand,
if used when the screen's resolution is 96dpi,
would instead render the image at 288dpi
(so that 3 image pixels map to 1 device pixel):
img { image-resolution: 300dpi snap; }
The ''snap'' keyword can also be used when the resolution is taken from the image:
img { image-resolution: snap from-image; }
An image declaring itself as 300dpi will,
in the situation above,
display at 288dpi
(3 image pixels per device pixel)
whereas an image declaring 72dpi will render at 96dpi
(1 image pixel per device pixel).
cross-fade( (100% - t) start image, end image)
.
Issue: Special-case interpolating to/from no image,
like "background-image: url(foo);" to "background-image: none;".
Interpolating cross-fade() {#interpolating-image-combinations}
--------------------------------------------------------------
The three components of ''cross-fade()'' are interpolated independently.
Note this may result in nested ''cross-fade()'' notations.
Interpolating <Linear-Gradient( to bottom, red 0%,yellow,black 100px)
must serialize as:
linear-gradient(red, yellow, black 100px)
Changes Since the 11 September 2012 Working Draft
- Added color interpolation hints
- Added the two location syntax for gradient color stops
- Added start angles to conic gradients
- The position(s) of a color stop can now come before the color
- Text that is identical to [[css3-images]] has been replaced with a reference to [[css3-images]].
Changes Since Level 3
- Added the ''image()'' notation (deferred from Level 3)
- Added the 'image-resolution' property (deferred from Level 3)
- Added the ''element()'' notation (deferred from Level 3)
- Added conic gradients