Fabric.js: Resize canvas and continue working - javascript

I am working with Fabric.js manipulation images uploaded by users.
Here is how the application works
The uploaded image can be moved and worked on on the entire canvas (grey area). Once finished, I only need the middle area.
One the user clicks save, currently I save the green area as an image, but I also need to resize the whole canvas to be only the green area.
I tried changing the width and height of the whole canvas and then redrawAll(), but the canvas goes bottom left and only a small portion of the working area is visible, the rest is empty gray.
Is it possible to take a snapshot of the green area and continuing working on the canvas ?
Thank you

You should do extensive use of viewportTransform.
let's make an example:
you have a canvas element of 1000x1000 and you want to work on an area of 500x500 pixels.
You set the green area using a green image or a green rect of 500x500 as background image, with top left 0,0. Then you set the viewportTransform of the fabric canvas to [1, 0, 0, 1, 250, 250] this will make your canvas translate to right bottom of that grey border.
When is time to save, you have 2 choices:
1) set a viewportTransform that lets you cover everything of the original canvas and export to dataurl
2) you create an offscreen canvas of 500x500px, you move the objects over there, without touching the new canvas zoom and pan ( viewport transform ) and export that canvas to dataUrl
The snippet has some number for examples, but you need to fully understand how to use and check also the toDataUrl options of fabricCanvas.
var butt = document.getElementById('butt');
butt.onclick = function() {
canvas.viewportTransform = [2, 0, 0, 2, 0, 0];
var img = document.getElementById('export');
img.src = canvas.toDataURL()
canvas.viewportTransform = [1, 0, 0, 1, 250, 250];
canvas.renderAll();
}
var canvas = new fabric.Canvas('c', { backgroundColor: 'grey' });
var rect = new fabric.Rect({ width: 500, height: 500, fill: 'green'})
canvas.backgroundImage = rect;
canvas.viewportTransform = [1, 0, 0, 1, 250, 250];
var exampleRect = new fabric.Rect({left: 5, top: 5, width: 100, height: 100, fill: 'red' });
canvas.add(exampleRect);
canvas.renderAll();
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.11/fabric.min.js"></script>
<img id="export" />
<button id="butt" >export</button>
<canvas id="c" width="1000" height="1000"></canvas>

Related

Can I scale up part of an image by secluding an area?

I'm using html5 canvas and JS.
I'm drawing an image on the canvas with CanvasRenderingContext2D.drawImage(). This image is 15x15 black square, with a 1 pixel wide white border: so 16x16 total.
Is there a way to read image data in JS, and only scale up the border width of that image. So, leave the inner black square 15x15, but make the white border width 3 pixels wide?
As far as I can tell, the CanvasRenderingContext2D.getImageData() can only select an area, but you cannot seclude an area, to use the rest.
Thanks :)
You can not change the size of a canvas without clearing its content. The only way to resize the canvas is to create a second canvas, copy the existing content to the new canvas while adding the new content.
The following function will pad a canvas with a boarder by creating a second canvas. Filling it with the padding color, adding the original canvas image to it. Then resize the original and copying the new content to it.
// padCanvas(canvas, padAmount, padColor)
// canvas : HTMLCanvasElement Canvas to pad
// Note all state setting on the input canvas will be reset
// padAmount : Number Amount to pad canvas in pixels
// padColor : String Color of padding as CSS style color string. eg "White"
// Returns the original canvas,
function padCanvas(canvas, padAmount, padColor) {
// create a canvas with the padded size
const can = Object.assign(
document.createElement("canvas"), {
width: canvas.width + padAmount * 2,
height: canvas.height + padAmount * 2
}
);
// add padding color and source (image)
const ctx = can.getContext("2d");
ctx.fillStyle = padColor;
ctx.fillRect(0, 0, can.width, can.height);
ctx.drawImage(canvas, padAmount, padAmount);
// Resize original image and add new content
Object.assign(canvas, {width: can.width, height: can.height});
canvas.getContext("2d").drawImage(can, 0, 0);
return canvas;
}

Adding elem in canvas removes background and renders itself on a white canvas

