HTML Canvas ShadowBlur Not Working After Rotating - javascript

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.

Related

Canvas rotate, image does not fill the canvas

I am implementing a photo editor, where the image can be zoomed in, rotated and cropped. I am having trouble rotating a rectangular image 90° to the right and to the left. When rotating, the image does not maintain its aspect ratio and does not fill the canvas.
The crop area is square shaped. Upon loading, the image looks like this:
Image initially upright
When I rotate the image 90° to the right, it looks like this:
Image rotated 90°
The image is now out of the cropped area bounds and the aspect ratio is off. I can't figure it out.
This is my code.
function rotate (img) {
var imageCropper = document.querySelector ('.imageCropper');
var cropArea = imageCropper.querySelector ('.cropArea');
var originalCanvas = imageCropper.querySelector ('canvas.original');
var resizedCanvas = imageCropper.querySelector ('canvas.resized');
var cropAreaRect = cropArea.getBoundingClientRect();
var resizedCtx = resizedCanvas.getContext ('2d');
rotationAngle = img.getAttribute ('data-rotation-angle');
if (Math.abs(rotationAngle) == 90 || Math.abs(rotationAngle == 270)) {
resizedCanvas.width = img.height;
resizedCanvas.height = img.width;
} else {
resizedCanvas.width = img.width;
resizedCanvas.height = img.height;
}
if (img.matches('canvas')) { // canvas element as opposed to Image object
// The source image is the resized canvas itself, and drawing it onto itself won't work.
// Set the original canvas as source so that we can draw it on the resized canvas.
img = originalCanvas;
}
//######################################################### --- CALCULATE CENTER POSITION
var centerPosX = Math.abs (resizedCanvas.offsetLeft) + (cropAreaRect.width / 2);
var centerPosY = Math.abs (resizedCanvas.offsetTop) + (cropAreaRect.height / 2);
//######################################################### --- ROTATE
resizedCtx.translate (Math.floor (centerPosX), Math.floor (centerPosY));
resizedCtx.rotate(rotationAngle * Math.PI / 180);
resizedCtx.translate (Math.floor (-centerPosX), Math.floor (-centerPosY));
resizedCtx.drawImage (
img,
0, 0,
img.width, img.height,
0, 0,
resizedCanvas.width, resizedCanvas.height
);
}
if (node.matches ('.imageCropper button.rotateRight')) {
node.addEventListener ('click', function () {
var imageCropper = node.closest ('.imageCropper');
var cropArea = imageCropper.querySelector ('.cropArea');
var resizedCanvas = imageCropper.querySelector ('canvas.resized');
// Get the rotation angle value in the data-rotation-angle attribute
var rotationAngle = parseFloat(resizedCanvas.getAttribute ('data-rotation-angle'));
// If a rotation angle value does not exist, it means the image is in the original position
// and this will be the first rotation, so we set the rotationAngle as 0.
if (! rotationAngle) {
var rotationAngle = 0;
}
// We increment the rotation angle by 90 degrees, which will rotate the image in a clockwise direction
rotationAngle += 90;
// When the rotation angle reaches 270 and the next decrementation would take it to 360,
// that means the image has done a full rotation and is back to its original position,
// so we set it to 0.
// We don't want values bigger than 360.
if (rotationAngle > 270) {
rotationAngle = 0;
}
// We set the data-rotation-angle with the new rotation angle value
resizedCanvas.setAttribute('data-rotation-angle', rotationAngle);
// Call the rotate() function
rotate (resizedCanvas);
}, false);
}

Html canvas rotate larger image with aspect ratio

