CSS offers a nifty property called image-rendering
which lets you influence how images scale. Normally, when you take a small image and make it bigger, the image becomes blurry. That's kinda ok for photos, but for pixelart, the effect is... rather devastating.
What is pixelart?
Pixelart is a way of creating pictures. Rather than using brush strokes, each individual pixel is carefully colored. It's like if you were given just a few hundred square tiles and asked to make a mosaic out of them.
However, thanks to image-rendering
, it's possible to upscale images in a way that highlights their pixelated nature! Just a couple lines of code later and...
.pixelart {
/* This order matters! */
image-rendering: crisp-edges;
image-rendering: pixelated;
}
...hooray! It looks fantastic!
Except, wait just a second. Why on earth does the above example CSS specify image-rendering
twice, and with two different values? Something's fishy!
The Paradox
Permalink to "The Paradox"It turns out there's a bit of a paradox in the browser support for image-rendering
. Waddle on over to Can I Use and we see the following weirdness.
That's right! Chrome supports pixelated
but not crisp-edges
, and Firefox supports crisp-edges
but not pixelated
. In order to support both browsers, both values had to be specified in the example above, utilizing the fact of CSS that if one value is invalid then the other will be used.
What I learned, though, is that resolving this paradox is not as simple as "just specify both properties", because the properties have different semantics. That is, even though crisp-edges
and pixelated
accomplish the same result, they mean different things.
To resolve this, we'll need to embark on a mystical journey through different versions of the specification, discussions on implementations, and—
Well actually, I already did all that! Instead, I'll walk through what I discovered by answering three key questions:
- Why is there a paradox?
- What is the difference between
pixelated
andcrisp-edges
? - What CSS should I use for pixelated images? What about for images with crisp edges?
What's with the paradox?
Permalink to "What's with the paradox?"Without being involved in the dialogue directly, it's hard to pinpoint the precise reasons why image-rendering
has the support it has. After delving through documentation, discussions, and definitions (oh my!), there was one truth underpinning it all.
image-rendering
should work are not set in stone.
In fact, despite this property having first appeared in 2012, just a few months ago in the images specification changed! And furthermore, it had seen periodic change over its nine-year history. In other words, image-rendering
is undergoing active discussion, at least some of which has come from feedback from browsers implementing the feature for testing.
What is the CSS Specification?
The specification is a set of documents detailing what the features of CSS are, how they should be used, and how they should be implemented by browsers. It's basically the source of truth for what CSS is and will be, and has been in constant development since its inception decades ago. Feel free to read more about the CSS standardization process.
As a result, the property has only ever been implemented to different degrees, with Firefox and Chrome having taken different routes. And since the spec is still under discussion, no one has a complete implementation.
In the end, Firefox developed crisp-edges
because it already supported the non-standard property -moz-crisp-edges
which represents the nearest neighbor algorithm. Chrome had implemented pixelated
because, at the time in 2014, the spec for pixelated
was more straightforward.
If you want to learn more about the history, I've got some extra bits at the end of the article!
Pixelated or Crisp Edges?
Permalink to "Pixelated or Crisp Edges?"To grasp the pixelated
and crisp-edges
values, it's important to understand the purpose of the image-rendering
property.
The Semantics of image-rendering
Permalink to "The Semantics of image-rendering"I'm gonna unoriginally paste a direct quote from the CSS spec, emphasis added:
The image-rendering property provides a hint to the user-agent about what aspects of an image are most important to preserve when the image is scaled...
When an image is scaled, the computer either has to fill in missing details when scaled up or choose what to collapse when scaled down. That can be tricky, kinda like doubling a cooking recipe but realizing you don't have enough ingredients. And so, there's no single correct strategy for scaling images, leading to a diversity of scaling algorithms meant to do the job.
That said, notice that the spec does not say that the purpose of image-rendering
is to choose a scaling algorithm. Rather, the goal is to specify what aspects of an image are most important to preserve
. For example, when we scale an image, do we care more about the way colors blend, or about keeping the edges sharp? Depending on the answer, one algorithm may be better than another.
Though a scaling algorithm will be ultimately chosen, the point of image-rendering
is really to provide the browser additional information so it knows better how to treat the image!
The Semantics of pixelated and crisp-edges
Permalink to "The Semantics of pixelated and crisp-edges"Knowing that image-rendering
is all about identifying what aspects of the image are important to preserve, we can see how pixelated
and crisp-edges
are defined.
- pixelated
- The image is scaled in a way that preserves the pixelated nature of the original as much as possible.
- crisp-edges
- The image is scaled in a way that preserves contrast and edges, and which does not smooth colors or introduce blur to the image in the process.
For pixelated images, the emphasis is on the pixels, but for crispy images, the emphasis is on the edges. The key point here is that pixels are not the same as edges!
pixelated
and crisp-edges
values are not semantically interchangeable.
We can illustrate the difference by scaling up our pixelart Lea image by a non-integral factor, say 2.5 times the original size, using algorithms the spec currently mandates.
What algorithms does the spec mandate?
For crisp-edges
, the nearest neighbor algorithm is used.
For pixelated
, nearest neighbor is used to take the image to the nearest integer scale. Afterward, a smooth-scaling algorithm takes the image the rest of the way.
- pixelated
- crisp-edges
- auto
Because the image is scaled by a non-integer, the resulting enlarged "pixels" cannot all be the same size. Therefore, a compromise must be made, and the different rendering values make different compromises as a result of their semantics.
- For
pixelated
, pixels must be square, and the only way to preserve that property is to allow the enlarged pixels to overlap. The blurring on cell boundaries represent places where pixels are overlapping. - For
crisp-edges
, blurring is not allowed since the contrast between colors is most important. Resizing a pixelart image, therefore, results in cells that are not square, which distorts the pixelation aesthetic. - And
auto
, the browser default and included here mostly for reference, treats the image like a photo where smoothing is both allowed and expected.
Resolving the Paradox
Permalink to "Resolving the Paradox"Equipped with the history and semantics of image-rendering
, we can resolve the paradox!
For pixelart, it is clear the pixelated
value should be used; that's what most closely matches the semantics of the art. However, since Firefox does not yet support pixelated
, we can fall back onto its currently provided solution, crisp-edges
, which will resolve to the nearest neighbor algorithm.
.pixelart {
image-rendering: crisp-edges;
image-rendering: pixelated;
}
The fact that pixelated
is last is very important! If we imagine a future where Firefox has implemented pixelated
, then we want that value to be applied instead of crisp-edges
. Letting the most semantically appropriate value be last future-proofs the solution.
And what about images which should have high contrast?
Chrome and Safari do not support crisp-edges
, but instead support a webkit property called -webkit-optimize-contrast
which bears similar semantics. Therefore, rather than use pixelated
, it is better to use something that more closely resembles what crisp-edges
means:
.crispy-art {
image-rendering: -webkit-optimize-contrast;
image-rendering: crisp-edges;
}
And with that, we can celebrate with the final demo!
Extra Stuff
Permalink to "Extra Stuff"The article's basically done now, but if you want more I got more!
Resources
Permalink to "Resources"- Pixelart is of Lea from the game Cross Code by Radical Fish Games - I highly recommend this game
- Can I Use - current browser support for
image-rendering
- CSS Images Module Level 3 - the current Editor's Draft of the specification for CSS images
- Chromium Issue - Tracking Chrome's supprt
- Mozilla Issue - Tracking Firefox's support
- Pixelated Github Issue - Tracking recent updates to the spec
Timeline of image-rendering
Permalink to "Timeline of image-rendering"As part of my research, I tried to uncover as much of the history of image-rendering
as I could. Here's the best timeline I'm able to come up with.
- September 2012
image-rendering
first appeared in a draft of the CSS Specification.- See CSS Image Values and Replaced Content Module Level 4, archived from 2012
- November 2013
- Browsers began implementing the
pixelated
value. - See the Chromium Issue for Chrome, and Mozilla Issue for Firefox
- September 2014
- Behind-the-scenes discussions surfaced difficulty in implementing downscaling, and so the specification was revised to loosen requirements.
- See the mailing list summarizing the discussion
- October 2014
- Firefox deprioritized implementing
pixelated
, because doing so required substantial changes and testing. Since Firefox already offered-moz-crisp-edges
which accomplished the same result, it was deemed sufficient. - See Downscaling Mozilla Issue
- December 2014
- Chrome finished implementing
pixelated
, using the Nearest Neighbor algorithm for both upscaling and downscaling. - See Chromium Issue
- November 2018
- Firefox unprefixed
-moz-crisp-edges
, thereby officially supportingcrisp-edges
. - See Crisp Edges Mozilla Issue
- October 2019
- The
image-rendering
property is moved from the level 4 spec into level 3, and made into a candidate recommendation. - See CSS Images Module Level 3, archived from 2019
- January 2021
- An issue is raised on Github with concerns on the spec definition for
pixelated
. The concern was the algorithm recommended by the spec did not adequately fulfill the intended semantics of the value. - See Pixelated Github Issue
- February 2021
- The Editor's Draft of the CSS Images Spec is updated to reflect new recommendations for
pixelated
andcrisp-edges
given the Github issue a month earlier. - See CSS Images Module Level 3, the current Editor's Draft
What changed in February 2021?
Permalink to "What changed in February 2021?"In , the CSS specification for image-rendering
was updated in a non-trivial way. Although the semantics for pixelated
and crisp-edges
remained the same, the recommended algorithms for each changed.
It's easiest to see the change when comparing the description for pixelated
between the current Candidate Recommendation (from ) and the more recent Editor's Draft:
- Candidate Recommendation
- The image must be scaled with the "nearest neighbor" or similar algorithm, to preserve a "pixelated" look as the image changes in size.
- Editor's Draft
- For each axis, independently determine the integer multiple of its natural size that puts it closest to the target size and is greater than zero. Scale it to this integer-multiple-size as for crisp-edges, then scale it the rest of the way to the target size as for smooth.
Meanwhile, crisp-edges
was changed to explicitly call for the nearest neighbor algorithm.
This change was introduced in order to better fit the semantics of pixelated
. The nearest neighbor algorithm does not preserve the squareness of pixels when sized to non-integer scales; the new algorithm, on the other hand, introduces some blurring along pixel borders, but overall retains the gridded look pixelart should have.
Perhaps more interesting, however, is the change to crisp-edges
. Although the semantic definition of the value is unchanged, the provided example is dramatically different. Both images below represent the same source image being upscaled several times, but are from different versions of the spec.
Long story short, this property has been and presently is a point of discussion.