Adding any fabric js element like text or any shape in canvas removes background which i have set earlier to my canvas and object renders itself on a white canvas. I want to load object on my canvas having background.
I tried renderAll() and bind() but it doesn't help at all.
Here c is another canvas and window.canvas is another one, I'm setting c as background of window.canvas. Everything is fine here
var ctx = window.canvas.getContext("2d");
var background = new Image();
background.src = c.toDataURL("image/png", 1.0);
background.onload = function(){
ctx.drawImage(background,0,0,this.naturalWidth,this.naturalHeight,0,0,window.canvas.width,window.canvas.height);
}
window.canvas.setHeight(c.height);
window.canvas.setWidth(c.width);
window.canvas.renderAll();
Issue when i draw this shape or any other fabric element it loads on a white canvas instead on my old canvas which is window.canvas with some background
shapCirEl.onclick = function() {
var cir = new fabric.Circle({
radius: 60,
fill: 'red',
top: 100,
left: 100
});
cir.set({
borderColor: 'black',
cornerColor: 'black',
cornerSize: 6,
transparentCorners: false,
hasRotatingPoint: false
});
window.canvas.add(cir);
window.canvas.setActiveObject(cir);
};
When i draw this shape or any other fabric element it loads on a white canvas instead on my old canvas which is window.canvas with some background
Use setBackgroundImage to set background image.
var url = c.toDataURL("image/png", 1.0);
window.canvas.setBackgroundImage(url,function(){
canvas.renderAll();
})

How to bring one canvas context over the top of another?

I am using two images in a canvas, now i want to bring one image over another, ie. i want to bring the plane over the sky, how can i do that?
here is my code
var canvas = document.getElementById('canvas');
var skyContext = canvas.getContext('2d');
var planeContext = canvas.getContext('2d');
var sky = new Image();
sky.src = './images/m35.jpeg';
sky.onload = function () {
skyContext.drawImage(sky, 0, 0, 250, 250, 50, 50, 250, 250);
}
var plane = new Image();
plane.src = './images/space-ship.png';
plane.onload = function () {
planeContext.drawImage(plane, 0, 0, 70, 80, 50, 250, 70, 80);
}
In the above code, the sky is coming at the front of the plane making the plane invisible.
I also tried to use the same context like this but i am not able to bring the image at the top of another.
The most effective way to do this (especially as it looks like you're creating a game) is to use two separate canvas elements, positioned on top of each other using CSS.
For example:
var canvasMain = document.getElementById('canvasMain');
var canvasBackground = document.getElementById('canvasBackground');
var skyContext = canvasMain.getContext('2d');
var sky = new Image();
sky.src = 'https://i.stack.imgur.com/xjj19.jpg';
sky.onload = function () {
skyContext.drawImage(sky, 0, 0, 250, 250, 50, 50, 250, 250);
}
var planeContext = canvasBackground.getContext('2d');
var plane = new Image();
plane.src = 'https://i.stack.imgur.com/nHugQ.png';
plane.onload = function () {
planeContext.drawImage(plane, 0, 0, 70, 80, 50, 250, 70, 80);
}
.canvas {
position: absolute;
top: 0;
left: 0;
}
<canvas id="canvasMain" class="canvas" width="700" height="500"></canvas>
<canvas id="canvasBackground" class="canvas" width="700" height="500"></canvas>
The issue with your code is that the images are drawn once loaded and I would suspect that because the sky image is larger than the plane image it takes longer to load so the sky image is being drawn second.
Drawing things on canvas works like layers in photoshop with whatever is drawn last overwriting things drawn before it.
It is possible to use the same canvas for both the sky and the plane, you just need to wait until both images have loaded then draw them in the correct order to the sky is drawn first, then the plane on top.
You can use the same canvas context for both objects if the are on the same canvas, no need to create multiple contexts (2 just point to the same place anyway). Usually when I work with canvases I just create one context variable called ctx as its a lot quicker than typing context.etc all the time.
almcd answer is one way to do it, though to me the background and main are reversed; I would put the sky / galaxy on the background and draw the plane on the main canvas, but there is a better way...
If the sky never changes, 2 canvases is not even needed, just one canvas with a background image behind it; the sky. When it comes time to animate the plane, this method of having the background a static image means there is less to draw each frame of the game loop, so you will get better FPS.
In terms of a solution for you, I think 1 canvas with the sky as a background image positioned by CSS, and only the plane being drawn on the canvas is the best, here is the code for that...
<!doctype HTML>
<html>
<head>
<style>
#canvasContainer {
background-image: url('https://i.stack.imgur.com/xjj19.jpg');
background-repeat: no-repeat;
}
</style>
</head>
<body>
<div id="canvasContainer">
<canvas id="canvas" width="1200" height="600"></canvas>
</div>
<script>
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// Load the plane and draw it.
var plane = new Image();
plane.src = 'https://i.stack.imgur.com/nHugQ.png';
plane.onload = function () {
ctx.drawImage(plane, 100, 100);
}
</script>
</body>
</html>
You can move the plane by changing the 100, 100 parameters sent to draw image, as these are the left and top used to position the plane image.
Kind regards,
DouG.
Creator of Winwheel.js a feature packed JavaScript library for making spinning prize wheels on HTML canvas. See http://dougtesting.net

FabricJS prevent canvas.clipTo from clipping canvas.backgroundImage

