I am trying to work on image processing with fabric js.I am dealing with very huge images hence I have to save copy of canvases after image processing so that next time it can be shown faster with jquery's id.show. But I want to render the images on the exact location. I am using canvas.zoomToPoint and canvas.relativePan to zoom and pan the image but after I do zoom + pan and then apply image processing to show hidden canvas and apply hiddencanvas.zoomToPoint and hiddencanvas.relativePan on hidden canvas, it doesn't render the image on the exact location where I left the older canvas. Am I doing any mistake. Here's a supporting Fiddle .However, the fiddle renders a image by uploading and if you zoom and pan and click on invert, the inverted image doesn't move there Panning code : ` ``var panning = false;
canvas.on('mouse:up', function (e) {
panning = false;
});
canvas.on('mouse:down', function (e) {
panning = true;
});
canvas.on('mouse:move', function(e) {
if (panning && e && e.e) {
var x = e.offsetX, y = e.offsetY;
var delta = new fabric.Point(e.e.movementX, e.e.movementY);
canvas.relativePan(delta);
//Above statement pan's the image but what to save to server in order to render the image on the exact panned location ?
}
});
`` whereas this is zoom code : canvas.zoomToPoint({ x: x, y: y }, newZoom);
I found the answer , it was a very silly mistake .
Every canvas has a Viewport transform .
So, we just need to get canvas.viewportTransform and then we can get the ScaleX, scaleY, left , top as [scaleX, 0,0, scaleY, left,top] .
Hope , it will help someone .
Related
I am trying to draw on a rotating p5.js canvas, where the canvas element is being rotated via its transform CSS attribute. The user should be able to draw points on the canvas with the cursor, even while the canvas element is actively rotating.
The below code is what I have tried so far. However, it is currently not working, because the points are not correctly showing up where the mouse is hovering over the rotating canvas. I'm currently testing this out on the p5.js editor.
let canvas
function setup() {
canvas = createCanvas(400, 400)
background('#fb88f3')
strokeWeight(10)
}
function draw() {
canvas.style(`transform: rotate(${frameCount}deg)`)
translate(200, 200)
rotate(-radians(frameCount))
point(mouseX-200, mouseY-200)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
I've found this related StackOverflow post where they drew on a rotating canvas as a p5.Graphics element. However, for my purposes, I'd like to rotate the actual element instead, as part of a simple p5.js painting application I am working on.
I've been stuck on this for a while and would appreciate any help! Thanks in advance!
This seems to be a bug in p5.js.
The mouse coordinate seems to depend on the size of the bounding box of the canvas. The bounding box depends on the rotation. Therefor you need to scale the offset for the calculation of the center of the canvas.
Unfortunately, that's not all. All of this does not depend on the current angle, but on the angle that was set when the mouse was last moved. Hence the scale needs to be computed in the mouseMoved callback:
let scale;
function mouseMoved() {
let angle_rad = radians(frameCount)
scale = abs(sin(angle_rad)) + abs(cos(angle_rad))
}
let center_offset = 200 * scale;
point(mouseX-center_offset, mouseY-center_offset)
let canvas, scale
function setup() {
canvas = createCanvas(400, 400)
background('#fb88f3')
strokeWeight(10)
}
function draw() {
let angle = frameCount;
let angle_rad = radians(angle)
let center_offset = 200 * scale
canvas.style(`transform: rotate(${angle}deg)`)
translate(200, 200)
rotate(-angle_rad)
point(mouseX-center_offset, mouseY-center_offset)
}
function mouseMoved() {
let angle_rad = radians(frameCount)
scale = abs(sin(angle_rad)) + abs(cos(angle_rad))
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
I have an HTML canvas element and have implemented a brush that captures the mousedown, mousemove, and mouseup events of the canvas element. This all works fine for drawing on the canvas. However, I don't think I like how you can't continue a drawing if your mouse leaves the canvas mid stroke. It kind of just cuts it off. It's very unforgiving to the person and not very user-friendly in my opinion.
If you open up Microsoft Paint and begin drawing with the brush or ellipse or something, as long as you start within the canvas, you can drag your mouse anywhere on your screen and re-enter the canvas wherever. It also makes it easy, for example, to draw quarter-circles in corners because you can drag the ellipse tool off-screen. I hope this makes sense.
Anyways, I was wondering if there was a way to implement this with the HTML5 canvas or how I would go about implementing this sort of thing. The user would never have to actually seen anything drawn over there; it's mostly just going to be a feature for usability.
Edit: A problem with a lot of these solutions is how to handle coordinates. Currently my canvas is in the middle of the screen and the top left of the canvas is (0, 0) and the bottom right is (500, 500). The translation work of the coordinates has to be considered as well.
Edit2: I found out that apparently you can draw off the canvas bounds just fine. For example, you can supply negative widths, heights, and coordinates and the canvas element will handle it just fine. So basically the solution will likely involve just capturing the document's mousemove and mouseup and just translating the x and y to start at the canvas's top left corner.
Here is one way you can keep drawing when reenter the canvas:
Create a global variable and set that one to true on mousedown
Add a global event for mouseup so you can catch if someone do that outside
the canvas, and if so, set global variable to false, and the canvas element's mouseup need of course also to set the same variable
On mousemove, check for global variable to be true before draw
To draw "outside" the canvas, like quarter-circles in a corner, I would move all events to the document level as global handler and catch the canvas element on click and pass its client coordinates to be computed with the document coordinates.
Here is an extremely rough first draft of how you can listen for mouse events on the window rather than the canvas to be able to draw continuously:
var logger = document.getElementById("logger"),
mState = document.getElementById("mState"),
mX = document.getElementById("mX"),
mY = document.getElementById("mY"),
cX = document.getElementById("cX"),
cY = document.getElementById("cY"),
c = document.getElementById("canvas"),
ctx = c.getContext("2d");
var mouse = {
x: 0,
y: 0,
state: ""
};
function printCanvasLocation() {
var b = c.getBoundingClientRect();
cX.innerHTML = b.top;
cY.innerHTML = b.left;
}
function setState(mouseE, state) {
mouse.x = mouseE.clientX;
mouse.y = mouseE.clientY;
mX.innerHTML = mouseE.clientX;
mY.innerHTML = mouseE.clientY;
if (state) {
mState.innerHTML = state;
mouse.state = state;
}
}
window.addEventListener("mousedown", function(mouseE) {
setState(mouseE, "down");
});
window.addEventListener("mouseup", function(mouseE) {
setState(mouseE, "up");
});
window.addEventListener("mousemove", function(mouseE) {
var offset = c.getBoundingClientRect();
var fix = {
x1: (mouse.x - offset.left),
y1: (mouse.y - offset.top),
x2: (mouseE.clientX - offset.left),
y2: (mouseE.clientY - offset.top)
};
if (mouse.state === "down") {
ctx.moveTo(fix.x1, fix.y1);
ctx.lineTo(fix.x2, fix.y2);
ctx.strokeStyle = "#000";
ctx.stroke();
}
setState(mouseE);
});
window.addEventListener("resize", function() {
printCanvasLocation();
});
printCanvasLocation();
.center {
text-align: center;
}
canvas {
background-color: lightblue;
}
<main>
<div class="center">
<canvas id="canvas" width="128" height="128">If you can see me, you should update your browser</canvas>
</div>
<div id="logger" role="log">
<span>State: </span><span id="mState">Unknown</span>
<span>X: </span><span id="mX">Unknown</span>
<span>Y: </span><span id="mY">Unknown</span>
<span>Canvas X: </span><span id="cX">Unknown</span>
<span>Canvas Y: </span><span id="cY">Unknown</span>
</div>
</main>
One solution would be to literally make the canvas the size of the window, and scale its size with it. The canvas can be mostly transparent.
I'm sure there's also a way to make mouse events and such go through the canvas first but then pass through to the elements behind, if that's desired. (See: "js events bubbling and capturing".)
But then you would have absolute control and be able to draw anything anywhere.
I am using this library to upload and crop the images
http://andyshora.com/angular-image-cropper.html.
I have added functions for rotate left, rotate right, flip horizontal and flip vertical to overcome mobile EXIF orientation issues.
scope.clockwise = function() {
scope.angleInDegrees+=90;
drawRotated(scope.angleInDegrees);
};
scope.counterclockwise = function() {
scope.angleInDegrees-=90;
drawRotated(scope.angleInDegrees);
};
function drawRotated(degrees){
ctx.clearRect(0,0,canvasWidth,canvasHeight);
ctx.save();
ctx.translate(canvasWidth/2,canvasHeight/2);
ctx.rotate(degrees*Math.PI/180);
transformPoint = untransformPoint = function(x, y) {
return {
x: x,
y: y
};
};
ctx.drawImage($img,-$img.width/2,-$img.width/2);
ctx.restore();
}
After rotate I can not move the image with rotated image. While clicking image its automatically displaying first uploaded image(ie before rotation).
Fiddle link http://jsfiddle.net/x9f94yz5/1/ (Not a running code)
I have a page which allows you to browse in an image, then draw on it and save both the original and the annotated version. I am leveraging megapix-image.js and exif.js to help in rendering images from multiple mobile devices properly. It works great, except in certain orientations. For example, a vertical photo taken on an iPhone4s is considered orientation 6 by exif and gets flipped accordingly by megapix-image so it's rendered nicely on the canvas. For some reason, when I draw on it afterward, it seems like the drawing is reversed. Mouse and touch both behave the same way. The coordinates look right to me (meaning they match a working horizontal pic and a non-working vertical pic), as does the canvas height and width when megapix-image.js flips it. This leads me to believe it has something to do with the context, but honestly, I am not really sure. I have a JS fiddle of the part of my work that shows the behavior. Just browse in a vertically taken pic from a mobile device or take a pic in vertical format on a mobile device and use it. I think all will show this same behavior.
The final rendering is done like this:
function RenderImage(file2) {
if (typeof file2[0].files[0] != 'undefined') {
EXIF.getData(file2[0].files[0], function () {
orientation = EXIF.getTag(this, "Orientation");
var file = file2[0].files[0];
var mpImg = new MegaPixImage(file);
var resCanvas1 = document.getElementById('annoCanvas');
mpImg.render(resCanvas1, {
maxWidth: 700,
maxHeight: 700,
orientation: orientation
});
});
}
}
But the full jsfiddle is here:
http://jsfiddle.net/awebster28/Tq3qU/6/
Does anyone have any clues for me?
If you look at the lib you are using there is a transformCoordinate function that is used to set the right transform before drawing.
And they don't save/restore the canvas (boooo!!!) so it remains with this transform after-wise.
Solution for you is to do what the lib should do : save the context before the render and restore it after :
function RenderImage(file2) {
// ... same code ...
var mpImg = new MegaPixImage(file);
var eData = EXIF.pretty(this);
// Render resized image into canvas element.
var resCanvas1 = document.getElementById('annoCanvas');
var ctx = resCanvas1.getContext('2d');
ctx.save();
//setting the orientation flips it
mpImg.render(resCanvas1, {
maxWidth: 700,
maxHeight: 700,
orientation: orientation
});
ctx.restore();
//...
}
I ended up fixing this by adding another canvas to my html (named "annoCanvas2"). Then, I updated megapix-image.js to include this function, which draws the contents of the new canvas to a fresh one:
function drawTwin(sourceCanvas)
{
var id = sourceCanvas.id + "2";
var destCanvas = document.getElementById(id);
if (destCanvas !== null) {
var twinCtx = destCanvas.getContext("2d");
destCanvas.width = sourceCanvas.width;
destCanvas.height = sourceCanvas.height;
twinCtx.drawImage(sourceCanvas, 0, 0, sourceCanvas.width, sourceCanvas.height);
}
}
Then, just after the first is rotated and flipped and rendered, I rendered the resulting canvas to my "twin". Then I had a nice canvas, with my updated image that I could then draw on and also save!
var tagName = target.tagName.toLowerCase();
if (tagName === 'img') {
target.src = renderImageToDataURL(this.srcImage, opt, doSquash);
} else if (tagName === 'canvas') {
renderImageToCanvas(this.srcImage, target, opt, doSquash);
//------I added this-----------
drawTwin(target);
}
I was glad to have it fixed so I met my deadline, but I am still not sure why I had to do this. If anyone out there can explain it, I'd love to know why.
I write this html code :
<div id="container">
<canvas id="imageView" width="1181" height="1181">
<p>Unfortunately, your browser is currently unsupported by our web
application.</p>
</canvas>
<script type="text/javascript">
window.onload = function() {
var c = document.getElementById('imageView');
var cxt = c.getContext('2d');
var img = new Image();
img.src = "map.jpg";
cxt.drawImage(img, 0, 0);
};
</script>
</div>
And write this javascript :
this.mousemove = function(ev) {
if (!tool.started) {
return;
}
var x = Math.min(ev._x, tool.x0),
y = Math.min(ev._y, tool.y0),
w = Math.abs(ev._x - tool.x0),
h = Math.abs(ev._y - tool.y0);
context.clearRect(0, 0, canvas.width, canvas.height);
if (!w || !h) {
return;
}
context.clearRect(x, y, w, h);
context.strokeRect(x, y, w, h);
this code is make a rectangle . I want change this rectangle to a area map , that when I click on the area do something , (for example open google.com) .
If I understand you correctly you want to invoke a function when you hit a pixel on the actual map - not just in the map area.
Method 1
You can check a map click in more than one way. You can simply check for the pixel value at the click point to check if it is inside the area you want it to be by comparing the map color value.
I provided an example below for this method.
Method 2
You can pre-define a polygon which traces the outline of the map area you want to check.
Then build a path (ctx.beginPath(); and ctx.lineTo(..); etc.) to allow the use of the method:
if (ctx.isPointInPath(x, y)) { ... };
This is a good method if you have small regions to check.
Method 3
Store a separate image of the map containing only a matte (sort of an alpha map), That is usually black (or transparent) for non-clickable areas, white for clickable areas.
This is useful if your map is complex color-wise and a simple pixel value check is not trivial.
And speaking of which: you can even provide different solid color values for different areas so that you can define red color = USA, blue = Argentina, etc. As these are not visible to the user the only thing that matters is that the color value can be recognized (for this reason don't save images for this use with an ICC color profile).
Then project the mouse position from the click onto the matte image (which is basically an off-screen canvas where the matte image is drawn into) and check for the color (white or other color).
Example for method 1
This is a simple example, but in any case there are a couple of things you need to know in advance:
That the image is loaded from same server as the page or from a domain that allow cross-origin use. Or else you cannot grab a pixel from the map due to security reasons.
You need to know what color or alpha value to check for. If the map is solid and everything is transparent you just need to check for alpha value above zero (as in this example), and if not just check the RGB value of the region you want to trigger an action with.
ONLINE DEMO HERE
HTML:
<canvas width=725 height=420 id="demo"></canvas>
JavaScript:
var ctx = demo.getContext('2d'),
img = new Image();
/// we need to wait for the image to actually load:
img.onload = function() {
/// image is loaded and we can raw it onto canvas
ctx.drawImage(this, 0, 0);
/// enable mouse click
demo.onclick = function(e) {
/// adjust mouse position to be relative to canvas
var rect = demo.getBoundingClientRect(),
x = e.clientX - rect.left,
y = e.clientY - rect.top;
/// grab a pixel
var data = ctx.getImageData(x, y, 1, 1).data;
/// check it's alpha value to see if we're in a map point
/// this of course assumes the map has transparent areas.
/// if not just check for the color values instead.
if (data[3] > 0) alert('We hit map');
}
}
/// we need crossOrigin allowed image or we can't grab pixel later
img.crossOrigin = 'anonymous';
img.src = 'http://i.imgur.com/x8Ap3ij.png';
Just replace the alert with:
window.open('http://google.com/');
if you want it to open a new window/tab.
You can turn canvas into an anchor link by using addEventListener to listen for clicks on the canvas.
Then you can use window.open to open google in a new browser tab.
Also, you need to use image.onload to give your image time to load before using drawing it.
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var img=new Image();
img.onload=function(){
ctx.drawImage(img,0,0);
canvas.addEventListener("click",function(){
window.open("http://google.com");
});
}
img.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/google.jpg";