Can't resize canvas/scale using Fabric.js - html

I need to display a large canvas as a smaller one for the purposes of good user interaction, but uploading a large canvas later. I am using Fabric.js and am trying the Canvas css trick where you set the width/height for the canvas in HTML and then set it via CSS to smaller one as documented here:
fabric.js resize canvas to fit screen
When I attempt to resize as follows, it makes the canvas 300x150 (default canvas) and doesn't respect my CSS.
HTML:
<canvas id="c"></canvas>
JavaScript:
var canvas = new fabric.Canvas('c', {
});
canvas.width = 1000;
canvas.height = 1000;
CSS:
#c {
width: 650px;
height: 436px;
}
How can I adjust the "size" of the canvas the user interacts with while maintaining the "actual size" of the larger canvas via fabric.js? I am using bootstrap as well and am unsure if this would have any impact.

Replace your javascript code with:
var canvas = new fabric.Canvas('c', {});
canvas.setWidth(1000) ;
canvas.setHeight(1000);

Related

Building an web based image annotation tool - saving annotations to localStorage

I am building a web application for annotating images. The work flow is as follows:
Select a project - using : action = list all sub-projects
Click on a sub-project : action = fetch all the images within-sub project
Display the images as a horizontal scrollable thumbnail gallery
Onclick image thumbnail from the gallery, display the larger image for annotation.
I am using canvas to display larger image. I have used another canvas as a layer to the first one, and I am able to draw rectangles using mouse over regions of interest. I am saving it locally. However, when I move on to the next image, the rectangle also gets carried to the next image.
My question is, instead of using just one layer, do I have to dynamically create as many canvas layers as I have in the annotation dataset. I am not sure because in each sub project I have around 8000-9000 images. Though I wont be annotating on all of them, still creating as many canvases as layers doesn't really sound good for me.
The following is the code:
HTML Canvas
<div class="body"> <!-- Canvas to display images begins -->
<canvas id="iriscanvas" width=700px height=700px style="position:absolute;margin:50px 0 0 0;z-index:1"></canvas>
<canvas id="regncanvas" onclick="draw(this, event)" width=700px height=700px style="position:absolute;margin:50px 0 0 0;z-index:2"></canvas>
</div> <!-- Canvas to display images ends -->
Step 4 given above: OnClick display thumbnail
function clickedImage(clicked_id) {
var clickedImg = document.getElementById(clicked_id).src;
var clickedImg = clickedImg.replace(/^.*[\\\/]/, '');
localStorage.setItem("clickedImg", clickedImg);
var canvas = document.getElementById("iriscanvas");
var ctx = canvas.getContext("2d");
var thumbNails = document.getElementById("loaded_img_panel");
var pic = new Image();
pic.onload = function() {
ctx.drawImage(pic, 0,0)
}
thumbNails.addEventListener('click', function(event) {
pic.src = event.target.src;
});
}
Draw rectangles on second layer of canvas
window.onload=function(){
c=document.getElementById("regncanvas");
if (c) initCanvas(c);
};
function initCanvas(canvas){
// Load last canvas
loadLastCanvas(canvas);
}
function draw(canvas, event){
// Draw at random place
ctx=c.getContext("2d");
ctx.fillStyle="#ff0000";
ctx.beginPath();
ctx.fillRect (250*Math.random()+1, 220*Math.random()+1, 40, 30);
ctx.closePath();
ctx.fill();
// Save canvas
saveCanvas(canvas);
}
function saveCanvas(c){
localStorage['lastImgURI']=c.toDataURL("image/png");
}
function loadLastCanvas(c){
if (!localStorage['lastImgURI']) return;
img = new Image();
img.onload = function() {
ctx=c.getContext("2d");
ctx.drawImage(img, 0, 0, img.width, img.height);
};
img.src= localStorage['lastImgURI'];
}
Can someone guide me please?
The following is a screen grab of my application:
I have developed OCLAVI which is an image annotation tool with loads of features. It's still in beta but just after 3 weeks of release, it is gaining attraction quickly.
I have few advises for you.
HTML Canvas follow draw and forget strategy and every time redrawing the image is not a good idea. Be it 10 images or 10k, you should have one canvas for drawing the image and one canvas for drawing the shapes. Image canvas need be touched only when the image changes. Different shapes can share the same canvas.
You should integrate a data storage. Local storage is clearly not a good option to store this amount of data (especially if you have a team member who also would be annotating on the same image dataset.)
Isolate the code to a separate-separate file according to the shape. It will be very handy when you will think of adding support for Circle, Polygon, Cuboidal, Point interactions. Trust me following OOPs concepts will relive you from a lot of pain.
In terms of complexity
zooming with coordinates is easy
move with coordinates is of medium level difficulty
but you need to think with pen and paper to implement move on a zoomed image canvas (P.S. take care of the canvas flickering when the image moves
). How much the image can move in each direction also need to be calculated.
Take care of the image to canvas dimension ratio because at the end you need to have the coordinates scaled down to image level.
If your canvas size vs image size ratio is 1:1 then your job is simplified.
But this won't happen always because some images might be very small or very large to directly fit in window screen and you need to scale up and down accordingly.
The complexity increases if you like to use percentage width and height for canvas and your other team member annotating the image has a different screen size. So he drawing something will look something else on your screen.