I want to set a global clipTo in my Fabric-powered Canvas that will affect all user-added layers. I want a background image and an overlay image, which are unaffected by this clip mask.
Example:
Here's what's happening in this photo:
A canvas overlay image makes the t-shirt look naturally wrinkled. This overlay image is mostly transparent
A background image in the exact shape of the t-shirt was added, which is supposed to make the t-shirt look blue
A canvas.clipTo function was added, which clips the canvas to a rectangular shape
A user-added image (the famous Fabric pug) was added
I want the user-added image (the pug) to be limited to the rectangular area.
I do not want the background image (the blue t-shirt shape) affected by the clip area.
Is there a simple way to accomplish this? I really don't want to have to add a clipTo on every single user layer rather than one tidy global clipTo.
You can play with a JS fiddle showing the problem here.
I came here with the same need and ultimately found a solution for what I'm working on. Maybe it helps:
For SVG paths, within the clipTo function you can modify the ctx directly prior to calling render(ctx) and these changes apply outside the clipped path o. Like so:
var clipPath = new fabric.Path("M 10 10 L 100 10 L 100 100 L 10 100", {
fill: 'rgba(0,0,0,0)',
});
var backgroundColor = "rgba(0,0,0, 0.2)";
var opts = {
controlsAboveOverlay: true,
backgroundColor: 'rgb(255,255,255)',
clipTo: function (ctx) {
if (typeof backgroundColor !== 'undefined') {
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, 300, 150);
}
clipPath.render(ctx);
}
}
var canvas = new fabric.Canvas('c', opts);
canvas.add(new fabric.Rect({
width: 50,
height: 50,
left: 30,
top: 30,
fill: 'rgb(255,0,0)'
}));
You can of course add an image instead of a color, or whatever else you want done. The trick I've found is to put it in the clipTo function on the ctx directly.
here's a fiddle
One (sorta hacky) solution: set a CSS background image on your canvas element, as shown in https://jsfiddle.net/qpnvo3cL/
<canvas id="c" width="500" height="500"></canvas>
<style>
background: url('http://fabricjs.com/assets/jail_cell_bars.png') no-repeat;
</style>
<script>
var canvas = window._canvas = new fabric.Canvas('c');
canvas.clipTo = function(ctx) {
ctx.rect(100,100,100,100);
}
</script>
Have you tried clipping a fabric Group? You could make the whole shirt one canvas. The center graphics would be one Group which you clip to where you want it. The white t-shirt and the blue overlay would of course not be part of the clipped group.
Here's an example of clipping a group:
var rect = new fabric.Rect({width:100, height: 100, fill: 'red' });
var circle = new fabric.Circle({ radius: 100, fill: 'green' });
var group1 = new fabric.Group([ circle, rect ], { left: 100, top: 100 });
canvas.add(group1);
group1.clipTo = function(ctx) {
ctx.rect(50,50,200,200);
};
See this jsfiddle I made: https://jsfiddle.net/uvepfag5/4/
I find clip rather slow so I tend to use globalCompositeOperation to do masking.
If you really need to use clip then use it in conjunction with save and restore.
// ctx is canvas context 2d
// pug is the image to be clipped
// draw your background
ctx.save(); // save state
ctx.rect(100,100,100,100); // set the clip area
ctx.clip(); // apply the clip
ctx.drawImage(pug,x,y); // draw the clipped image
ctx.restore(); // remove the clipping
// draw the other layers.
or you can
// draw background
ctx.globalCompositeOperation = "xor"; // set up the mask
ctx.fillRect(100,100,100,100); // draw the mask, could be an image.
// Alpha will effect the amount of masking,
// not available with clip
ctx.globalCompositeOperation = "destination-over";
ctx.drawImage(pug,x,y); // draw the image that is masked
ctx.globalCompositeOperation = "source-over";
// draw the stuff that needs to be over everything.
The advantage of composite operations is you have control over the clipping at a per pixel level, including the amount of clipping via the pixel alpha value

Kineticjs clip shape by mask image

I have mask image like this
How can I hide drawn elements by this mask like in photoshop (dark areas are invisible, transparent areas are shown).
It is possible to do this in raw canvas via setting globalCompositeOperation = 'destination-over', but I want it in Kineticjs
You can do something like this:
var shape = new Kinetic.Shape({
drawFunc: function(context) {
context.beginPath();
context.rect(0, 0, image.width, image.height)
context.closePath();
context.fillStrokeShape(this);
context.setAttr('globalCompositeOperation', 'destination-out');
context.drawImage(image, 0, 0);
context.setAttr('globalCompositeOperation', 'source-over');
},
fill: '#00D2FF',
});
Demo: http://jsbin.com/nebuvi/1/edit?html,js,output
As of my knowledge you can try by using opacity: 0. for your image value. or create a rectangle overlap the rectangle over your image.

Categories