I currently have a canvas on which I draw 4 "shapes" which are displayed as an image.
The code for making the shapes:
function init() {
var s = new CanvasState(document.getElementById('canvas1'));
s.addShape(new Shape(40,40,180,180)); // The default is gray
s.addShape(new Shape(60,140,40,60, 'lightskyblue'));
// Some partially transparent
s.addShape(new Shape(80,150,60,30, 'rgba(127, 255, 212, .5)'));
s.addShape(new Shape(125,80,30,80, 'rgba(245, 222, 179, .7)'));
}
And drawing the shapes:
Shape.prototype.draw = function(ctx) {
var locx = this.x;
var locy = this.y;
var imgNew = new Image();
imgNew.onload = function(){
ctx.drawImage(imgNew, locx, locy);
}
imgNew.src = "upload/Evenredig.jpg";
}
As you can see I am currently setting the width and height of the shape manually but I would like to know if it is possible to get the width and height from the image and set that as the width and height of the shape.
The full code for the canvas can be found here (It may be helpfull??)
http://pastebin.com/Z3eqvVnw
ps. feel free to edit the title if you know a better one because I have no idea how to name this question.
Related
I implemented image cropping using FabricJS and clipPath property.
The question is how do I make the image fit the canvas after cropping? I want the cropped image to fill the canvas area but can't figure out whether it's possible to do using fabric js.
So I want the selected part of the image to fit the canvas size after the user clicks the Crop button:
About the code: I draw a rectangle on the canvas and the user can resize and move it. After that, the user can click the Crop button and get a cropped image. But the cropped part stays the same size it was on the original image whereas I want it to scale and fit the canvas.
Note: I tried to use methods like scaleToWidth. Additionally, I used absolutePositioned set to true for the selection rectangle. I tried to scale to image with this property set to false, but it didn't help.
Please do not suggest using cropX and cropY properties for cropping instead of clipPath as they don't work properly for rotated images.
HTML:
<button>Crop</button>
<canvas id="canvas" width="500" height="500"></canvas>
JS:
// crop button
var button = $("button");
// handle click
button.on("click", function(){
let rect = new fabric.Rect({
left: selectionRect.left,
top: selectionRect.top,
width: selectionRect.getScaledWidth(),
height: selectionRect.getScaledHeight(),
absolutePositioned: true
});
currentImage.clipPath = rect;
canvas.remove(selectionRect);
canvas.renderAll();
});
var canvas = new fabric.Canvas('canvas');
canvas.preserveObjectStacking = true;
var selectionRect;
var currentImage;
fabric.Image.fromURL('https://www.sheffield.ac.uk/polopoly_fs/1.512509!/image/DiamondBasement.jpg', img => {
img.scaleToHeight(500);
img.selectable = true;
canvas.add(img);
canvas.centerObject(img);
currentImage = img;
canvas.backgroundColor = "#333";
addSelectionRect();
canvas.setActiveObject(selectionRect);
canvas.renderAll();
});
function addSelectionRect() {
selectionRect = new fabric.Rect({
fill: 'rgba(0,0,0,0.3)',
originX: 'left',
originY: 'top',
stroke: 'black',
opacity: 1,
width: currentImage.width,
height: currentImage.height,
hasRotatingPoint: false,
transparentCorners: false,
cornerColor: 'white',
cornerStrokeColor: 'black',
borderColor: 'black',
});
selectionRect.scaleToWidth(300);
canvas.centerObject(selectionRect);
selectionRect.visible = true;
canvas.add(selectionRect);
}
Here is my jsfiddle: https://jsfiddle.net/04nvdeb1/23/
I also faced this same issue for a few days. But i could not found any solution even i also found your SO question and github issue. I think clipPath is the best choice for clipping the image using a nice adjustable musk.
Demo Link
Github Repo Link
In the button callback function, you have to implement like this way
step 1: 1st you have to create an Image instance to hold the cropped image
let cropped = new Image();
Step 2: You have to use Canvas.toDataURL() to exports the canvas element to a dataurl image. Here wan want to export only the selection rectangle or mask rectangle, so we pass the mast rect left, top, width, and height
cropped.src = canvas.toDataURL({
left: rect.left,
top: rect.top,
width: rect.width,
height: rect.height,
});
Step 3: I the last step after the image loaded we clear the canvas. create new object with our cropped image, and set some option and rerender the canvas
cropped.onload = function () {
canvas.clear();
image = new fabric.Image(cropped);
image.left = rect.left;
image.top = rect.top;
image.setCoords();
canvas.add(image);
canvas.renderAll();
};
i actually marged Crop Functionality using FabricJs #soution2 with your problem.
Actually you don't have to use clipPath method. What you want is actually move the area you need to the corner, and change the size of canvas.
so the sodo code is as below:
fabricCanvas.setDimensions({
width: cropRectObject.getScaledWidth(),
height: cropRectObject.getScaledHeight()
})
imageObject.set({
left: -cropRectObject.left,
top: -cropRectObject.top
})
fabricCanvas.remove(cropRectObject);
tweaked your fiddle a bit to show this idea:
https://jsfiddle.net/tgy320w4/4/
hope this could help.
const { width, height, left = 0, top = 0 } = rect;
if (width == null || height == null) return;
const zoom = canvas.getZoom();
// toExact is a prototype for precision
const nw = (width * zoom).toExact(1);
const nh = (height * zoom).toExact(1);
const nl = (left * zoom).toExact(1);
const nt = (top * zoom).toExact(1);
// Apply
canvas.clipPath = o;
// Size
canvas.setWidth(nw);
canvas.setHeight(nh);
canvas.absolutePan(new fabric.Point(nl, nt));
canvas.remove(rect);
This question already has answers here:
Canvas is stretched when using CSS but normal with "width" / "height" properties
(10 answers)
Closed 4 years ago.
I have a fairly simple test, a 400 x 400 white image with text on it saying "1" over and over again.
I draw it on a fairly simple 1000 x 1000 canvas, trying to resize it to 100 x 100.
var image = new Image();
document.body.appendChild(image);
image.addEventListener("load",function (event) {
var image1 = event.target;
var tempCanvas = window.document.createElement("canvas");
tempCanvas.style.width = "1000px";
tempCanvas.style.height = "1000px";
tempCanvas.style.background = "rgba(0, 0, 0, 0.1)";
document.body.appendChild(tempCanvas);
tempCanvas.getContext("2d").drawImage(image1, 0, 0, 100, 100);
});
image.src = "1.png";
But despite all that being squares, I end up with an odd-looking, deformed, weirdly scaled result that's rectangular, low quality, and without having any of its dimensions being 100px.
On the left, you can see the original image, on the right, that's the top left corner of my canvas.
If you want the original image, here it is: https://i.stack.imgur.com/yDkLx.png
What am I missing?
Try setting the width and height values on the canvas element prior to the drawImage(), rather than relying on the styles as you are.
Setting the width and height attributes corresponds to setting the dimensions of that canvas element.
Once you've defined the dimensions of a canvas element, the rendering behavior of the canvas becomes much more predicatable:
var image = new Image();
document.body.appendChild(image);
image.addEventListener("load",function (event) {
var image1 = event.target;
var tempCanvas = window.document.createElement("canvas");
//tempCanvas.style.width = "400px";
//tempCanvas.style.height = "400px";
tempCanvas.style.background = "rgba(0, 0, 0, 0.1)";
tempCanvas.width = 100;
tempCanvas.height = 100;
document.body.appendChild(tempCanvas);
tempCanvas.getContext("2d").drawImage(image1, 0, 0, 100, 100);
});
image.src = "https://puu.sh/C4HE2/d96b531d08.png";
canvas {
border:1px solid blue;
}
img {
border:1px solid red;
}
The code snippet above shows the original source image with red border, and the down-scaled canvas rendered image with blue border - hope this helps!
I need to set canvas size using code and than drawImage on this canvas. The problem is drawn image is zoomed.
code follows -
currentImageDIV = $("#cnv").css({ "position" : "relative" ,"height": 400, "width": 800,"top" :"100px" , "border": "1px solid red" });
var c = document.getElementById("cnv");
var ctx = c.getContext("2d");
ctx.imageSmoothingEnabled = false;
var img = new Image();
img.src = "/images/sample2.jpg";
img.onload = function () {
ctx.drawImage(img, 0, 0);
}
If I do not set the canvas size than image drawn as expected, not zoomed.
How can I control the canvas size without drawImage zoomed image ?
I found out that if I hard code the width and height of the canvas tag than no zooming is taking place.
Another solution for this is to set the width and height using prop rather than css (e.g. $("#cnv").prop({"width": 800, "height": 600});)
I want to free-draw shapes with fabric.js. The outline is, say, 20px (such that the user sees it clearly).
After the user has drawn it, the shape should been filled with the same color as the outline.
The whole thing should be semi-transparent. Unfortunately, this causes the overlap between outline and fill to be less transparent and draws a strange "inner outline" to the shape.
Is there a way to make shape uniquely semi-transparent?
Maybe a trick would be: after the user has drawn the shape, "widen" the shape by half of outline thickness and set outline thickness to 1. Would that be possible?
See this https://jsfiddle.net/4ypdwe9o/ or below for an example.
var canvas = new fabric.Canvas('c', {
isDrawingMode: true,
});
canvas.freeDrawingBrush.width = 10;
canvas.freeDrawingBrush.color = 'rgb(255, 0, 0)';
canvas.on('mouse:up', function() {
canvas.getObjects().forEach(o => {
o.fill = 'rgb(255, 0, 0)';
o.opacity = 0.5;
});
canvas.renderAll();
})
canvas {
border: 1px solid #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.6.3/fabric.js"></script>
<canvas id="c" width="600" height="600"></canvas>
It's a bit tricky but can be solved using a temporary canvas. So you first render the path using a solid color fill on the temporary canvas, then copy it to the main canvas like this:
//create temporary canvas
var tmpCanvas = document.createElement("canvas");
tmpCanvas.width = canvas.width;
tmpCanvas.height = canvas.height;
var tmpCtx = tmpCanvas.getContext("2d");
//remember the original render function
var pathRender = fabric.Path.prototype.render;
//override the Path render function
fabric.util.object.extend(fabric.Path.prototype, {
render: function(ctx, noTransform) {
var opacity = this.opacity;
//render the path with solid fill on the temp canvas
this.opacity = 1;
tmpCtx.clearRect(0, 0, tmpCanvas.width, tmpCanvas.height);
pathRender.apply(this, [tmpCtx]);
this.opacity = opacity;
//copy the path from the temp canvas
ctx.globalAlpha = opacity;
ctx.drawImage(tmpCanvas, 0, 0);
}
});
See plunker here: https://plnkr.co/edit/r1Gs2wIoWSB0nSS32SrL?p=preview
In fabric.js 1.7.3, they have another implementation. When I use
fabricCanvasObject.getObjects('path').slice(-1)[0].setFill("red");
fabricCanvasObject.getObjects('path').slice(-1)[0].setOpacity(0.5);
instead of
fabricCanvasObject.getObjects('path').slice(-1)[0].fill = "red";
fabricCanvasObject.getObjects('path').slice(-1)[0].opacity = 0.5;
The boundary is painted correctly, without overlap. So, the temporary canvas from Janusz's answer is not needed anymore. For my former fabric.js 1.5.0, the answer from Janusz solved the problem.
Jetic,
You are almost finished your logic. Instead of using "opacity" use rgba:
canvas.getObjects().forEach(o => {
o.fill = 'rgba(255, 0, 0, 0.5)';
o.stroke = 'rgba(255, 0, 0, 0)';
// o.opacity = 0.5;
});
canvas.renderAll();
This question already has an answer here:
HTML Canvas: Drawing grid below a plot
(1 answer)
Closed 6 years ago.
I have a canvas, and I want to use drawImage to draw an image behind the current content on the canvas.
Due to the fact that there is content already on the canvas (I'm using Literally Canvas to create a canvas containing an image, so I can't really draw the image first), I cannot use drawImage before I render the rest of my content.
Is it possible to drawImage behind all other content on a canvas?
Yes you can just use globalCompositeOperation destination-over, but note that your first image needs some transparency, otherwise, you will obviously not see anything :
var img1 = new Image();
var img2 = new Image();
var loaded = 0;
var imageLoad = function(){
if(++loaded == 2){
draw();
}
};
img1.onload = img2.onload = imageLoad;
var draw = function(){
var ctx = c.getContext('2d');
ctx.drawImage(img1, 100,100);
// wait a little bit before drawing the background image
setTimeout(function(){
ctx.globalCompositeOperation = 'destination-over';
ctx.drawImage(img2, 0,0);
}, 500);
}
img1.src = "https://dl.dropboxusercontent.com/s/4e90e48s5vtmfbd/aaa.png";
img2.src = "https://picsum.photos/200/200";
<canvas id="c" width="200" height="200"></canvas>
Sorry about the previous post, I didn't properly read your post
Perhaps you could save the canvas, draw your image, and then reload the old content on top of your drawn image? Here's some JS psuedocode:
var imgData=ctx.getImageData(0,0,canvas.width,canvas.height);
ctx.drawImage('Your Image Watermark Stuff');
ctx.putImageData(imgData,0,0);
You can use KonvaJS. And then use layers for it.
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.rawgit.com/konvajs/konva/0.13.0/konva.min.js"></script>
<meta charset="utf-8">
<title>Konva Rect Demo</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #F0F0F0;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
var width = window.innerWidth;
var height = window.innerHeight;
var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
var layer = new Konva.Layer();
var imageObj = new Image();
imageObj.onload = function() {
var baseImage = new Konva.Image({
x: 50,
y: 50,
width: width,
height: height,
image: image
});
// add the shape to the layer
layer.add(rect);
// add the layer to the stage
stage.add(layer);
};
imageObj.src = 'url to your image'
</script>
</body>
</html>
A simple solution would be to use another canvas behind the first one.
Normally canvas pixels are initialized to transparent black and therefore are perfectly see-through.
If your first canvas is created opaque instead the only other option I can think to is
create a temporary canvas of the same size
draw your image in this temporary canvas
get the ImageData object of both the temporary canvas and of the original canvas
copy from the temporary canvas to the original canvas only where the original canvas is not set at the background color
In code:
var tmpcanvas = document.createElement("canvas");
tmpcanvas.width = canvas.width;
tmpcanvas.height = canvas.height;
var temp_ctx = tmpcanvas.getContext("2d");
// ... draw your image into temporary context ...
var temp_idata = temp_ctx.getImageData(0, 0, canvas.width, canvas.height);
var temp_data = temp_idata.data;
// Access the original canvas pixels
var ctx = canvas.getContext("2d");
var idata = ctx.getImageData(0, 0, canvas.width, canvas.height);
var data = idata.data;
// Find the background color (here I'll use first top-left pixel)
var br_r = data[0], bg_g = data[1], bg_b = data[2];
// Replace all background pixels with pixels from temp image
for (var i=0,n=canvas.width*canvas.height*4; i<n; i+=4) {
if (data[i] == bg_r && data[i+1] == bg_g && data[i+2] == bg_b) {
data[i] = tmp_data[i];
data[i+1] = tmp_data[i+1];
data[i+2] = tmp_data[i+2];
data[i+3] = tmp_data[i+3];
}
}
// Update the canvas
ctx.putImageData(idata, 0, 0);
this approach however will have a lower quality if the original canvas graphics has been drawn with antialiasing or if pixels of the background color are also used in the image (e.g. an object on #FFF white background where object highlights are also #FFF). Another problem is if the background color is not a perfectly uniform RGB value (this will happen if the image has been compressed with a lossy algorithm like jpeg).
All these problems could be mitigated with more sophisticated algorithms like range matching, morphological adjustments and color-to-alpha conversions (basically the same machinery used for chroma-keying).