Should I specify height and width to canvas new Image() constructor?

I've seen in all canvas image creating documentation (https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Using_images) that the new image constructor, e.g. myImg = new Image();, is used just like that with no parameters. However, I know that it takes optional parameters for width and height, e.g. myImg = new Image(400,300);.
Is it good practice to specify those parameters if you know the width and height of the image beforehand?
After the constructor I use myImg.src = 'myurl.jpg'; and myImg.onload = function() { ctx.drawImage(myImg, x, y)...};
If you're going to draw the new image to the canvas, then there's no need to specify the image size in the image's constructor. Javascript will know the image's native size after the image is fully loaded in myImg.onload.
When you draw the image on the canvas using context.drawImage, by default the image will be drawn at its native size. But you can also specify a different image size with extra arguments to drawImage:
// draw myImg on the canvas in the top-left corner
// and resize the image to half-size
context.drawImage(myImg, 0,0, myImg.width/2, myImg.height/2);
If you want the canvas to be the same size as your image, you must resize the canvas inside your myImg.onload which is the first time javascript knows the native size of the image:
// create the canvas element its context
var canvas=document.createElement('canvas');
var context=canvas.getContext('2d');
// create the image object
var img=new Image();
img.onload=start;
img.src="myImage.png";
function start(){
// The native image size is now known,
// so resize the canvas to the same size as the image
canvas.width=img.width;
canvas.height=img.height;
// draw the image on the canvas
context.drawImage(img,0,0);
}

CSS Aspect Ratio on Canvas

