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.
Related
How can I make a canvas for the video to fit perfectly in the area
See picture
...It can't be done with native Html5 Canvas transformations.
You are trying to transform the display Canvas content (== your video) into a non-parallelogram -- which is not possible with native Canvas transformations. The Canvas can only be reshaped into parallelograms.
But...
You can skew the canvas to approximate your desired display.
The gold parallelogram in this image can be done with native Canvas transformations
Example code an a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var angle=-Math.PI*.06;
var img=new Image();
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/billboard1.jpg";
function start(){
cw=canvas.width=img.width;
ch=canvas.height=img.height;
ctx.drawImage(img,0,0);
ctx.transform(1,Math.tan(angle),0,1,0,0);
ctx.strokeStyle='gold';
ctx.lineWidth=4;
ctx.strokeRect(333,135,275,150);
}
<canvas id="canvas" width=300 height=300></canvas>
Drawing process
Prepare the background image offline by removing the red area making an alpha channel out of it instead, then use it like this:
Draw the video to canvas with transformation applied matching closely the shape plus a little overlap
Draw the background (or parts of it after first draw) on top of the video. This will mask the edges of the video that doesn't fit perfectly.
Transformation
Offline process:
Plot the points of the (now) alpha area so you have the coordinates, either a triangle or a quadrilateral. Note: canvas only support affine transformations (2D/parallelograms) so triangle has to be used. For CSS you can use 3D transforms with basis in a quadrilateral shape.
Normalize the values
In action:
Scale the canvas and the normalized values to the desired size
Apply transform, draw video, draw background as overlay on top of video
And since most video is max 30 frames per second you can also throttle the drawing loop to half to leave more resources for other things (use requestAnimationFrame() with a toggle flag).
Alternative approach:
Prepare a video with everything setup as you want. It should compress well since the surrounding areas don't change (use a long key-frame interval), and in this case the background doesn't contain much details due to low depth-of-field which also helps.
I am working on a flash sound mixer application with multiple sound channels, and I am having trouble with the lights beside the volume knob.
Is there a way to hide just a part of an image?
On the image below, image-2 is on top of image-1 to create some kind of volume level indicator effect, and how much of image-2 is shown depends on the value of the volume.
image-url: http://s30.postimg.org/r3ow1g5bl/volume_lights_level.png
I've tried by just reducing the height of image-2, but it looks awful and distorted.
Is there something in flash that works closely the same as CSS's behavior.
example: I'll just make image-2 a background of a shape, and when I reduce the shape's height, the image-background does not get distorted or changes it's height as well.
By searching for solutions, I have come across the mask property, but I don't quite understand how it works, and most of the examples shown are images placed inside circles.
Is the mask property applicable in this situation?
I'm quite new to flash so I don't know a lot of things yet.
You can indeed use a mask.
How to programmatically create your mask
Put an occurrence of your image named myImage on the stage, and put over this occurrence a mask named myMask with the same dimensions. You can apply myMask mask to myImage using it's mask property like below:
Main Timeline
myImage.mask = myMask;
function mouseMoveHandler(e:MouseEvent):void {
myMask.height = myImage.y - e.stageY;
}
stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
You have just to adapt this code to your animation, in the function where you click your button.
I got it working now, many THANKS #VC.One. heres how I did it.
Imported img-2 to stage, converted it into symbol(type:Movie Clip), assigned instance name: img2_mc.
I created a new layer for the mask, drawn a rectangle using rectangle tool, converted it also to symbol(type:Movie Clip), assigned instance name: mask_mc.
Then applied the mask to img2_mc.
/* the code */
img2_mc.mask = mask_mc;
function onEnterFrame(event:Event):void{
var volumeKnob_y = volSliderKnobOn.y + 12; // adjust it to the center of the knob
mask_mc.height = volumeKnob_y;
}
I want to clip html5 canvas so that I can achieve drawing result as per following image.
I want to achieve clip path such that all drawing will be performed only in black area.
Method 1
Assuming the white areas are transparent and the black is non-transparent then you can use composite mode:
ctx.globalCompositeOperation = 'source-in';
... draw graphics on top - only solid color will be affected ...
ctx.globalCompositeOperation = 'source-over'; // reset to default mode
A demo fiddle for this here
Method 2
Another simpler approach is to simply fill the canvas with the graphics you need then use clearRect() for those areas you want transparent.
This operation is fairly fast so there shouldn't be any flashing (and in case you can trigger this operation with a single requestAnimationFrame call).
A demo fiddle with clearRect
A demo fiddle with clearRect + requestAnimationFrame
Just note that calling rAF makes the code asynchronous but the purpose of using it is that your draw operations are synchronized within a frame update so flicker will be removed (if you should for some reason get problem with that).
Method 3
Create rectangle regions surrounding the area you want to preserve by a series of call to rect(). The set that as a clipping mask by using clip().
This techniques works best if the non-clipped areas are in a certain order or else you would have to define a lot of regions.
Remember to translate canvas 0.5 pixel first and only use integer values for the rectangles.
Method 4
Parse the pixel buffer manually to fill in pixels in the areas fulfilling the requirements, for example non-transparent pixels only.
Just be aware of that this is probably the slowest approach, it's affected by CORS restrictions (in case you draw an external image onto the canvas first) and it's more tedious if you want to fill in shapes, images, gradients and so forth which in case you would probably prefer an off-screen canvas to copy from.
There are other ways using different composite modes and drawing order to achieve the same result but I leave it with this as it should cover most scenarios.
You can use layering to fill your need:
make a copy of your image with all black made transparent
draw the original image on the canvas
draw your desired shapes
draw the transparent image on top
A Demo: http://jsfiddle.net/m1erickson/dFRUf/
This function creates a temporary canvas with the color-range you specify made transparent:
function makeImageTransparentByColor(image,r1,r2,g1,g2,b1,b2){
// create a temporary canvas and draw the image on the canvas
var bk=document.createElement("canvas");
var bkCtx=bk.getContext("2d");
bk.width=image.width;
bk.height=image.height
bkCtx.drawImage(image,0,0);
// get the pixel array from the canvas
var imgData=bkCtx.getImageData(0,0,bk.width,bk.height);
var data=imgData.data;
// loop through each pixel and make every pixel transparent
// that is between r1-r2, g1-g2 and b1-b2
for(var i=0;i<data.length;i+=4){
var r=data[i];
var g=data[i+1];
var b=data[i+2]
if(r>=r1 && r<=r2 && g>=g1 && g<=g2 && b>=b1 && b<=b2){
data[i]=0;
data[i+1]=0;
data[i+2]=0;
data[i+3]=0;
}
}
// put the modified pixels back on the canvas
bkCtx.putImageData(imgData,0,0);
// return the canvas with transparent image
return(bk);
}
I am currently programming a little game using canvas. For the game I need some kind of fog which hides the most part of the map and only a small area around the player should be visible. Therfor I use a second canvas to overlay the one where the game takes place and fill it with a gradient (from transparent to black):
function drawFog(){
fogc.clearRect(0,0,700,600);
// Create gradient
var grd=fogc.createRadialGradient(player.getPosX(),player.getPosY(),0,player.getPosX(),player.getPosY(),100);
grd.addColorStop(0,"rgba(50,50,50,0)");
grd.addColorStop(1,"black");
// Fill with gradient
fogc.fillStyle=grd;
fogc.fillRect(0,0,700,600);
}
Unfortunatly this is causing huge perfomance problems since it will be redrawn for every frame.
I wanted to ask if there might be a better solution to achieve the same effect with a better performance.
Cache the gradient to an off-screen canvas then draw in the canvas with drawImage() instead:
Create an off-screen canvas the size of the fog
Draw in the gradient
Use off-screen canvas as an image when you need the fog.
This way the processing creating and calculating the gradient is eliminated. Drawing an image is basically a copy operation (there is a little bit more, but performance is very good).
function createFog(player) {
// Create off-screen canvas and gradient
var fogCanvas = document.createElement('canvas'),
ctx = fogCanvas.getContext('2d'),
grd = fogc.createRadialGradient(player.getPosX(),
player.getPosY(),
0,player.getPosX(),
player.getPosY(),100);
fogCanvas.width = 700;
fogCanvas.height = 700;
grd.addColorStop(0,"rgba(50,50,50,0)");
grd.addColorStop(1,"black");
// Fill with gradient
ctx.fillStyle = grd;
ctx.fillRect(0,0,700,600);
return fogCanvas;
}
Now you can simply draw in the canvas returned from the above function instead of creating the gradient every time:
var fog = createFog(player);
ctx.drawImage(fog, x, y);
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.