Draw multiple image's on canvas with each image rotated - javascript

I'm new into HTML5 programming and I wanted to know how to rotate each image when it is added into canvas. Should each of them be placed into a canvas and then rotated? If so how can i add multiple canvas into a single canvas context.
Fiddle : http://jsfiddle.net/G7ehG/
Code
function loadImages(sources, callback) {
var images = {};
var loadedImages = 0;
var numImages = 0;
// get num of sources
for(var src in sources) {
numImages++;
}
for(var src in sources) {
images[src] = new Image();
images[src].onload = function() {
if(++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = sources[src];
}
}
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var sources = {
image1: 'http://farm3.static.flickr.com/2666/3686946460_0acfa289fa_m.jpg',
image2: 'http://farm4.static.flickr.com/3611/3686140905_cbf9824a49_m.jpg'
};
loadImages(sources, function(images) {
context.drawImage(images.image1, 100, 30, 200, 137);
context.drawImage(images.image2, 350, 55, 93, 104);
});

In your comment you mentioned that you know about context.rotate, but you don't want the context to stay rotated. That's not a problem at all. First, calling context.rotate only affects things which are drawn afterwards. Anything drawn before will stay were it was. Second, it can be easily reversed after drawing.
use context.save() to create a snapshot of all current context settings, including current rotation.
use context.rotate(angle) and draw your image. The angle is in Radian. That means a full 360° circle is Math.PI * 2. The point the image will be is rotated around is the current origin of the canvas (0:0). When you want to rotate the image around its center, use context.translate(x, y) to set the origin to where you want the center of the image to be, then rotate, and then draw the image at the coordinates -img.width/ 2, -img.height / 2
use context.restore() to return to your snapshot. Rotation and translation will now be like they were before.
Here is an example function which draws an image rotated by 45° at the coordinates 100,100:
function drawRotated(image, context) {
context.save();
context.translate(100, 100);
context.rotate(Math.PI / 4);
context.drawImage(image, -image.width / 2, -image.height / 2);
context.restore();
}

Related

Safari incorrectly drawing shadows on SVGs via HTML5 Canvas

I'm using HTML5 canvas in a project and occasionally need to draw drop shadows on SVGs within a canvas. I've noticed that, compared to Chrome, Safari does two things incorrectly when doing this:
Safari draws a shadow on each individual shape within an SVG
Safari crops off parts of the shadow that go beyond the SVG's bounds
These issues can be illustrated by the following code:
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.shadowOffsetX = 10;
context.shadowOffsetY = 10;
context.shadowColor = 'red'
var image = new Image();
image.src = 'https://storage.googleapis.com/card-conjurer/img/manaSymbols/0.svg';
image.onload = function() {
context.drawImage(image, 10, 10, 100, 100);
}
<canvas id='canvas'></canvas>
I can't embed images yet, but here are some links to images that illustrate the problem:
SVG Shadows with Google Chrome
SVG Shadows with Safari
(they are screenshots of the code above)
The results from Safari are... quite ugly, as you can see. Is there a way make Safari to render SVGs with shadows on HTML5 canvas like Chrome does?
Any help would be greatly appreciated.
Thanks so much for your time!
That's a bug, you should report it to webkit's bug-tracker.
Though you can workaround it by first drawing the image on a second canvas just to rasterize that svg image and use that canvas as source for the shadowing:
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
var image = new Image();
image.src = 'https://storage.googleapis.com/card-conjurer/img/manaSymbols/0.svg';
image.onload = function() {
const off = canvas.cloneNode();
off.getContext('2d').drawImage(image, 10, 10, 100, 100);
context.shadowOffsetX = 10;
context.shadowOffsetY = 10;
context.shadowColor = 'red';
context.drawImage(off, 0, 0);
}
<canvas id='canvas'></canvas>
In order to use a single canvas, we need to use an offset trick, but it's not always easy to do since it requires knowing clearly the position of our drawing:
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
var image = new Image();
image.src = 'https://storage.googleapis.com/card-conjurer/img/manaSymbols/0.svg';
image.onload = function() {
// first pass without shadow
context.drawImage(image, 10, 10, 100, 100);
// set shadow offsets to the position in page of bottom-right corner
context.shadowOffsetX = 10 + 110;
context.shadowOffsetY = 10 + 110;
context.shadowColor = 'red';
// draw behind
context.globalCompositeOperation = "destination-over";
// draw with inverse offset, so that the image is not visible
// but the shadow is in-screen
context.drawImage(canvas, -110, -110);
}
<canvas id='canvas'></canvas>

Filling canvas shapes with multiple different images in a grid

I am creating a hexagonal grid using canvas, and I'm trying to fill each tile with a specific pattern from an image. The following code is what I'm working with.
The end result is a hexagonal grid that has tiles patterned all with the same image... and it shouldn't be. I think what is happening is that it's creating an overlay for the pattern for every tile, but that image basically covers all tiles... and I only ever end up seeing that last image called.
I was under the impression that my fill() was only filling that small hexagon shape... and not all of them. How can I make this so each individual hex shape can have it's own image?
This code is run in a for loop to create the grid. This is my drawHex() method. I can't imagine that I need a whole new canvas for each tile to make this happen.
var numberOfSides = 6,
size = hex.properties.radius,
Xcenter = hexObj.x + (hex.properties.width / 2),
Ycenter = hexObj.y + (hex.properties.height / 2);
var img = new Image();
if (hexObj.t == "grassland"){
img.src = "/static/grass.jpg";
}else{
img.src = "/static/mountain.jpg";
}
var pattern = context.createPattern(img, "repeat");
context.fillStyle = pattern;
context.beginPath();
context.moveTo (Xcenter + size * Math.cos(0), Ycenter + size * Math.sin(0));
for (var i = 1; i <= numberOfSides;i += 1) {
context.lineTo (Xcenter + size * Math.cos(i * 2 * Math.PI / numberOfSides), Ycenter + size * Math.sin(i * 2 * Math.PI / numberOfSides));
}
context.fill();
context.closePath();
context.stroke();
First important thing is to understand how image loading is working. This is an asynchronous operation which means that you have to wait for the image to load before continue.
This sort of will dictate the strategy as setting image source each time you want to change an image will also force you to handle the asynchronous aspect of it will the delays and so forth. Also, if the image(s) hasn't been loading at the time of creating a pattern the style will fail to render.
So a better approach is to preload all the images (or a single sprite-sheet) that you will use. Then hold on to them in memory (this is usually not a problem today if the images aren't gigantic).
Bundling tile
You can then store several CanvasPattern objects referencing each image. One way is to build a tile object holding all information about that tile incl. its pattern.
For example:
function Tile(ctx, x, y, radius, img) {
this.pattern = ctx.createPattern(img, "repeat");
// store other properties here like x, y, radius etc.
}
Tile.prototype.render = function(ctx) {
ctx.beginPath();
// create hex shape here
ctx.fillStyle = this.pattern;
ctx.fill();
}
Now you can create a Tile object and hold it in an array (or a mother object):
var tiles = [];
tiles.push(new Tile(ctx, x1, y1, radius, img1); // img1 has loaded (onload)
tiles.push(new Tile(ctx, x2, y2, radius, img2); // img2 has also loaded (onload)
// etc.
Then simply render them at once like:
tiles.forEach(function(tile) { tile.render(ctx) });
Loading images
You will need an image loader to load all images. The drawback is that the user has to wait for the images to load unless you have a front that can occupy the user meanwhile the images are loading in the background.
A loader doesn't have to be complicated, but for production you would want to handle errors (onerror/onabort). This example will call the function start() when all images has loaded:
var images = [];
var urls = ["//image1.jpg", "//image2.jpg", ...];
var count = urls.length;
function handler() {
if (!--count) start();
}
urls.forEach(function(url) {
var img = new Image;
images.push(img);
img.onload = handler;
//img.onerror = ... // handler for error and abort here
//img.onabort = ...
img.src = url;
})
A small demo
var images = [], urls = ["//i.imgur.com/DAg71N5.jpg?1", "//i.imgur.com/ZO3XQpj.jpg?1"],
tiles = [], count = urls.length, ctx = c.getContext("2d");
function handler() {if (!--count) start()}
function Tile(ctx, x, y, radius, img) {
this.pattern = ctx.createPattern(img, "repeat");
this.x = x;
this.y = y;
this.radius = radius;
}
Tile.prototype.render = function(ctx) {
ctx.beginPath();
for(var i = 0; i < Math.PI*2; i += Math.PI/3)
ctx.lineTo(this.x + Math.cos(i) * this.radius, this.y + Math.sin(i) * this.radius);
ctx.fillStyle = this.pattern;
ctx.fill();
}
urls.forEach(function(url) {
var img = new Image;
images.push(img);
img.onload = handler;
img.src = url;
});
function start() {
tiles.push(new Tile(ctx, 50, 50, 50, images[0]));
tiles.push(new Tile(ctx, 130, 95, 50, images[1]));
tiles.forEach(function(tile) { tile.render(ctx) });
}
<canvas id=c></canvas>
Tips
Hex shape can be drawn this way:
for(var i = 0; i < Math.PI*2; i += Math.PI/3)
ctx.lineTo(this.x + Math.cos(i) * this.radius, this.y + Math.sin(i) * this.radius);
Note that this requires beginPath(). This allows us to pass on the moveTo() as the first lineTo() on the new path will move the path-cursor to its start point.
To keep patterns relative to the shape you can use translate() on the context before drawing them relative to (0,0) which also simplify the hex drawing:
ctx.translate(this.x, this.y);
ctx.moveTo(this.radius, 0);
for(var i = 0; i < Math.PI*2; i += Math.PI/3)
ctx.lineTo(Math.cos(i) * this.radius, Math.sin(i) * this.radius);
// cancel transforms here if needed
In newer browsers you can use setTransform() on the pattern itself. This is not supported in all browsers so be careful..
Mini-update For reuse purposes you can consider creating a pattern outside the object as part of the loading process, so that you only use a reference to the pattern for each tile.
The specs are a little unclear on what patterns do to images copy-wise. The only requirement is that changes to the image source must not affect the pattern after it has been created, which may or may not mean the image is copied internally, always or just when the condition for it demands it:
Modifying the image used when creating a CanvasPattern object after
calling the createPattern() method must not affect the pattern(s)
rendered by the CanvasPattern object.
In any case, there should be enough meat in the examples above to give you a basis and understanding to how to attack the problem. Modify as needed!

CamanJS revert and rotate fix

If you use the rotation plugin in CamanJS there is an issue when you are trying to revert changes. Caman is only implemented in a way that is working good when you crop or resize your image, but not when you rotate it. When you revert and the image is rotated the image reloads distorted, because it doesn't take under consideration that the canvas has rotated and changed size. Also the imageData.data of the canvas are different now. So I think i fixxed it by looking how he implemented the resize. Basicaly what I did (and he does too) is:
Create a canvas in the initial state
Update his pixelData from the initialState
create a new canvas
Rotate him with the initial image
get the ImageData and rerender them
So what I added. I needed to know how many angles was the image rotated so I can get the correct imageData when rotate the new canvas (step 4).
this.angle=0; //added it in the constructor
I also added a new boolean in the constructor to tell me if canvas was rotated
this.rotated = false;
In the rotated plugin:
Caman.Plugin.register("rotate", function(degrees) {
//....
//....
//....
this.angle += degrees;
this.rotated = true;
return this.replaceCanvas(canvas);
}
and on the originalVisiblePixels prototype:
else if (this.rotated){
canvas = document.createElement('canvas');//Canvas for initial state
canvas.width = this.originalWidth; //give it the original width
canvas.height = this.originalHeight; //and original height
ctx = canvas.getContext('2d');
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
pixelData = imageData.data;//get the pixelData (length equal to those of initial canvas
_ref = this.originalPixelData; //use it as a reference array
for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) {
pixel = _ref[i];
pixelData[i] = pixel; //give pixelData the initial pixels
}
ctx.putImageData(imageData, 0, 0); //put it back on our canvas
rotatedCanvas = document.createElement('canvas'); //canvas to rotate from initial
rotatedCtx = rotatedCanvas.getContext('2d');
rotatedCanvas.width = this.canvas.width;//Our canvas was already rotated so it has been replaced. Caman's canvas attribute is allready rotated, So use that width
rotatedCanvas.height = this.canvas.height; //the same
x = rotatedCanvas.width / 2; //for translating
y = rotatedCanvas.width / 2; //same
rotatedCtx.save();
rotatedCtx.translate(x, y);
rotatedCtx.rotate(this.angle * Math.PI / 180); //rotation based on the total angle
rotatedCtx.drawImage(canvas, -canvas.width / 2, -canvas.height / 2, canvas.width, canvas.height); //put the image back on canvas
rotatedCtx.restore(); //restore it
pixelData = rotatedCtx.getImageData(0, 0, rotatedCanvas.width, rotatedCanvas.height).data; //get the pixelData back
width = rotatedCanvas.width; //used for returning the pixels in revert function
}
You also need to add some resets in the reset prototype function. Basicaly reset angle and rotated
Caman.prototype.reset = function() {
//....
//....
this.angle = 0;
this.rotated = false;
}
and that's it.
I used it and works so far. What do you think?Hope it helps
Thanks for this, it worked after one slight change.
in the else if statement inside the originalVisiblePixels prototype I changed:
x = rotatedCanvas.width / 2; //for translating
y = rotatedCanvas.width / 2; //same
to:
x = rotatedCanvas.width / 2; //for translating
y = rotatedCanvas.height/ 2; //same
before this change my images where being cut.

Drawing an arc on an image inside a canvas

I am trying to clip and display a very large image inside a canvas div.
Using basic calculations and drawImage I managed to clip the image around the pixel I want and display the clipped image.
An example is here on JSFiddle (displaying image arround eye of the person)
I would like to add an arc which will be over the image around the pixel (the sx, sy pixel I use in the example in drawImage), how should I adjust the coordinates ?
var canvas = document.getElementById('test-canvas');
canvas.width = 500;
canvas.height = 285;
var context = canvas.getContext('2d');
var imageObj = new Image();
imageObj.onload = function () {
//context.drawImage(img,sx,sy,swidth,sheight,x,y,width,height);
context.drawImage(imageObj, 1324 - 250, 1228 - 142.5, 500, 285, 0, 0, 500, 285);
};
imageObj.src = "http://upload.wikimedia.org/wikipedia/en/b/b3/Edvard_Munch_-_Self-Portrait_-_Google_Art_Project.jpg";
An arc is part of a path, which can be either filled or stroke. In order to get your desired result, you need to move to a point on your circle*, create the arc, and then use stroke() (fiddle):
function strokeCircle(ctx, midx, midy, radius){
ctx.moveTo(midx + radius, midy);
ctx.arc(midx, midy, radius, 0, 2 * Math.PI, false);
ctx.stroke();
}
imageObj.onload = function () {
context.drawImage(imageObj, 1324 - 250, 1228 - 142.5, 500, 285, 0, 0, 500, 285);
strokeCircle(context, 250, 142.5, 30);
};
* The correct coordinate depends on your polar coordinates used for the circle. If you draw from 0 to Math.PI, you need to start on the right-most point.

Any javascript method for canvas image cover detection?

I'm developing following case.
Html canvas created by JS.
There are a star image(loaded from png with alpha=0 background) and a diamond image(also loaded from png with alpha=0 background) on canvas.
diamond image is moving toward the star image.
when diamond image is completely behind the star image, like showing star image only and diamond image is completely behind the star image, alert("Hidden");
if more than one pixel of the diamond is shown, alert should not appear.
Since the alpha value of the background of the star is 0, which means star is not a rectangle, it is difficult to detect whether the star image is fully covering the diamond image.
Is there any library or way to detect whether an image is fully covered by other?
Or, does any one know the name of this algorithm so that I can implement in JS?
Thanks for any help!
For objects with unknown shape we can check if object is behind by using pixel check.
Here is a full example on how to do this:
ONLINE DEMO HERE
(GameAlchemist provided a modified version here)
/// basic allocations
var ctx = demo.getContext('2d'),
os = document.createElement('canvas'),
octx = os.getContext('2d'),
w = os.width = demo.width,
h = os.height = demo.height,
/// the images
urlD = 'http://i.imgur.com/U72xIMZ.png',
urlS = 'http://i.imgur.com/n5rgo11.png',
imgD = new Image(),
imgS = new Image(),
cnt = 2,
/// check region (optimized)
rect = [140, 140, 180, 60];
/// load images and when ready, start show
imgD.crossOrigin = imgS.crossOrigin = 'anonymous';
imgD.onload = imgS.onload = function() {
cnt--;
if (cnt === 0) start();
}
imgD.src = urlD;
imgS.src = urlS;
The main function checks the pixels within the region defined above. To optimize we can narrow down the search area. If you need to check if image is visible on the other size the region is simply extended to check that area as well.
The function compares an off-screen canvas with just the foremost image drawn against the "live" canvas where both background and foreground are drawn.
If live canvas = off-screen canvas that means the background image is not visible.
function start() {
octx.drawImage(imgS, (w - imgS.width) * 0.5, 20);
var x = -50,
buffer1 = octx.getImageData(rect[0], rect[1], rect[2], rect[3]).data,
len = buffer1.length;
loop();
function loop() {
ctx.clearRect(0, 0, w, h);
ctx.drawImage(imgD, x, 130);
ctx.drawImage(imgS, (w - imgS.width) * 0.5, 20);
if (compare() === true) {
info.innerHTML = 'Object is behind!';
return;
}
x += 2;
if (x < w) requestAnimationFrame(loop);
}
function compare() {
var buffer2 = ctx.getImageData(rect[0], rect[1], rect[2], rect[3]).data,
i = len - 1;
while(i--) {
if (buffer1[i] !== buffer2[i]) return false
}
return true;
}
}

Categories

Resources