Recently, Mozilla launched a HTML5 game called Browser Quest. In the game, if you resized the window, the canvas would also resize.
I looked more into and I saw that it was beacuse of usign CSS3 Media Queries found here https://developer.mozilla.org/en/CSS/Media_queries
However, I still don't think I am doing it right. My canvas ID is #canvas. How would I go about putting it for my canvas?
my canvas specific width/height: height:352px; width:512px;
So you don't want to define size of a canvas in CSS since you will only ever be scaling it away from its "true" size. You always want to use the width and height attributes of the Canvas instead.
But that doesn't mean you can't define it's parent's size that way. Wrap the canvas in a div and set the div's CSS width/height to 100% (or whatever you please)
In code during setup you are going to have to do:
// javascript pseudocode
canvas.width = theCanvasParent.clientWidth; // or whatever attribute it is, I'd reccomend putting all of those things in one giant container div
canvas.height = theCanvasParent.clientHeight;
Since most browsers do not fire an event when the parent div changes size, you'll simply have to check, say, every half second with a timer to see if the div has changed size. If it has, then you resize the canvas accordingly.
However there is the onresize event, and depending on how your page is setup this may do the trick.
In Firefox, Opera, Google Chrome and Safari, the onresize event is fired only when the size of the browser window is changed.
In Internet Explorer, the onresize event is fired when the size of the browser window or an element is changed.
So if the only way to change your div's size is by changing the window's size, onresize will do you just fine. Otherwise you'll need a timer that constantly checks to see if the canvas size and div size are different (and if so, to resize the canvas).
A timer that constantly checks is what the Mozilla Bepsin team did (before Bespin became Skywriter and then merged with the Ace project, dropping all Canvas use)
Media queries won't provide you with the functionality you seek. Their purpose is simply to limit when a particular stylesheet is applied to a page.
Furthermore, the CSS width and height properties do not adjust the actual dimensions of canvas elements. Instead, they scale the element to the requested size. In your case, I'm assuming you want the canvas to actually be a different resolution. The resolution of the canvas is specified via the DOM width and height attributes on your <canvas> tag.
In order to handle resizing, you will need to use window.onresize to capture the resize event. Your canvas code will need to then create a new canvas at the desired size and properly copy over everything from the original canvas (when you resize a canvas object its pixel data is cleared).
As was yet pointed by Xenethyl, the most important point is to hook onresize so that you can adapt to your new canvas object size :
adjust the canvas dimensions (the drawing area dimensions) to the canvas rendering area (clientWidth and clientHeight)
take into account the new dimensions of the canvas for your drawing algorithms
redraw the canvas
You don't have to make a new canvas (which would force you to rehook other event handlers).
Most of the canvas in my web applications, in order to be perfectly adjusted to the window, are managed by a dedicated class whose skeleton is here :
function Grapher(options) {
this.graphId = options.canvasId;
this.dimChanged = true; // you may remove that if you want (see above)
};
Grapher.prototype.draw = function() {
if (!this._ensureInit()) return;
// makes all the drawing, depending on the state of the application's model
// uses dimChanged to know if the positions and dimensions of drawed objects have
// to be recomputed due to a change in canvas dimensions
}
Grapher.prototype._ensureInit = function() {
if (this.canvas) return true;
var canvas = document.getElementById(this.graphId);
if (!canvas) {
return false;
}
if (!$('#'+this.graphId).is(':visible')) return false;
this.canvas = canvas;
this.context = this.canvas.getContext("2d");
var _this = this;
var setDim = function() {
_this.w = _this.canvas.clientWidth;
_this.h = _this.canvas.clientHeight;
_this.canvas.width = _this.w;
_this.canvas.height = _this.h;
_this.dimChanged = true;
_this.draw(); // calls the function that draws the content
};
setDim();
$(window).resize(setDim);
// other inits (mouse hover, mouse click, etc.)
return true;
};
In your case I would create a new Grapher({canvasId:'#canvas'}) and the #canvas dimensions are defined in css (and usually adjust in complex ways to the available space).
The most interesting points are in the setDim function.

Is it possible to mask / crop a html element group dynamically?

Is there a way use javascript, css, canvas or svg to create an animated mask (can be just binary).
The closest I've come is using this:
var data = canvas.toDataURL();
$('#masked').css("-webkit-mask-image","url("+data+")");
But this only works in chrome and safari and is a bit buggy.
Is there a way of using css to draw a really simple triangular mask?
If its going to be dynamic you can do it with canvas, draw the path you want and fill it, by default canvas is transparent, so any of the parts not filled will show the element underneath. However if its going to be a static mask I suggest just using a png.
Live Demo
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d");
canvas.width = canvas.height = 200;
// make a path for a triangle
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(200,0);
ctx.lineTo(200,200);
ctx.lineTo(100,50);
ctx.lineTo(0,200);
ctx.lineTo(0,0);
ctx.fill();

Drag div element with canvas to another

I'm having my first experience in developing html5 applications. My issue is to make room plan. In the top of the page I have elements to drag to the bottom map area (they are copied). In the map area I can move elements, but not copy.
I've built drag'n'drop with help of image elements. But now I want to use canvas for updating numbers on images. I want to use canvas text functions for updating images.
The problem is when I copy canvas element from the top, html inserts well, but it is not drawn in some reasons.
Please, watch code here http://jsfiddle.net/InsideZ/MuGnv/2/. Code was written for Google Chrome.
EDIT:
I made a few small tweaks here: http://jsfiddle.net/MuGnv/5/
Note the changes made to the drawImg function:
function drawImg(src, targetClass) {
$(targetClass).each(function() {
var ctx = $(this).get(0).getContext('2d');
var img = new Image();
img.src = src;
img.onload = function() {
ctx.drawImage(img, 0, 0);
};
});
}
Anytime a drop event is handled, the images are drawn again. This was the missing component as the image was only being drawn once.