When displaying images naively, you may end up losing image quality because of a relatively unknown phenomena. If you happen to display an image with a dimension that is one pixel off the real image dimension, the resizing operation (which is costly in the browser) is going to be the equivalent of a blur. See the following example:
130x130 | 129x129 |
When you look at it from an external perspective, it seems to be very intentional to display and image with a dimension that is one pixel off. However it can happen for many reasons, some are bugs and some are legitimate.
Grid Sizes
Let's say the content area where you want to display a 4-columns image grid has a width of 500 pixels. And you want to have the same padding in the edges as in-between the images.
\[4 * image{ }width + 5 * padding = 500\]\[image{ }width = \frac{(500 - 5 * padding)}{4}\]
The only padding value between 2px and 8px that give an integer number for the image width are 4px and 8px. But unfortunately, none of them look good, you really want 6px padding.
In this case, you want to cheat and don't have all the same width and padding but make some of them 1 pixel smaller.
You can for example say that edges will have 5 pixel and inside 6 pixels. However this is a bad idea because it is going to be visually visible. By changing from 5 to 6 you are doing a variation of 17%.
\[5 + 118 + 6 + 118 + 6 + 118 + 6 + 118 + 5 = 500\]
Instead you want to borrow a pixel from the images. Having two with 127px width and two with 128px width. The difference is not visible by the eye.
\[6 + 117 + 6 + 118 + 6 + 117 + 6 + 118 + 6 = 500\]
So now we are in a situation where we want to display an image with 1 less pixel. In order to do that without bluring the image, the trick is to use a container with the size you want to display with overflow: hidden;
and inside the properly sized image.
<div style="overflow: hidden; width: 129px; height: 129px;"> <img src="130x130.png" width="130" height="130" /> </div> |
130x130 | 129x129 |
Chrome bug
Being one pixel off is really easy, the main cause is different rounding. One one part of the code you use round()
and in another part you use floor()
. If the number is decimal, you have half chances to get a wrong result. For example, there is currently a bug in Chrome where hardware accelerated rendering has similar issue.
In order to get good scrolling performance, we enable hardware acceleration using transform: translateZ(0);
on all the visible images on the viewport. However, when we mouse over an image, we display some overlay and therefore decide to remove hardware acceleration for it to avoid thrashing GPU memory.
To display images, we use a container as described above with the CSS property left: -7.92%;
to position the image properly in the viewport. The result is that the image is moving around when you mouse hover it on Chrome. There is probably a different rounding applied between the CPU and the GPU code. The net effect is the image being resized by one pixel and blurry by default. When you mouse over, the image has the correct size.
In order to fix the issue, we can use integer number in pixel left: -24px;
instead. This way the browser doesn't have to round anything.
This is only one of the many similar issues with the browsers handling rounding differently. People implementing fluid layout suffer a lot because of browser inconsistencies. If this is happening in browser implementations, there is also a high probability that this issue is going to appear in your own code if you didn't make sure it was rounding as expected.
Conclusion
This problem is very common and comes from many different sources, but always because of the same root cause: rounding issues. Since sub-pixel rendering is not widely implemented, it is not going to disappear. I hope that you are now aware of it and will address it to avoid affecting image quality of your thumbnails 🙂