Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Applying a Custom Mask on an Image using Raphael

0.00/5 (No votes)
25 Jun 2013CPOL3 min read 14.8K   122  
How to apply a clipping mask to an image using the raphael vector library

Introduction

Quite often, when developing a new web page, we get the requirement to display images that are not rectangle. The easiest way out is to use a format that has transparency (png, gif). However, when there are many such large images within the page, both formats have disadvantages: We either lose on quality, if we use gif, or we lose in responsiveness, when using png, as png is lossless and a large-sized image results in a large file. Here I suggest an alternative way of loading such images, using jpg (which can result in a significantly small file size for the image without compromising too much quality) and a bit of JavaScript, using raphaël and a bit of jquery (although the latter is purely for reducing the lines of code).

Using the Code

The HTML markup and inline JavaScript is only an example of a relatively simple almost circular (but not quite circular) path. To get all those coordinates, I used a vector drawing program (e.g. Inkscape) to first draw the shape I wanted. Then I opened the resulting svg file with a text editor, as svg files are really XML. I searched for the path information. The relevant line is:

XML
<path d="M 85.106383,487.46857 C 
		81.847017,330.82524 237.24118,217.14379 
		377.74267,222.77754 527.94414,228.80023 
		618.60707,399.39619 604.25532,530.02176 589.90357,
		660.64733 463.97372,772.57588 311.70213,768.31963 
		215.82865,765.63981 88.365749,644.1119 
		85.106383,487.46857 z" id="path2996"/>

We only need the information in the d attribute. These are instructions on how to reproduce the shape I created in the svg. To see what each instruction means, please refer to the SVG path data reference, or to the more concise raphaël js Path reference.

The problem with the above coordinates is that they are actually relative to the canvas created in the vector program. We don't really want that, we want the path translated to the top-left hand corner of the canvas. This can be a non-trivial task, however. What I did (I am sure there are other methods as well) was the following:

  • Convert all spaces into line breaks. This gives the following:
    M
    85.106383,487.46857
    C
    81.847017,330.82524
    237.24118,217.14379
    377.74267,222.77754
    527.94414,228.80023
    618.60707,399.39619
    604.25532,530.02176
    589.90357,660.64733
    463.97372,772.57588
    311.70213,768.31963
    215.82865,765.63981
    88.365749,644.1119
    85.106383,487.46857
    z
  • Convert all commas into tabs to get the following:
    M
    85.106383	487.46857
    C
    81.847017	330.82524
    237.24118	217.14379
    377.74267	222.77754
    527.94414	228.80023
    618.60707	399.39619
    604.25532	530.02176
    589.90357	660.64733
    463.97372	772.57588
    311.70213	768.31963
    215.82865	765.63981
    88.365749	644.1119
    85.106383	487.46857
    z
  • Copy-paste the resulting data into a spreadsheet. The data should be presented as a 2-column array.
  • Find the minimum of each column and subtract that from all values in that column.
  • Get the resulting string. It should be as such:
    M
    3.259366	270.32478
    C
    0	113.68145
    155.394163	0
    295.895653	5.633750
    446.097123	11.65644
    536.760053	182.2524
    522.408303	312.87797
    508.056553	443.50354
    382.126703	555.43209
    229.855113	551.17584
    133.981633	548.49602
    6.518732	426.96811
    3.259366	270.32478
    z
  • and make a JavaScript array out of it.
    JavaScript
    var MaskPath= [
    		["M"],
    		[3.259366, 270.32478],
    		["C"],
    		[0, 113.68145],
    		[155.394163, 0],
    		[295.895653, 5.63374999999999],
    		[446.097123, 11.65644],
    		[536.760053, 182.2524],
    		[522.408303, 312.87797],
    		[508.056553, 443.50354],
    		[382.126703, 555.43209],
    		[229.855113, 551.17584],
    		[133.981633, 548.49602],
    		[6.518732, 426.96811],
    		[3.259366, 270.32478],
    		["z"]
    	]; 

The rest of the JavaScript code is pretty straight forward.

First, we find the scale - all the above coordinates may need to be scaled down. Our canvas originally is 525x555, and the path just fits in that rectangle. However, the image we want to clip is 363x382, so we need a smaller canvas. We find the ratio of the two rectangles (image vs canvas).

JavaScript
var imageWidth = Number($(div).data('width')); 
var imageHeight = Number($(div).data('height'));
var WidthRatio = imageWidth / initialCanvasWidth;
var HeightRatio = imageHeight / initialCanvasHeight;
var ratio = Math.min(WidthRatio, HeightRatio); 

and scale down the canvas itself:

JavaScript
var paper = Raphael(id, initialCanvasWidth * ratio, initialCanvasHeight * ratio); 

and each coordinate in the array:

JavaScript
[instruction[0] * ratio, instruction[1] * ratio] 

Then it's only a matter of applying the image as "fill" for the scaled path we have created:

JavaScript
fill: "url(" + $(div).data('url') + ")" 

All information regarding the image is contained as data in the div that serves as parent to the svg canvas created by raphaël:

HTML
<div id="raph1" class="raphObj" data-url="/image1.jpg" data-width="363" data-height="382"></div> 

Points of Interest

The JavaScript code assumes two non-trivial things:

  • The image has roughly the same width/height ratio with the original canvas, i.e., the canvas is almost square and so is the image. Trying to use an image that is elongated (e.g. a portrait or a landscape photo), you'll notice that it will be tiled as a background in the path. One can probably mitigate this by creating various path-and-canvas configurations (i.e. one for square or almost square images, one for portrait and one for landscape images).
  • The image is smaller than the original canvas. Otherwise the call to...
    JavaScript
    var ratio = Math.min(WidthRatio, HeightRatio);

    ... doesn't work properly. One could probably check if the ratios are above or below 1 and choose between Math.min and Math.max accordingly.

Also, please make sure you are viewing the HTML file via a web server, otherwise the cdn-based JavaScript libraries (jquery and raphaël) will not load.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)