tearswep
tearswep

Reputation: 145

Create a repetitive image pattern using fabricjs

As the title says, I am trying to create a tile pattern by repeating the same image throughout my canvas element.

This is what I've tried so far: <canvas id='c' width='345' height='345'></canvas>

let canvas = new fabric.Canvas('c',{backgroundColor: "#fff"});
const logo = 'https://www.logomaker.com/wpstatic/uploads/2018/11/bakerylogo2.png';
canvas.add(
        new fabric.Line([7,10,17,10],{
            fill: "grey",
            stroke: "grey",
            strokeWidth: 3           
        }),
        new fabric.Line([10,10,10,150],{
            fill: "grey",
            stroke: "grey",
            strokeWidth: 3
        }),
        new fabric.IText("500mm",{
            left: 20,
            top: 152,
            fill: "grey",
            stroke: "grey",
            fontSize: 10,
            angle: 90
        }),
        new fabric.Line([10, 190, 10, 315], {
            fill: "grey",
            stroke: "grey",
            strokeWidth: 3
        }),
        new fabric.Line([7,315,17,315],{
            fill: "grey",
            stroke: "grey",
            strokeWidth: 3           
        }),
        new fabric.Line([30,333,30,320],{
            fill: "grey",
            stroke: "grey",
            strokeWidth: 3           
        }),
        new fabric.Line([30,325,150,325],{
            fill: "grey",
            stroke: "grey",
            strokeWidth: 3           
        }),
        new fabric.IText("380mm",{
            left: 152,
            top: 320,
            fill: "grey",
            stroke: "grey",
            fontSize: 10,
        }),
        new fabric.Line([187,325,310,325],{
            fill: "grey",
            stroke: "grey",
            strokeWidth: 3           
        }),
        new fabric.Line([310,333,310,320],{
            fill: "grey",
            stroke: "grey",
            strokeWidth: 3           
        }),
    )
    fabric.Image.fromURL(logo, function(img) {
        var oImg = img.set({
            left: 40,
            top: 40,
            angle: 310
        }).scale(0.1);
        canvas.add(oImg);
    })
    canvas.renderTop();

, but now I am stuck.

Here's a jsfiddle if it helps to visualize the problem faster: https://jsfiddle.net/1fevg8dc/3/

Any help in the right direction would help me greatly, as I really struggle with fabricjs's documentation.

My end goal would be to apply a greyscale filter on the image(declared as logo) and rotate the image at 45 degrees and create the pattern inside the lines that I've

Upvotes: 0

Views: 1232

Answers (1)

obscure
obscure

Reputation: 12891

A tiled, repeated pattern as a fabric.js background is not that hard to do actually. However, the problem in your case is that you want it to be at an angle and a grayscale filter applied.

Anyway, first we need to create a canvas.js Shape object - which will serve as our background and hence needs to cover the whole viewport.

let shape = new fabric.Rect({
    width: canvas.width,
    height: canvas.height,
    left: 0,
    top: 0,
    selectable: false
});

We don't add it to the stage yet though. As we want to fill the Shape with an image, we need to load it first. For this task we utilize the .fromURL() method of the Image object.

fabric.Image.fromURL("https://corsanywhere.herokuapp.com/https://www.logomaker.com/wpstatic/uploads/2018/11/bakerylogo2.png", function(img) {}, {crossOrigin: 'anonymous'})

Inside the empty callback function - function(img) {} - the following must happen:

  • Convert the image to grayscale
  • Use this image as a fill for the shape
  • Rotate the fill e.g. 45°

The first step is done using fabric.js built-in filters

img.filters.push(new fabric.Image.filters.Grayscale());
img.applyFilters();

Step number two involves creating a Pattern object which is then applied to the Shape object.

The last step is the tricky part. A fill doesn't have a rotation property. Instead it's transformed by a transformation matrix using it's patternTransform property. This patternTransform is an array of 6 values which control the position, scale and skew. For a 45 degree rotation it has to be like this:

let angle = -45 * Math.PI / 180;
let transformMatrix=[Math.cos(angle), Math.sin(angle), -Math.sin(angle), Math.cos(angle), 0, 0];

Let's elaborate a bit on the transformation matrix. As I said, it contains six values. If we give those a meaningful name it looks a bit like this:

matrix= [scaleX, skewY, skewX, scaleY, translateX, translateY];

We can see that there is no value which controls the rotation. That's because in a matrix, a rotation is a combination of skew and scale. If we look back at our own transformation:

let transformMatrix = [Math.cos(angle), Math.sin(angle), -Math.sin(angle), Math.cos(angle), 0, 0];

it's indeed the scaleX, skewY, skewX and scaleY properties being populated.

If we were to alter the scale of a matrix, everything we would need to change it's scaleX and scaleY properties. In this case however it's a bit different as we already applied a rotation. Fortunately it ain't that hard. The scale itself is a decimal number, where 1 means no change, 2 is the double scale and 0.5 is half the scale for example. So if we want to make the pattern half it's original size, we need to multiply scaleX, skewY, skewX and scaleY by 0.5.

let angle = -45 * Math.PI / 180;
let scale = 0.5;
let transformMatrix = [Math.cos(angle) * scale, Math.sin(angle) * scale, -Math.sin(angle) * scale, Math.cos(angle) * scale, 0, 0];

Now if we put everything together we will come up with this:

let canvas = this.__canvas = new fabric.Canvas('c');

let shape = new fabric.Rect({
  width: canvas.width,
  height: canvas.height,
  left: 0,
  top: 0,
  selectable: false
});

fabric.Image.fromURL("https://corsanywhere.herokuapp.com/https://www.logomaker.com/wpstatic/uploads/2018/11/bakerylogo2.png", function(img) {
  img.filters.push(new fabric.Image.filters.Grayscale());
  img.applyFilters();

  let angle = -45 * Math.PI / 180;
  let scale = 0.5;
  shape.set('fill', new fabric.Pattern({
    source: img._element,
    repeat: 'repeat',
    patternTransform: [Math.cos(angle) * scale, Math.sin(angle) * scale, -Math.sin(angle) * scale, Math.cos(angle) * scale, 0, 0]
  }));

  canvas.add(shape);
  canvas.renderAll();

}, {
  crossOrigin: 'anonymous'
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/500/fabric.min.js" integrity="sha512-ft2xOUYF7h2UeYQUbol4GJHrkGPVrgimINEYwLI23hyxN1JIy6EEj2vEc8RWWIIExvUKZvYq3oJTDrbif2oSVw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<canvas id="c" width="800" height="600" style="border:1px solid #ccc"></canvas>

Upvotes: 1

Related Questions