I have a code sample where I am adjusting a large image to a smaller size with the canvas matching the size of the image. There is a rotate right functionality that will rotate the image inside of the canvas which will adjust the canvas size. I almost have it perfectly working but the image is perfect size only when original or upside down. I can't get the height to be right ratio when the image is rotated on left or right. Here is my jsfiddle and code which is modified code from a different fiddle. Image is just used as test.
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var canvasWidth = 400;
var canvasHeight;
var degrees=0;
var image=document.createElement("img");
image.onload=function(){
canvas.width = canvasWidth;
canvasHeight = 400 / (image.width / image.height);
canvas.height = canvasHeight;
ctx.drawImage(image,0,0,canvas.width,canvas.height);
}
image.src="https://www.magezinepublishing.com/equipment/images/equipment/Lumix-DMCFZ330-5824/highres/Panasonic-Lumix-FZ330-Wide-P1010001_1438873612.jpg";
$("#clockwise").click(function(){
degrees+=90
if (degrees >= 360) degrees = 0;
if (degrees === 0 || degrees === 180 ) {
canvas.width = canvasWidth;
canvas.height = canvasHeight;
}
else {
// swap
canvas.width = canvasHeight;
canvas.height = canvasWidth;
}
ctx.save();
// you want to rotate around center of canvas
ctx.translate(canvas.width/2,canvas.height/2);
ctx.rotate(degrees*Math.PI/180);
ctx.drawImage(image, -canvas.width*0.5, -canvas.height*0.5, canvas.width, canvas.height);
ctx.restore();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="canvas" ></canvas><br>
<button id="clockwise">Rotate right</button>
jsfiddle
Any help is appreciated.
Remember you rotate your image. You are changing the aspect ratio of the image when using the swapped canvas width/height in the image draw call. By your calculations, the original image, rotated, should already fit the canvas. The image you produce is also stretched because of this.
Use ctx.drawImage(image, -canvasWidth*0.5, -canvasHeight*0.5, canvasWidth, canvasHeight);.
Else said, you want to draw the same image, then rotate/translate it with canvas calls. But what you are doing is draw a different sized, stretched image when being rotated 90° or 270° and then rotate/translate that.
To rotate an image in 90deg steps.
Rather the use the translate and scale you can rotate the image by directly setting the tranformation matrix.
ctx.setTransform(a,b,c,d,e,f);
The arguments are as follows
a,b the vector describing the direction and size of a pixels x axis
c,d the vector describing the direction and size of a pixels y axis
e,f the coordinate of the origin (where 0,0 will be)
All these are in canvas pixel coordinates. The default is a = 1, b =0 x axis of a pixel is 1 pixel across, and 0 down, c = 0, d = 1 y axis 0 pixels across and one pixel down, e = 0, f = 0 origin at the top left.
To rotate the image 90 deg clockwise you want the xAxis to go down the canvas and the y axis to go from right to left. The origin is shifted to the top right of the canvas.
ctx.setTransform(
0,1, // x axis down
-1,0 // y axis from left to right
ctx.canvas.height, // origin x and y to top right
0,
)
ctx.drawimage(image,0,0);
As you are scaling by 0.5 this means the pixels will be half the size, and as you are drawing an image you want the origin so that the image fits the image.
// rotate image 90 deg and scale 0.5
ctx.setTransform(
0,0.5, // x axis down
-0.5,0 // y axis from left to right
image.height * 0.5, // origin x and y to top right
0,
)
ctx.drawimage(image,0,0);
You can do the same for each additional rotation
// rotate 180 scale 0.5
ctx.setTransform(-0.5,0,0,-0.5, image.width * 0.5,image.height * 0.5);
// rotate -90 scale 0.5
ctx.setTransform(0,-0.5,0.5,0, 0,image.width* 0.5);
The image dimensions for each rotation is as follows
// for 0deg and 180 deg rotation
canvas.width = image.width * 0.5;
canvas.width = image.height * 0.5;
// for 90deg and -90 deg rotation
canvas.width = image.height * 0.5;
canvas.width = image.width * 0.5;
To restore the transform to the default just set the transform to the identity matrix
ctx.setTransform(1,0,0,1,0,0);

Rotate a full size canvas image from a centerpoint without moving other canvas images

the example of the code below can be viewed here - http://dev.touch-akl.com/celebtrations/
What I have been trying to do is draw 2 images onto the canvas (glow and then flare. links for these images are below)
http://dev.touch-akl.com/celebtrations/wp-content/themes/beanstalk/img/flare.jpg
http://dev.touch-akl.com/celebtrations/wp-content/themes/beanstalk/img/blue-background.jpg
The goal is for the 'blue-background' image to sit on the canvas at the height and width of the container, and for the 'flare' image to be drawn ontop of this image with a blending mode and rotated with an animation to create a kind of twinkle effect.
My problem is that because the images I am using are rectangular when the 'flare' rotates at certain points you can see the edges of the layer underneath...
What I tried to do was find the diagonal width of the container using trigonometry and draw the 'flare' image at that width so that it always covered the whole canvas but alas you can still see the background layer at some points (but much less than before).
I need a way for the flare image to always cover the whole canvas, can anyone point me in the right direction please?
var banner = $('#banner'),
flare = document.getElementById('flare'),
glow = document.getElementById('glow'),
canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
blendMode = "multiply";
$window.load(function(){
_canvasWidth = banner.outerWidth(),
_canvasHeight = banner.outerHeight();
canvas.width = _canvasWidth;
canvas.height = _canvasHeight;
var _flareSum = (_canvasWidth * _canvasWidth) + (_canvasHeight * _canvasHeight);
_flareWidth = Math.sqrt(_flareSum);
_angle = 0;
setInterval(function() {
_angle = _angle +0.25;
// draw the bg without a blend mode
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(glow, 0, 0, _canvasWidth, _canvasHeight);
ctx.save();
// clear the canvas
// ctx.clearRect(0, 0, _canvasWidth, _canvasHeight);
ctx.translate( _canvasWidth/2, _canvasHeight); // move to center point
ctx.globalCompositeOperation = blendMode;
ctx.rotate(Math.PI / 180 * (_angle)); // 1/2 a degree
ctx.drawImage(flare, -_flareWidth/2, -_flareWidth/2, _flareWidth, _flareWidth); // redraw ia=mages
ctx.restore();
//console.log(_angle)
}, 1);
If I understand correctly, you need the shortest part of the flare to still cover the canvas when the flare is rotated at any angle.
Since you're only showing half the flare at any time, the shortest part of the flare is the distance from the flare center to the top of the flare:
var flareMinHeight = flare.height/2;
The longest length the flare must cover is from the flare rotation point to the top-left of the canvas.
var dx=rotationPointX;
var dy=rotationPointY;
var requiredLength=Math.sqrt(dx*dx+dy*dy);
So you will need to scale the flare to be at least the length computed above:
var minScale = requiredLength / flareMinHeight;

Issues in canvas image rotation

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();

Move HTML5 Canvas with a background image

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.

Categories

Resources