I'm zooming out the contents of my awesome graph editor using transform: scale(x)
. When zooming-out the scale goes down towards 0
(exclusive), when zooming-in the scale goes up, up to a maximum of 1
(inclusive) which means full zoom or initial scale.
However, when zoomed-out considerably, image quality starts becoming really noisy -- please consider the following example, and notice how zooming out will make image appearance noisy:
var graphContainer = document.getElementById("graph-container");
var zoomInButton = document.getElementById("zoom-in-button");
var zoomOutButton = document.getElementById("zoom-out-button");
var zoomLevel = 1;
zoomInButton.addEventListener("click", function () {
zoomLevel = Math.max(1, zoomLevel - 0.25);
graphContainer.style.transform = "scale(" + (1 / zoomLevel) + ")";
});
zoomOutButton.addEventListener("click", function () {
zoomLevel = zoomLevel + 0.25;
graphContainer.style.transform = "scale(" + (1 / zoomLevel) + ")";
});
#editor-container {
background-color: #001723;
}
#graph-container { transform-origin: top center; }
<div id="editor-container">
<button id="zoom-in-button">Zoom in</button>
<button id="zoom-out-button">Zoom out</button>
<div id="graph-container">
<img class="shape" src="http://i.imgur.com/zsWkcGz.png" />
</div>
</div>
Demo also on JSFiddle
This image is a canvas-drawn shape that interactively visualizes a connection between two graph nodes, exported into png.
Please zoom out and see how noisy that line is, even though zooming is done in steps of 0.25
and with CSS. How can I get rid of this pixel-noise? The issue happens in both the latest Google Chrome and Microsoft Edge, untested in other browsers. The issue happens with and without 3D GPU acceleration.
Note: obviously, this is a Minimal, Complete, and Verifiable example and the real work is magnitudes more complex.
Hundrends of line shapes are drawn procedurally onto a canvas (<1ms per line) and then cached to
img
elements asynchronously using toDataUrl (~40ms per line) when idle, so that screen panning -- which is also a required feature -- works more smoothly, as moving animg
element on the screen is much cheaper than moving acanvas
element (or redrawing all lines on a single canvas), whether it's the element itself, the container, or the browser viewport that is translated into a given direction.As such, generating mipmaps is not really an option, or only as a last resort, as it will come with a significant performance penalty (each mip-level would have to be cached onto a separate image, cutting performance in half at the very least). I'd like to believe it can be avoided, though. Redrawing line shapes on each zoom step will mercillesly obliterate performance down to a slideshow.
The following is the list of things I tried, no effect:
image-rendering: pixelated | optimizeSpeed | optimizeQuality
transform
scale3d
instead of scale
img.width
and img.height
and then compensating by doubling img.style.width
and img.style.height
img
and instead displaying the canvas
element directly (slower performance, same bad quality)I also tried using filter: blur
when zoomed out, but it did not yield a better quality, as the blur effect itself is applied after the given shape has been rendered on screen.
What else can I do to get rid of this pixel-noise, besides creating downsampled versions of each shape, effectively creating a software-rendered mipmap (LOD) system? (which, as I wrote, would like to strongly avoid)
How is this question different from the array of similar questions investigating bad image quality from downscaling?
In particular, you need mix-blend-mode and CSS filters. Hover over the image to see the effect. Tweaking of parameters (and maybe using opacity to "fade" the effect, similar to the "intensity" setting of unsharp mask) can help in yielding more desireable results for particular use-cases.
One of the simplest ways to resize an image in the HTML is using the height and width attributes on the img tag. These values specify the height and width of the image element. The values are set in px i.e. CSS pixels. For example, the original image is 640×960.
You can get much better results using SVG images instead of PNG and they are also very easy to generate and embed in your code, you don't even need to host them.
As you can see in this demo the SVG won't pixelate or become blurry and you will get even better results in high resolution screens like the Retina ones.
The SVG code:
<svg class="shape" width="440px" height="319px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M1,316.040132 C1,316.040132 241.5837,339.680482 241.5837,163.497447 C241.5837,-12.6855883 439,2.31775322 439,2.31775322" id="Path" stroke="#50E3C2" stroke-width="2"></path>
</g>
</svg>
Obviously is not perfect.
The interpolation of an image with sharp edge will inevitably create artifacts. Even if it is antialised, even if it is blured. You can test this in Photoshop itself, you'll get the same result.
Even the mipmap approach won't work.
The inherent problem is that pixels aren't able to retain all the information of your complex vector shapes; and when you squeeze pixels, shapes get lost. There are two solutions to improve the quality:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With