Why tiny JPEGs look different in Chrome
guillaumetech.github.io
guillaumetech.github.io
When Chrome introduced this "optimization" and it made it through to an Electron release which we were upgrading to, it really messed up the icons in a lot of places in our product such that we had to hold off the upgrade until we had SVGs to replace them.
SVGs also have the advantage of being able to respond to light/dark mode. We just needed to put each icon in its own shadow DOM to avoid styles clashing between the different SVGs if they happen to be named the same which was a bit of an annoyance for our graphic designer.
The icon appeared bigger in some places than others and wanted it to look decent on higher DPI displays.
SVGs are so much better for this though, smaller in size and look great at pretty much any resolution.
(still different from JPEG's method, of course)
What do you mean by "named the same" here?
The problem is if these same element ids and styles are used in different places in your DOM, including other SVGs.
Our product is a bit of a platform with extensibility which includes icons for ui elements. It was very easy for us or our users to accidentally use the same style names or element ids when exporting from Adobe Illustrator, especially for icons which are similar to others and started as a copy.
When using SVGs as a background it’s not an issue, but we want our SVGs to be able to have different colours be usable in light or dark mode which CSS can do, except it doesn’t work when making the SVG a background.
Shadow DOM solves this issue perfectly.
If the same class was defined in a multiple SVGs under their respective <style/> elements, they would affect each other.
So sometimes if two icons were in the DOM at the same time, the one’s colours might look wrong since both had the same class names they were using.
Element ids were also not used so much for styling but instead for common SVG components (or something, I don’t know the terminology), so would similarly cause rendering issues.
Like I said it was especially common if the one started as a copy of the other in Illustrator.
The “fix” was to ensure unique element ids and class names, but this was an annoying extra step.
Using a shadow DOM for each icon which is an SVG circumvents the entire problem as they ignore anything outside their isolated DOM.
What's so funny about this is that XML is absolutely brimming with rich namespace solutions for all the problems you don't have. For example, if your users ever want to blithely roll their own "href" namespace, don't worry! The xmlns http://www.w3.org/1999/xlink is there for them to safely disambiguate all the custom:hrefs from the xlink:href things!
Meanwhile, in 2026 you've got two designers who each use their own "bgcolor" and XML is like, "Nope!"
Digression-- I just checked MDN and apparently xlink is deprecated! This makes me want to write alternate history from the mid-2000s about the href wars: XML namespacing parties where front-end designers would mash up dozens of href namespaces in the same document.
Even toy examples! I'm trying to think back through my recollection of the W3C archives, and I can't remember a single time when a proposed feature had a demo that utilized XML namespaces. Unnamespaced pseudocode, yes. JS polyfills, yes. XML namespaces? Please link me some examples for my fan fiction research.
Have you tried adjusting the image-rendering css property?
We may also have moved to the SVGs anyway as they also look much nicer when running at different zoom levels like 125%.
https://stackoverflow.com/questions/47139528/html5-canvas-te...
My inner paranoiac says it is something like yellow dots[0] for browsers but it could just be negligent developers.
And, more importantly, you should use images that are an appropriate resolution for the size they will be displayed. Even if you switch to PNG, using a 2000x2000 image for a icon displayed 20x20 is a waste.
The number of web sites that do this is enraging.
The same Firefox on macOS shows different favicons depending on if I'm using it on the laptop screen or an external one (probably because of different pixel densities). It's crazy!
Some encoders (and a meaningful share of JPEGs in the wild) leave zeros or garbage in the unused rows/columns of the trailing MCU. At full-size decode those samples are cropped away, but IDCT scaling mixes them into neighboring output pixels and the artifacts become visible. See https://crbug.com/890745 and https://github.com/libjpeg-turbo/libjpeg-turbo/issues/297
In our particular case we were able to just crop out the edges and it was worth the tradeoff
The reason I believe the scaling algorithm is contributing more in your particular example, is because it looks almost exactly like a classic B/W comparison between a sharp ringing heavy algorithm and a more blurry one.
For reference here is a quick comparison between Lanczos/Bilinear I threw together: https://twinlens.app/compare?share=40973d84a174
It would be interesting to see how your example would look if you used colors instead, where the ringing artifacts become a lot more obvious. Like this one: https://twinlens.app/compare?share=8fb890a5820e
Another interesting bit is that It wasn't so easy to get an image showing the issue a clear as we had it. My hunch is that moiré patterns might have a role in this, as most images where the the issue showed, had some kind of grid/repetition. I suppose some images might be more affected by the IDCT and other more affected by the scaling algorithm, making it more or less visible. It's a mix like you said!
That said, it's limited due to the training set, iirc. So we still use PSNR regularly — it's an easily-understood metric with widely-understood limitations, which is in practice not too bad. Devil you know, etc.
Mozilla wringed out more from JPEG for Meta/Instagram about a decade or so ago, and their library would be noteworthy in this article, for comparison. The basic idea was that JPEG was devised when CPU cycles were expensive and displays were analogue, meaning that only a limited effort was made to optimise, resulting in things like banding, that OG CRTs handled well, unlike modern digital displays (on systems with 1000000 more CPU).
Regardless of what tools you are using, going from 'gigapixels' to a thumbnail is going to be a two or more step process, with a colourful item rendered a grey mush, when you really needed some of the colours in the original.
Few respect the pixel blocks of JPEG (8 x 8) and most graphic artists have no knowledge of the powers of two. Once upon a time it was necessary to align image dimensions with important binary numbers, particularly if working with texture maps for early 3D graphics.
The other thing going on in the article is that practically every web screen should be considered 'retina' resolution, with everything apart from the lamest office PC having a pixel resolution equivalent to 1.5x the amount of pixels shown 'nominally'. If the web stats say '1280 x 720' as a oft-used resolution, regardless of device, that will be a 1920 x 1080 screen at 1.5x, with Chrome and other browsers packing in 1920 rather than 1280 pixels across, if the image has the pixels in it for that.
That said if you are using the web to develop UI, it’s accepted that different browsers and users get slightly different renderings, unless you write your own image renderer with canvas. If you do care that much you should probably write native code.
In almost every case, downscaling-while-decoding is going to beat decode-then-scale on performance - merely keeping the uncompressed image in RAM is going to cost you, when you can scale it down in L1 or even vector registers.
In other news, AVIF is at 95%: https://caniuse.com/avif so it's probably time to give it the first preference over jpg.
We have jpeg, webp, avif and jpegxl. Portrait orientation and landscape. 3 or 4 different pixel ratios and of course, different screen resolutions. Dark mode support doubles all of that work. Hardly anyone is going to do all of this correctly, automated or not.