2

I'd like to give a sprite an outline when the character gets healed/damaged/whatever but I can't think of a way to code this using the 2d canvas. If it were possible, I'd think it would be a global composite operation, but I can't think of a way to achieve it with one of them.

I did find this stackoverflow answer that recommends creating a fatter, solid color version of the original and put the original on top of it. That would give it an outline, but it seems like a lot of extra work especially considering I'm using placeholder art. Is there an easier way?

This question is different from the one linked because this is specifically about the HTML5 2D canvas. It may have a solution not available to the other question.

For what it's worth, I don't mind if the outline creates a wider border or keeps the sprite the same size, I just want the outline look.

Community
  • 1
  • 1
Daniel Kaplan
  • 62,768
  • 50
  • 234
  • 356
  • 1
    Instead of "live" processing, how about using expand selection in Photoshop or alternatively using grow selection in Gimp to create outlined versions of your sprites? – markE Aug 24 '14 at 00:40
  • @markE not a bad idea. I didn't know it could be that easy. I thought I would have to use a pencil tool to draw an outline. – Daniel Kaplan Aug 24 '14 at 00:42

3 Answers3

5
  • Just draw your original image in 8 position around the original image
  • Change composite mode to source-in and fill with the outline color
  • Change composite mode back to source-over and draw in the original image at correct location

This will create a clean sharp outline with equal border thickness on every side. It is not so suited for thick outlines however. Image drawing is fast, especially when image is not scaled so performance is not an issues unless you need to draw a bunch (which in that case you would cache the drawings or use a sprite-sheet anyways).

Example:

var ctx = canvas.getContext('2d'),
    img = new Image;

img.onload = draw;
img.src = "https://i.stack.imgur.com/UFBxY.png";

function draw() {

  var dArr = [-1,-1, 0,-1, 1,-1, -1,0, 1,0, -1,1, 0,1, 1,1], // offset array
      s = 2,  // scale
      i = 0,  // iterator
      x = 5,  // final position
      y = 5;
  
  // draw images at offsets from the array scaled by s
  for(; i < dArr.length; i += 2)
    ctx.drawImage(img, x + dArr[i]*s, y + dArr[i+1]*s);
  
  // fill with color
  ctx.globalCompositeOperation = "source-in";
  ctx.fillStyle = "red";
  ctx.fillRect(0,0,canvas.width, canvas.height);
  
  // draw original image in normal mode
  ctx.globalCompositeOperation = "source-over";
  ctx.drawImage(img, x, y);
}
<canvas id=canvas width=500 height=500></canvas>
  • "It is not so suited for thick outlines however." Why not? – Daniel Kaplan Nov 25 '14 at 20:24
  • 1
    @DanielKaplan you will start to see the outline "crack up" - getting dominant directions (try setting offset values to f.ex. 10 to see). You can solve this by either using more fractional offsets in the array to interpolate, or replace it using trigonometry which draws the outline image in a circle using a coarse step value for angle based on width. –  Nov 25 '14 at 22:02
  • @K3N its working. Is it possible to get that red line path like its x y position ? – Bhoomika Brahmbhatt Sep 23 '15 at 03:34
3

Maybe it would be worth trying this :

• build a canvas 1.1 time bigger than the original sprite
• fill it with the outline color
• draw the sprite scaled by 1.1 on the canvas using destination-in globalCompositeOperation.

Then you have a bigger 'shadow' of your sprite in the outline color.

When you want to draw the outline :

• draw the 'shadow' (centered)
• draw your sprite within the shadow.

Depending on the convexity of your sprite, this will work more or less nicely, but i think it's worth trying since it avoids you doubling the number of input graphic files.

I just did a short try as proof-of-concept and it quite works :
http://jsbin.com/dogoroxelupo/1/edit?js,output

Before : enter image description here

After : enter image description here

html

<html>
<body>
  <image src='http://www.gifwave.com/media/463554/cartoons-comics-video-games-sprites-scott-pilgrim-paul-robertson_200s.gif' id='spr'></image>
  <canvas id='cv' width = 500 height= 500 ></canvas>
</body>
</html>

code

window.onload=function() {
  var spr = document.getElementById('spr');
  var margin = 4;
  var gh = createGhost(spr, '#F80', margin);
  var cv = document.getElementById('cv');
  var ctx = cv.getContext('2d');
  var outlined = true;
  setInterval(function() {
     ctx.clearRect(0,0,cv.width, cv.height);
     if (outlined)       
       ctx.drawImage(gh, 0, 0)
     ctx.drawImage(spr, 0, 0)
     outlined = !outlined;
  }, 400);
}

function createGhost (img, color, margin) {
  var cv= document.createElement('canvas');
  cv.width = img.width+2*margin;
  cv.height = img.height + 2*margin;
  var ctx = cv.getContext('2d');
  ctx.fillStyle = color;
  ctx.fillRect(0,0, cv.width, cv.height);
  ctx.save();
  ctx.globalCompositeOperation = 'destination-in';
  var scale = cv.width/spr.width;
  ctx.scale(cv.width/spr.width, cv.height/spr.height); 
  ctx.drawImage(img, -margin, -margin);
  ctx.restore();
  return cv;  
}
GameAlchemist
  • 18,995
  • 7
  • 36
  • 59
  • Pretty clever solution. I would predict problems scaling sprites by such small numbers, but your code snippet seems to show otherwise. I'll try this at home and if it works mark this as the answer. Thanks – Daniel Kaplan Aug 25 '14 at 17:05
-1

You could use strokeRect method to outline the sprite after drawing it. It should be asy if you know your sprite's dimensions...

NullPointer
  • 159
  • 3