Hayko Koryun
Hayko Koryun

Reputation: 1244

Why HTML5 Canvas rect / fillRect with pattern renders from canvas origin?

I was playing around with some code recently on the canvas when I discovered this oddity.

Given the following code

var pattern = ctx.createPattern(image, 'repeat');
ctx.fillStyle = pattern;
ctx.fillRect(10, 10, 500, 500);

you would expect that it would render the pattern with this image pattern on the canvas starting at point x:10, y:10.

However what actually seems to happen is that it renders the rectangle starting at x:10, y:10 but with the pattern starting at x:0, y:0.

offset illustration

The fix is the translate the canvas

ctx.translate(10,10);

and then render the rectangle starting from x:0, y:0

ctx.fillRect(0, 0, 500, 500);

I have set up a demo at jsfiddle to illustrate the issue. Just change the x and y offsets to see what happens by default and then use the checkbox to enable translation to 'fix' the issue.

So my questions is this:

Why is the behaviour of rect and fillRect this way when it comes to filling with patterns considering most of the time it is counter intuitive?

Upvotes: 4

Views: 2410

Answers (2)

user1693593
user1693593

Reputation:

Patterns (CanvasPattern) are separate global objects (in the sense of canvas' context) not bound to the method using it as style.

As the pattern is not "aware" of it being used or not, and if, it's used for style only which again neither is bound to the method using it position wise, the coordinate system becomes the only anchor point so-to-speak as to where to draw and tile the pattern.

Doing a, for instance, rect(x, y, w, h) will only inform browser where you want to render the rectangle itself, not how to fill it which is what the style mode holds information about and is something that is performed in the next step when compositing/filling the shape with the current style (which can be any of solid color, pattern or gradient - the latter have similar behavior as pattern).

Another aspect of this is that paths can be accumulated. For example, if you did:

ctx.fillStyle = myPattern;
ctx.rect(x1, y1, w, h);
ctx.rect(x2, y2, w, h);
ctx.fill();

which of the two rects should be anchor for the fill operation? (fillRect is just a shorthand for rect+fill but using a temporary path not affecting the current global path).

rect(), arc() etc. only creates paths and when you invoke fill/stroke, those paths are rasterized to canvas with whatever fill/stroke style that is set.

The browser (or the graphic sub-system of the OS) may do something like this when fill/stroke was invoked:

  • Rasterize the shapes/path using transformation matrix for the points (scale, orientation and position) and create an internal mask/matte
  • Use that mask/matte to composite (fill) that mask with current style
  • ..other steps

Now that there is just a mask/matte you don't have any defined anchor points anymore and the coordinate system is the only thing left.

(looking away from possibility to use different algorithms, ie. polygon filling, path would still exist etc. - but performance and graphic system affects these decisions as well as the specification which goal is to achieve the same behavior cross-browser and cross-platform). I am half asleep writing this so I hope I didn't make it more foggy..

Upvotes: 3

Friedrich
Friedrich

Reputation: 2290

You are right, that you have to set an offset in order to let the pattern start at your given point. I think it is because most of the time patterns are a lot smaller and have to make a wallpaper effect, and as wallpapers, the middle area matters, not the corners.

Also you already have the posibility to let the pattern start at another point, so there is not really a need for more functionality in this function.

In this: question you also will find a function which will do everything for you.

Upvotes: 0

Related Questions