Is the example at http://phrogz.net/tmp/canvas_image_zoom.html the only way to get nearest neighbor scaling in Chrome?



  • First post!

    I’ve been trying to figure out how to scale the size of my game in Chrome. In theory, I should be able to use this stack overflow answer: http://stackoverflow.com/a/10525163/61624 But after digging deeper, it doesn’t seem to work in Chrome because of a bug. I found this site that gives an example, in code, of how to accomplish the look I want: http://phrogz.net/tmp/canvas_image_zoom.html I want the one that’s titled “Zoomed by Code”, because all the others smooth the image and make it look terrible.

    But this code doesn’t really have much explanation on how to use it.

    • I’m not even sure if I can use it on canvases, although I’m assuming I can.
    • Do I need to call this every time I render my game, or just each time the window resizes?
    • My game has canvases nested inside canvases. Do I need to call this on each one, or just on the parent that contains the others?
    • Most importantly, is there an easier way to get nearest neighbor scaling in chrome? This algorithm is pretty invasive and will require me to refactor a lot of code.

    I listen to the podcast (and love it by the way), and they mention that they release on node-webkit. So I assume the Lost Decade guys have had to deal with this issue, too.

    Thanks a lot! Once again, I love the podcast. It gets me psyched to work on my game. I listen to it on the drive to and from work :)


  • Tiger Hat

    @Josue did a Low Rez Jam game entry in HTML5 and JavaScript and Chrome’s his environment. And I’m pretty sure he did what was in the phrogz code, basically represented points as larger squares. When I tried to retain pixilation without antialiasing on zoom I was using imageSmoothingEnabled which as you’ve pointed out in the linked Stackoverflow post is horrendously broken right now. For what it’s worth I’d suggest you create a Boolean to pass into your rendering function called

    imageSmoothingEnabled = false

    Then in your rendering function check for that Boolean and implement the phrogz code, then when Chrome fix imageSmoothingEnabled (if anyone fixes it first, it’ll be them most likely, it’s broken across pretty much all browsers), remove the code from your rendering function and change

    imageSmoothingEnabled = false

    to

    ctx.imageSmoothingEnabled = false

    I’d say that’s your smoothest solution outside of a library. I’m pretty sure @geoffb and @richtaur pre-scale their assets down for their game so they may not have had to apply on-the-fly zoom code yet. Let us know how it goes. Welcome to the forum!


  • LDG

    Welcome to the forum, glad you enjoy the show!

    I’ve actually been watching Chrome bug 13040 for… uh wow over 2 years now. This appears to be the primary issue tracking Chrome’s incompatibility with nearest neighbor/crisp edges scaling. The good news is it’s had some recent progress! The bad news is there’s no way to be sure when the fix will land in node-webkit. ImpactJS probably has the best fallback (article), which is something along the lines of:

    1. Load image
    2. Draw image to a hidden canvas, scaled up via software to ensure crisp edges
    3. Save this to a buffer and use it to draw instead of the image

    We haven’t used pixel-perfect scaling in our pixelated games (Onslaught! Arena’s assets were doubled-up at export), but our games do scale based on the viewport size. For example, in A Wizard’s Lizard you can resize the window and it just stretches the canvas to fit. So basically I’d advice: either avoid it or do what’s easiest/fastest for now, don’t let it slow you down, because it’ll be fixed eventually and then that work can be scrapped. Good luck!


  • Patron

    @Affordable_Desk said:

    @Josue did a Low Rez Jam game entry in HTML5 and JavaScript and Chrome’s his environment. And I’m pretty sure he did what was in the phrogz code, basically represented points as larger squares.

    Actually, I rescaled my assets in Gimp…

    But ctx.imageSmoothingEnabled works perfectly in Chrome 36 (= false on the left and = true on the right):

    chrome

    Here’s the code:

    var canvas = document.createElement("canvas");
    canvas.width = 32;
    canvas.height = 32;
    var ctx = canvas.getContext("2d");
    ctx.imageSmoothingEnabled = false;
    
    var canvas2 = document.createElement("canvas");
    canvas2.width = 640;
    canvas2.height = 480;
    var ctx2 = canvas2.getContext("2d");
    ctx2.imageSmoothingEnabled = false;
    document.body.appendChild(canvas2);
    
    var sonic = new Image();
    sonic.src = "sonic.png";
    
    sonic.onload = function()
    {
     ctx.drawImage(sonic,0,0);
     ctx2.drawImage(canvas,0,0,32,32,0,0,320,320);
     ctx2.imageSmoothingEnabled = true;
     ctx2.drawImage(canvas,0,0,32,32,320,0,320,320);
    }
    

    But it is completely broken in Firefox and Opera:

    ffoper

    @tieTYT said:

    • Do I need to call this every time I render my game, or just each time the window resizes?

    Every frame.

    • My game has canvases nested inside canvases. Do I need to call this on each one, or just on the parent that contains the others?

    So, you’re rendering canvases inside a parent canvas using ctx.drawImage(canvas)?

    In this case, you just have to call that for the parent canvas.

    • Most importantly, is there an easier way to get nearest neighbor scaling in chrome? This algorithm is pretty invasive and will require me to refactor a lot of code.

    I don’t think so…

    Actually, the algorithm is quite simple, IMO.

    So, let’s say the canvas you’re currently drawing stuff to is called parentCanvas. You just need to do this:

    var update = function()
    {
     /* 
    
       Your rendering code here
    
     */
    
     for(x = 0; x < parentCanvas.width; x++)
     {
      for(y = 0; y < parentCanvas.height; y++)
      {
       var color = parentCanvas.getContext("2d").getImageData(x,y,1,1);
       for(width =  0; width < 10; width ++)
       {
        for(height = 0; height < 10; height ++)
        {
         scaledContext.putImageData(color,x * 10 + width,y * 10 + height);
        }
       }
      }
     } 
    
    }
    

    The only issue I’m currently having with that approach is that when you draw images without CORS approval in your canvas, it gets tainted and you can no longer use getImageData on it, i.e, that Phrogz code doesn’t work anymore if you’re loading images locally.

    This MDN page has more details about that.


  • Patron

    P.S:

    Welcome to da forums!


  • Patron

    P.P.S:

    Ooops, had forgot to use the moz prefix in Firefox.

    It seems to work just fine:

    moz



  • Thanks for all the help guys. I’m going to go with imageSmoothingEnabled. I tried it before and it didn’t seem to work, but that was because of a bug in my code. Turns out I was setting that to false in every single canvas except the parent canvas. So I thought it was having no effect. At that time I somehow convinced myself that this was related to Chrome bug 13040 @richtaur linked to. PEBKAC!

    Thanks for the welcoming me :)


Log in to reply