25

When using the CSS zoom property, how can I convince the browser to use "nearest neighbor", instead of "bilinear" or any other more advanced zooming algorithms?

My setup is a div that contains a canvas, and the div gets its zoom set via JavaScript to be <div style="zoom:3200%">...</div> and to get nearest neighbor, I am using image-rendering: -webkit-optimize-contrast in my CSS. The app is available here ('z' zooms in, 'shift-z' zooms back out), and my css is here

Here is the desired effect in Chrome on OSX (zoom is set to 3200%): zoom on OSX/Chrome using nearest neighbor

But here is the same thing in Chrome on Windows 7: zoom on Windows/Chrome not using nearest neighbor

In both cases it is "vanilla" Chrome (version 15.x.x) out of the box, no experimental flags are turned on.

How can I convince Chrome on Windows to use nearest neighbor? For that matter, how can I convince all browsers? Safari also does not use nearest neighbor (so far the app only works in WebKit based browsers)

The CSS image-rendering property does affect Chrome/OSX and gives me the desired effect. But Chrome/Windows and Safari(5.1)/OSX both seem to completely ignore it. Something tells me I'm just out of luck, but I figured I'd ask.

Using zoom on the div container is so simple and works beautifully in Chrome/OSX, if I must resort to scaling my canvases internally, I can do that too. But would prefer the literally one line of code solution if possible!

UPDATE: I have found the use of image-rendering: optimizeSpeed helps. However it seems finicky in Chrome/Windows. If I set it on too many elements (I initially tried, my containers and all canvases), it doesn't take effect. But if I apply it to just canvas, I get 98% of the way there.

Now my problem is the first time I draw while zoomed in, it works perfectly, all other subsequent drawing actions are fuzzy while they are taking place, then revert to the correct nearest-neighbor afterwards (my app draws into a scratch canvas first, then applies the drawing to the real canvas). There is something odd about the scratch canvas where Chrome insists on using bilinear. I think with some digging I can resolve that.

UPDATE2: It seems like image-rendering on Chrome/Windows just isn't implemented well and is a bit buggy. I can now confirm that the values optimizeSpeed and optimizeQuality are not supported on Chrome/Windows. If you set image-rendering to them, Chrome will ignore the set. Chrome/Windows does recognize -webkit-optimize-contrast, however it does not use it consistently. Chrome will flip between what looks to be a bilinear scaling algorithm and nearest-neighbor almost at random. I've not been able to consistently get Chrome to use nearest-neighbor.

I tried a build of Chromium 17 on Windows and it behaves the same way.

Firefox (8.0.1) is looking pretty promising though as it does seem to honor -moz-crisp-edges quite well. Originally I was targeting Chrome as my "ideal browser" for this app, I might just switch over to Firefox.

In the end, it seems like proper support for image-rendering is in the pipeline for Chrome, just not quite there yet. WebKit itself claims to fully support all image-rendering values, but I'm guessing the build of WebKit that Chrome uses hasn't quite updated to this new fix.

Matt Greer
  • 60,826
  • 17
  • 123
  • 123
  • 2
    opacity: 0.99 used to disable the antialiasing of text, but it did nothing with your canvas in safari/mac. – biziclop Nov 09 '11 at 17:07
  • I would love to comment with something useful, but it is looking pretty bleak. There may be a way, but it would be very difficult to find a solution for every browser. Here is another question on the topic, although I bet you have already seen it. http://stackoverflow.com/questions/5364181/css-zoom-not-blurry-but-pixelated If this is in a canvas, could you scale the content of the canvas programmatically? So draw everything with 2 * the coordinates and 2 * the size? – Olical Dec 02 '11 at 13:24
  • Yeah, my solution has been to wait for the browsers to catch up. So far Chrome on OSX and Firefox on all platforms meet my needs. I think Opera does too, still investigating that. I'm hoping Google fixes the bug on Windows. I could scale the canvas itself using transforms, but that is pretty complicated and I have my doubts I could get it to perform well enough. My app can have 20+ canvases all going at once zoomed to 6400%. Nearest neighbor plus css zoom (scale transform in Firefox) is amazingly performant, and I doubt I can replicate that performance with canvas transforms. – Matt Greer Dec 02 '11 at 13:56
  • 2
    I filed a bug in chromium for this: http://code.google.com/p/chromium/issues/detail?id=106662&q=image-rendering&sort=-id&colspec=ID%20Pri%20Mstone%20ReleaseBlock%20Area%20Feature%20Status%20Owner%20Summary – Matt Greer Dec 08 '11 at 20:09
  • If this problem is affecting you please vote for / star @MattGreer's bug report at the link above! – UpTheCreek Sep 07 '12 at 08:57

