I want to crop image using fabricJs but I'm stuck on the calculus of the correct area to be cropped.
When I click on the crop button, it doesn't crop the image to the correct area.
The calculation is wrong.
Here's my crop code:
function centerXY (w, h) {
return {
x: canvas.width / 2 - w / 2,
y: canvas.height / 2 - h / 2
}
}
// zoneWidth: width of area where I want to cropped
// zoneHeight: height of area where I want to cropped
$('#crop').click(function () {
var zoom = canvas.getZoom()
const imgTop = canvasImage.top
const imgLeft = canvasImage.left
const {x, y} = centerXY(zoneWidth, zoneHeight)
let top = zoneHeight - imgTop - y
let left = zoneWidth - imgLeft - x
zoneWidth /= zoom
zoneHeight /= zoom
canvasImage.clipTo = ctx => {
ctx.rect(left, top, zoneWidth, zoneHeight)
};
canvas.renderAll()
})
This calculation is based on this post: Crop Functionality using FabricJs
Here's the demo.
Note: You can use the input type range to zoom out and see where the image is cropped.
I solved my problem.
Here's the solution:
$('#crop').click(function () {
var zoom = canvas.getZoom()
let top = zoneY // Top position of area
let left = zoneX// Left position of area
zoneWidth /= zoom
zoneHeight /= zoom
canvas.clipPath = shapeRect; // fabric.Rect()
canvas.renderAll()
})
Instead of cropping the image, I cropped the canvas itself.
And Using the position of the area to crop
Here the final demo: https://jsfiddle.net/58nvte7h/43/
Related
I am building an interactive map of a small geographical area using HTML canvas. The canvas will be the full width and height of the map image used. The map will have a few points dotted on it, and when one is clicked, the canvas should centre on this point and then zoom in on it.
I have so far tried this by using translate on the drawing context, drawing the map image so that the clicked point sits in the centre of the canvas, and then using scale to zoom, as follows:
var clickX = e.offsetX || (e.pageX - canvas.offsetLeft);
var clickY = e.offsetY || (e.pageY - canvas.offsetTop);
clickedPoint = { x: clickX, y: clickY };
ctx.translate(
canvas.width/2 - clickedPoint.x,
canvas.height/2 - clickedPoint.y
);
ctx.scale(2, 2);
However, when I draw at this point, the image doesn't appear where expected. I'm guessing because the translated point doesn't line up after the scale function, as the translate happens correctly without the scale, but I can't seem to get this working - can anyone explain how to resolve this?
EDIT: example link - http://staging.clicky.co.uk/canvas/
To scale and zoom at a point on the canvas you must know the current zoom / scale and current origin. If you do not track this information you can not correctly scale at a point on the canvas.
If you have the scale and origin which from default is scale = 1, and origin = {x:0,y:0}
// scale is current scale
// origin is current origin
function scaleAt (at, amount) { // at in screen coords amount is amount to scale
scale *= amount;
origin.x = at.x - (at.x - origin.x) * amount;
origin.y = at.y - (at.y - origin.y) * amount;
};
var scale = 1;
const origin = {x : 0, y : 0};
// in mouse event
// scale is change in scale
scaleAt(clickedPoint,2); // scale 2 times at clickedPoint
// then set the transform
ctx.setTransform(scale,0,0,scale,origin.x,origin.y)
To center a point on an image at the center of the canvas using a fixed scale
// scale and origin from above code.
// newScale is absolute scale
function scaleMoveCenter (point, newScale) {
scale = newScale;
origin.x = canvas.width / 2 - point.x * scale;
origin.y = canvas.height / 2 - point.y * scale;
}
// in mouse event
scaleMoveCenter (clickedPoint,2);
// then set the transform
ctx.setTransform(scale,0,0,scale,origin.x,origin.y)
If the canvas already has a scale and origin set that is not default you need to find the point in that coordinate system.
//
const realPos = {};
realPos.x = (clickedPoint.x - origin.x ) / scale;
realPos.y = (clickedPoint.y - origin.y ) / scale;
// increase scale by 2
scaleMoveCenter (realPos ,scale * 2);
// then set the transform
ctx.setTransform(scale,0,0,scale,origin.x,origin.y)
The problem I am having is that Canvas will not draw a ShadowBlur effect when drawing my image if I rotate the Canvas at all to draw it. It works perfectly fine if I set the rotation value to 0 degrees.
I threw together a jsfiddle real fast, the image is pixelated and distorted but anyhow it reproduces the issue https://jsfiddle.net/zsw7wkv4/1/
Edit: Seems to be a Chrome only issue
Here is the code
var canvas = document.getElementById('GameCanvas');
var ctx = canvas.getContext("2d");
var asset = card.asset;
// set the card height based off the width
var height = width * 2.66;
// save the canvas before rotating
ctx.save();
// hover effect for drawn card
if (core.information.xoffset >= left && core.information.xoffset <= left + width && core.information.yoffset >= top && core.information.yoffset <= top + height) {
ctx.shadowColor = 'white';
ctx.shadowBlur = 15;
}
// translate to the center of the card
ctx.translate(core.information.pwidth * (left + width/2), core.information.pheight * (top + height/2));
// rotate the canvas for the card
ctx.rotate(rotation * Math.PI/180);
// translate back
ctx.translate(-core.information.pwidth * (left + width/2), -core.information.pheight * (top + height/2));
// draw the card
ctx.drawImage(asset, core.information.pwidth * left, core.information.pheight * top, core.information.pwidth * width, core.information.pheight * height);
// restore the canvas after rotating
ctx.restore();
Have same issue.
Draw your blured image to a temp canvas first, then draw that as an image (rotated) on your "final" canvas.
I would like to be able to click on an object, and have it zoomed to its boundingbox in the canvas viewport. How do I accomplish that? See http://jsfiddle.net/tinodb/qv989nzs/8/ for what I would like to get working.
Fabricjs' canvas has the zoomToPoint method (about which the docs say: Sets zoom level of this canvas instance, zoom centered around point), but that does not center to the given point, but it does work for zooming with scrolling. See http://jsfiddle.net/qv989nzs/
I tried several other approaches, like using canvas.setViewportTransform:
// centers a circle positioned at (200, 150)??
canvas.setViewportTransform([2, 0, 0, 2, -250, -150])
But I can't find the relation between the last two parameters to setViewportTransform and the position of the object.
(Btw, another problem is with the first example fiddle, that the zooming only works on the first click. Why is that?)
I found a way to do this, which is composed of:
canvas.setZoom(1) // reset zoom so pan actions work as expected
vpw = canvas.width / zoom
vph = canvas.height / zoom
x = (object.left - vpw / 2) // x is the location where the top left of the viewport should be
y = (object.top - vph / 2) // y idem
canvas.absolutePan({x:x, y:y})
canvas.setZoom(zoom)
See http://jsfiddle.net/tinodb/4Le8n5xd/ for a working example.
I was unable to get it to work with zoomToPoint and setViewportTransform (the latter of which does strange things, see for example http://jsfiddle.net/qv989nzs/9/ and click the blue circle; it is supposed to put the top left viewport at (25, 25), but it does not)
Here's an example of how to do it with setViewportTransform:
// first set the zoom, x, and y coordinates
var zoomLevel = 2;
var objectLeft = 250;
var objectTop = 150;
// then calculate the offset based on canvas size
var newLeft = (-objectLeft * zoomLevel) + canvas.width / 2;
var newTop = (-objectTop * zoomLevel) + canvas.height / 2;
// update the canvas viewport
canvas.setViewportTransform([zoomLevel, 0, 0, zoomLevel, newLeft, newTop]);
I've created a basic HTML5 image slider where images move from top to bottom in a canvas.
I want all the images rotated at angle of 5 degrees. When I tried it out there seems to be some
distortion to the canvas and the image is not properly rotated.
I've tried the method for rotation mentioned in the below post
How do I rotate a single object on an html 5 canvas?
Fiddle - http://jsfiddle.net/DS2Sb/
Code
this.createImage = function (image, width, height) {
var fbWallImageCanvas = document.createElement('canvas');
var fbWallImageCanvasContext = fbWallImageCanvas.getContext('2d');
fbWallImageCanvas.width = width;
fbWallImageCanvas.height = height;
fbWallImageCanvasContext.save();
fbWallImageCanvasContext.globalAlpha = 0.7;
this.rotateImage(image, 0, 0, width, height, 5, fbWallImageCanvasContext);
fbWallImageCanvasContext.drawImage(image, width, height);
fbWallImageCanvasContext.restore();
return fbWallImageCanvas;
};
this.rotateImage = function (image, x, y, width, height, angle, context) {
var radian = angle * Math.PI / 180;
context.translate(x + width / 2, y + height / 2);
context.rotate(radian);
context.drawImage(image, width / 2 * (-1), height / 2 * (-1), width, height);
context.rotate(radian * (-1));
context.translate((x + width / 2) * (-1), (y + height / 2) * (-1));
};
The distortion you see is due to the fact that a rotated image will only fit in a larger canvas. So what we see is a rectangle view on a rotated image.
The computations are not that easy to get things done properly, but instead of pre-computing the rotated image, you might rotate them just when you draw them, which lets you also change the angle whenever you want (and opacity also btw).
So i simplified createImage, so that it just stores the image in a canvas (drawing a canvas is faster than drawing an image) :
this.createImage = function(image , width, height) {
var fbWallImageCanvas = document.createElement('canvas');
fbWallImageCanvas.width = width;
fbWallImageCanvas.height = height;
var fbWallImageCanvasContext = fbWallImageCanvas.getContext('2d');
fbWallImageCanvasContext.drawImage(image,0,0);
return fbWallImageCanvas;
};
And i changed drawItem so it draws the image rotated :
this.drawItem = function(ct) {
var angle = 5;
var radian = angle * Math.PI/180;
ct.save();
ct.translate(this.x + this.width/2 , this.y + this.height/2);
ct.rotate(radian);
ct.globalAlpha = 0.7;
ct.drawImage(fbc, - this.width/2, -this.height/2 , this.width, this.height);
ct.restore();
this.animate();
};
You'll probably want to refactor this, but you see the idea.
fiddle is here :
http://jsfiddle.net/DS2Sb/1/
Here is a link to a small html5 tutorial I created a while ago:
https://bitbucket.org/Garlov/html5-sidescroller-game-source
And here is the rotate code:
// save old coordinate system
ctx.save();
// move to the middle of where we want to draw our image
ctx.translate(canvas.width/2, canvas.height-64);
// rotate around that point
ctx.rotate(0.02 * (playerPosition.x));
//draw playerImage
ctx.drawImage(playerImage, -playerImage.width/2, -playerImage.height/2);
//and restore coordniate system to default
ctx.restore();
I want to visualize a huge diagram that is drawn in a HTML5 canvas. As depicted below, let’s imagine the world map, it’s impossible to visualize it all at the same time with a “decent” detail. Therefore, in my canvas I would like to be able to pan over it using the mouse to see the other countries that are not visible.
Does anyone know how to implement this sort of panning in a HTML5 canvas? Another feature would be the zoom in and out.
I've seen a few examples but I couldn't get them working nor they seam to address my question.
Thanks in advance!
To achieve a panning functionality with a peep-hole it's simply a matter of two draw operations, one full and one clipped.
To get this result you can do the following (see full code here):
Setup variables:
var ctx = canvas.getContext('2d'),
ix = 0, iy = 0, /// image position
offsetX = 0, offsetY = 0, /// current offsets
deltaX, deltaY, /// deltas from mouse down
mouseDown = false, /// in mouse drag
img = null, /// background
rect, /// rect position
rectW = 200, rectH = 150; /// size of highlight area
Set up the main functions that you use to set size according to window size (including on resize):
/// calc canvas w/h in relation to window as well as
/// setting rectangle in center with the pre-defined
/// width and height
function setSize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
rect = [canvas.width * 0.5 - rectW * 0.5,
canvas.height * 0.5 - rectH * 0.5,
rectW, rectH]
update();
}
/// window resize so recalc canvas and rect
window.onresize = setSize;
The main function in this is the draw function. Here we draw the image on the position calculated by mouse moving (see next section).
First step to get that washed-out look is to set alpha down to about 0.2 (you could also draw a transparent rectangle on top but this is more efficient).
Then draw the complete image.
Reset alpha
Draw the peep-hole using clipping with corrected offsets for the source.
-
/// main draw
function update() {
if (img === null) return;
/// limit x/y as drawImage cannot draw with negative
/// offsets for clipping
if (ix + offsetX > rect[0]) ix = rect[0] - offsetX;
if (iy + offsetY > rect[1]) iy = rect[1] - offsetY;
/// clear background to clear off garbage
ctx.clearRect(0, 0, canvas.width, canvas.height);
/// make everything transparent
ctx.globalAlpha = 0.2;
/// draw complete background
ctx.drawImage(img, ix + offsetX, iy + offsetY);
/// reset alpha as we need opacity for next draw
ctx.globalAlpha = 1;
/// draw a clipped version of the background and
/// adjust for offset and image position
ctx.drawImage(img, -ix - offsetX + rect[0], /// sx
-iy - offsetY + rect[1], /// sy
rect[2], rect[3], /// sw/h
/// destination
rect[0], rect[1], rect[2], rect[3]);
/// make a nice sharp border by offsetting it half pixel
ctx.strokeRect(rect[0] + 0.5, rect[1] + 0.5, rect[2], rect[3]);
}
Now it's a matter of handling mouse down, move and up and calculate the offsets -
In the mouse down we store current mouse positions that we'll use for calculating deltas on mouse move:
canvas.onmousedown = function(e) {
/// don't do anything until we have an image
if (img === null) return;
/// correct mouse pos
var coords = getPos(e),
x = coords[0],
y = coords[1];
/// store current position to calc deltas
deltaX = x;
deltaY = y;
/// here we go..
mouseDown = true;
}
Here we use the deltas to avoid image jumping setting the corner to mouse position. The deltas are transferred as offsets to the update function:
canvas.onmousemove = function(e) {
/// in a drag?
if (mouseDown === true) {
var coords = getPos(e),
x = coords[0],
y = coords[1];
/// offset = current - original position
offsetX = x - deltaX;
offsetY = y - deltaY;
/// redraw what we have so far
update();
}
}
And finally on mouse up we make the offsets a permanent part of the image position:
document.onmouseup = function(e) {
/// was in a drag?
if (mouseDown === true) {
/// not any more!!!
mouseDown = false;
/// make image pos. permanent
ix += offsetX;
iy += offsetY;
/// so we need to reset offsets as well
offsetX = offsetY = 0;
}
}
For zooming the canvas I believe this is already answered in this post - you should be able to merge this with the answer given here:
Zoom Canvas to Mouse Cursor
To do something like you have requested, it is just a case of having 2 canvases, each with different z-index. one canvas smaller than the other and position set to the x and y of the mouse.
Then you just display on the small canvas the correct image based on the position of the x and y on the small canvas in relation to the larger canvas.
However your question is asking for a specific solution, which unless someone has done and they are willing to just dump their code, you're going to find it hard to get a complete answer. I hope it goes well though.