22

I'm using canvas to display some sprites, and I need to flip one horizontally (so it faces left or right). I can't see any method to do that with drawImage, however.

Here's my relevant code:

this.idleSprite = new Image();
this.idleSprite.src = "/game/images/idleSprite.png";
this.idleSprite.frameWidth = 28;
this.idleSprite.frameHeight = 40;
this.idleSprite.frames = 12;
this.idleSprite.frameCount = 0;

this.draw = function() {
        if(this.state == "idle") {
            c.drawImage(this.idleSprite, this.idleSprite.frameWidth * this.idleSprite.frameCount, 0, this.idleSprite.frameWidth, this.idleSprite.frameHeight, this.xpos, this.ypos, this.idleSprite.frameWidth, this.idleSprite.frameHeight);
            if(this.idleSprite.frameCount < this.idleSprite.frames - 1) { this.idleSprite.frameCount++; } else { this.idleSprite.frameCount = 0; }
        } else if(this.state == "running") {
            c.drawImage(this.runningSprite, this.runningSprite.frameWidth * this.runningSprite.frameCount, 0, this.runningSprite.frameWidth, this.runningSprite.frameHeight, this.xpos, this.ypos, this.runningSprite.frameWidth, this.runningSprite.frameHeight);
            if(this.runningSprite.frameCount < this.runningSprite.frames - 1) { this.runningSprite.frameCount++; } else { this.runningSprite.frameCount = 0; }
        }
    }

As you can see, I'm using the drawImage method to draw my sprites to the canvas. The only way to flip a sprite that I can see is to flip/rotate the entire canvas, which isn't what I want to do.

Is there a way to do that? Or will I need to make a new sprite facing the other way and use that?

Damjan Pavlica
  • 31,277
  • 10
  • 71
  • 76
James Dawson
  • 5,309
  • 20
  • 72
  • 126

3 Answers3

40

While Gaurav has shown the technical way to flip a sprite in canvas...

Do not do this for your game.

Instead make a second image (or make your current image larger) that is a flipped version of the spite-sheet. The performance difference between "flipping the context and drawing an image" vs "just drawing an image" can be massive. In Opera and Safari flipping the canvas results in drawing that is ten times slower, and in Chrome it is twice as slow. See this jsPerf for an example.

Pre-computing your flipped sprites will always be faster, and in games canvas performance really matters.

Community
  • 1
  • 1
Simon Sarris
  • 62,212
  • 13
  • 141
  • 171
  • Thanks. What I ended up doing was adding another sprite to my existing one (below it) with my character running the other way, and added some code to use that set of sprites depending on the direction. Like this: http://i.imgur.com/uIPvF.png – James Dawson Oct 27 '11 at 18:33
  • 1
    Looks good! Though I would rearrange the new row: line up the flipped version of each vertically so the only thing you have to change when flipping is the y coordinate. – Simon Sarris Oct 27 '11 at 18:51
  • Gaurav's answer is really only performance costly due to the ctx.save/restore. In fact if you simply call ctx.scale(...) every time, hilariously the flipped version draws faster: http://jsperf.com/ctx-flip-performance/3. (it's even faster if you call ctx.scale(-1) for the flipped and nothing for a the non-flipped case: http://jsperf.com/ctx-flip-performance/4). – Jeff Gates Jul 10 '12 at 06:33
  • Jeff you raise a good point that `save` and `restore` aren't necessary, but your result was a fluke, see the results now. You may have begun the test before the browser finished loading the page or Java (used in the timer). I'd argue that the test isn't accurate at `/4` because the work required to flip also must include the work to return the transformation back to its normal state. This means at a minimum a second call to `ctx.scale(-1,1)`. Here's a "fair" test, and flipping is slower in every browser and mobile device tested, sometimes very much so: http://jsperf.com/ctx-flip-performance/5 – Simon Sarris Jul 10 '12 at 14:53
  • I had thought .scale() set the transforms scale as opposed to multiplying it. The multiply is what is implied in the w3.org docs (http://dev.w3.org/html5/2dcontext/#dom-context-2d-scale), which would indeed invalidate test /4. (It uses the term 'add' which is a bit ambiguous). However, that's not what it seems to be doing, at least on chrome: http://jsperf.com/ctx-flip-performance/8. – Jeff Gates Jul 10 '12 at 18:39
  • Woah! You just showed something really important. Jsperf's setup function is broken and not clearing the canvas! See http://jsperf.com/ctx-flip-performance/10 for what happens if I explicitly clear inside of the test, and and see http://jsfiddle.net/sX2jT/ for what really happens. `scale` always multiplies, its a bug with jsperf we are seeing :( – Simon Sarris Jul 10 '12 at 19:58
11

You can transform the canvas drawing context without flipping the entire canvas.

c.save();
c.scale(-1, 1);

will mirror the context. Draw your image, then

c.restore();

and you can draw normally again. For more information, see https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Transformations

erbridge
  • 1,376
  • 12
  • 27
Gaurav
  • 12,662
  • 2
  • 36
  • 34
  • 3
    Thank you! But due to Simon's answer, I'll be using his technique. I'll mark yours as the accepted answer though as it is answering my question :) – James Dawson Oct 27 '11 at 18:33
-3

Use this to flip your sprite sheet http://flipapicture.com/

Sam Backus
  • 1,693
  • 14
  • 19