3 Answers3

17

There is a way. I asked a question similar to this over here: How to stretch images with no antialiasing and got a really good response. I've modified it quite a bit since then, but here's a simple version which stretches (w/out anti-aliasing) when clicked: http://jsfiddle.net/howlermiller/U2eBZ/1/

It's incredibly hackish though. Don't use it unless you must. It has to re-create the image every time you click on it, so with a big image it would be slow and terrible. But it does work on Chrome/Windows.

Edit: This is supported in the CSS3 spec now! Support is super spotty at the moment, but Chrome just added support so I'm hopeful that we'll be able to use it before too long. Good times!

.thing {
  /* IE, only works on <img> tags */
  -ms-interpolation-mode: nearest-neighbor;
  /* Firefox */
  image-rendering: crisp-edges;
  /* Chromium + Safari */
  image-rendering: pixelated;
}

Demo

Timothy Miller
  • 2,391
  • 1
  • 27
  • 34
  • 1
    Firefox supports the `crisp-edges` value for `image-rendering` (though Chrome does not). Until one of the browsers supports both, I'd recommend using both values. – CoryCoolguy Feb 08 '20 at 16:35
1

ctx.webkitImageSmoothingEnabled=false now seems to work since 22.

Agamemnus
  • 1,395
  • 4
  • 17
  • 41
0

Since you're using canvas anyway, you might as well implement your own zoom and manually implement nearest neighbour resizing.

I suspect even if you solve this problem, you'll run into something else one day.

@Timothy's answer seems to have some good code you can use to help with the actual math.

Abhi Beckert
  • 32,787
  • 12
  • 83
  • 110
  • 1
    Yeah, I did have manual zooming implemented as a spike to see what it would take. It's definitely doable, but it's very memory and processor intensive. So much so that even a 500x500 canvas becomes unusable. It might be worth it to keep exploring this route and see if I can get the memory/performance under control though. – Matt Greer Dec 27 '11 at 00:06
  • It seems like you should be able to do this with high performance and not much memory usage. Maybe you can layer multiple canvas elements on top of one another to avoid re-drawing parts of the canvas that haven't changed? – Abhi Beckert Dec 27 '11 at 00:14
  • A typical use case for my app is to zoom way in (6400% is very common) and then pan around the image (think doing fine detail work in Photoshop, that fine detail work is the main mode of operation for pixel art). There are other reasons why I'd like to implement "only draw what's currently visible", so I do plan to explore this. But with the typical use case, the app will be forced to update the scaled image quite a bit. If it pans around sluggishly, then the app fails at its most fundamental/essential operation. – Matt Greer Dec 27 '11 at 00:27
  • Perhaps if it's too slow, you could look into WebGL. But really, I think you should be able to perform simple drawing like this in ~1/100th of a second if your code is efficient. Be interested to see how it works out. – Abhi Beckert Dec 27 '11 at 00:41
  • When the question is "how do I nearest-neighbor scale a canvas with CSS sizing", saying "do it with canvas" isn't a helpful response. It's also a bad idea--writing a resizing algorithm in JS will be massively slower than native, even for nearest neighbor. – Glenn Maynard Jul 12 '14 at 17:11
  • @GlenMaynard as I said, I think it can be done in 1/100th of a second which is faster than an LCD screen's refresh rate. That is not "massively slow". – Abhi Beckert Jul 13 '14 at 18:23