最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Improving image quality of CSS-downscaled elements - Stack Overflow

programmeradmin5浏览0评论

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 being 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=".png" />
    </div>
</div>

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 being 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./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 plex.

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 an img element on the screen is much cheaper than moving a canvas 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 e 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:

  • Hinting at better image quality by defining the following CSS property values, on the shapes elements itself, or the container:
image-rendering: pixelated | optimizeSpeed | optimizeQuality
  • Forcing elevating the rendering layer into GPU acceleration with a dummy transform
  • Using scale3d instead of scale
  • Halving img.width and img.height and then pensating by doubling img.style.width and img.style.height
  • Not caching canvas results into 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?

  • Here, it's the container that is downscaled (and with CSS transformations), instead of individual image elements. No manipulation is done to actual image data, which is what is discussed in similar topics.
Share Improve this question edited May 23, 2017 at 12:33 CommunityBot 11 silver badge asked Aug 3, 2016 at 12:30 John WeiszJohn Weisz 32.1k14 gold badges96 silver badges136 bronze badges 16
  • 1 I do not see any pixel-noise in Chromium. Version 48.0.2564.82 Ubuntu 15.04 (64-bit). – TrampolineTales Commented Aug 3, 2016 at 12:56
  • 1 Why do you think would make a difference if you're setting image dimensions or using transforms? This question is indeed not different from similar questions at all. If setting the image-rendering attribute doesnt help, you can only try to use a bigger image to begin with, eg. for a 200x200px target resolution use a 400x400px image, and the downsampled oute should be smoother at non integer scales. – j4k3 Commented Aug 3, 2016 at 12:57
  • 4 How pixel images scale down will also vary from one OS to the other and one browser to the other. I did a test on MBP - Retina and standard screen (Firefox and Safari). It looks alright to me (try zoom): imgur./a/E2l2B – eye-wonder Commented Aug 3, 2016 at 13:00
  • 3 I personally think that SVG would be the way to go here. Scalable Vector Graphics and all that. The demo you've provided is a simple path with one Bézier curve tipped with a marker-end arrowhead, for instance. – Niet the Dark Absol Commented Aug 3, 2016 at 13:05
  • 1 @john-white You are already downsampling, client-sided, if you're displaying the image at a different size than it's rendered. – j4k3 Commented Aug 3, 2016 at 13:11
 |  Show 11 more ments

2 Answers 2

Reset to default 3

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 bee 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/2000/svg" xmlns:xlink="http://www.w3/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 plex vector shapes; and when you squeeze pixels, shapes get lost. There are two solutions to improve the quality:

  • either render the picture at a higher resolution before scaling it so there's more pixels to store the intent of your shape
  • or directly render your vector shapes at the scale you're aiming
发布评论

评论列表(0)

  1. 暂无评论