How to flip images horizontally with HTML5 - javascript

In IE, I can use:
<img src="http://example.com/image.png" style="filter:FlipH">
to implement an image flip horizontally.
Is there any way to flip horizontally in HTML5? (maybe by using canvas?)
thanks all :)

canvas = document.createElement('canvas');
canvasContext = canvas.getContext('2d');
canvasContext.translate(width, 0);
canvasContext.scale(-1, 1);
canvasContext.drawImage(image, 0, 0);
Here's a snippet from a sprite object being used for testing and it produces the results you seem to expect.
Here's another site with more details. http://andrew.hedges.name/widgets/dev/

You don't need HTML5, it can be done with CSS same as in IE:
-moz-transform: scale(-1, 1);
-webkit-transform: scale(-1, 1);
-o-transform: scale(-1, 1);
transform: scale(-1, 1);
filter: FlipH;

I like Eschers function above. I have made it a little neater and better. I have added flop (vertically) besides flip. Also a possibility to draw/rotate around the center of the image instead of top left. Finally, the function does not require all arguments. img, x and y are required but the rest are not.
If you were using something like context.drawImage(...), you can now just use drawImage(...) and add the rotate/flip/flop functionality explained here:
function drawImage(img, x, y, width, height, deg, flip, flop, center) {
context.save();
if(typeof width === "undefined") width = img.width;
if(typeof height === "undefined") height = img.height;
if(typeof center === "undefined") center = false;
// Set rotation point to center of image, instead of top/left
if(center) {
x -= width/2;
y -= height/2;
}
// Set the origin to the center of the image
context.translate(x + width/2, y + height/2);
// Rotate the canvas around the origin
var rad = 2 * Math.PI - deg * Math.PI / 180;
context.rotate(rad);
// Flip/flop the canvas
if(flip) flipScale = -1; else flipScale = 1;
if(flop) flopScale = -1; else flopScale = 1;
context.scale(flipScale, flopScale);
// Draw the image
context.drawImage(img, -width/2, -height/2, width, height);
context.restore();
}
Examples:
var myCanvas = document.getElementById("myCanvas");
var context = myCanvas.getContext("2d"); // i use context instead of ctx
var img = document.getElementById("myImage"); // your img reference here!
drawImage(img, 100, 100); // just draw it
drawImage(img, 100, 100, 200, 50); // draw it with width/height specified
drawImage(img, 100, 100, 200, 50, 45); // draw it at 45 degrees
drawImage(img, 100, 100, 200, 50, 0, true); // draw it flipped
drawImage(img, 100, 100, 200, 50, 0, false, true); // draw it flopped
drawImage(img, 100, 100, 200, 50, 0, true, true); // draw it flipflopped
drawImage(img, 100, 100, 200, 50, 45, true, true, true); // draw it flipflopped and 45 degrees rotated around the center of the image :-)

Mirror an image or rendering using the canvas.
Note. This can be done via CSS as well.
Mirroring
Here is a simple utility function that will mirror an image horizontally, vertically or both.
function mirrorImage(ctx, image, x = 0, y = 0, horizontal = false, vertical = false){
ctx.save(); // save the current canvas state
ctx.setTransform(
horizontal ? -1 : 1, 0, // set the direction of x axis
0, vertical ? -1 : 1, // set the direction of y axis
x + (horizontal ? image.width : 0), // set the x origin
y + (vertical ? image.height : 0) // set the y origin
);
ctx.drawImage(image,0,0);
ctx.restore(); // restore the state as it was when this function was called
}
Usage
mirrorImage(ctx, image, 0, 0, true, false); // horizontal mirror
mirrorImage(ctx, image, 0, 0, false, true); // vertical mirror
mirrorImage(ctx, image, 0, 0, true, true); // horizontal and vertical mirror
Drawable image.
Many times you will want to draw on images. I like to call them drawable images. To make an image drawable you convert it to a canvas
To convert an image to canvas.
function makeImageDrawable(image){
if(image.complete){ // ensure the image has loaded
var dImage = document.createElement("canvas"); // create a drawable image
dImage.width = image.naturalWidth; // set the resolution
dImage.height = image.naturalHeight;
dImage.style.width = image.style.width; // set the display size
dImage.style.height = image.style.height;
dImage.ctx = dImage.getContext("2d"); // get drawing API
// and add to image
// for possible later use
dImage.ctx.drawImage(image,0,0);
return dImage;
}
throw new ReferenceError("Image is not complete.");
}
Putting it all together
var dImage = makeImageDrawable(image); // convert DOM img to canvas
mirrorImage(dImage.ctx, dImage, 0, 0, false, true); // vertical flip
image.replaceWith(dImage); // replace the DOM image with the flipped image
More mirrors
If you wish to be able to mirror along an arbitrary line see the answer Mirror along line

One option is to horizontally flip the pixels of images stored in ImageData objects directly, e.g.
function flip_image (canvas) {
var context = canvas.getContext ('2d') ;
var imageData = context.getImageData (0, 0, canvas.width, canvas.height) ;
var imageFlip = new ImageData (canvas.width, canvas.height) ;
var Npel = imageData.data.length / 4 ;
for ( var kPel = 0 ; kPel < Npel ; kPel++ ) {
var kFlip = flip_index (kPel, canvas.width, canvas.height) ;
var offset = 4 * kPel ;
var offsetFlip = 4 * kFlip ;
imageFlip.data[offsetFlip + 0] = imageData.data[offset + 0] ;
imageFlip.data[offsetFlip + 1] = imageData.data[offset + 1] ;
imageFlip.data[offsetFlip + 2] = imageData.data[offset + 2] ;
imageFlip.data[offsetFlip + 3] = imageData.data[offset + 3] ;
}
var canvasFlip = document.createElement('canvas') ;
canvasFlip.setAttribute('width', width) ;
canvasFlip.setAttribute('height', height) ;
canvasFlip.getContext('2d').putImageData(imageFlip, 0, 0) ;
return canvasFlip ;
}
function flip_index (kPel, width, height) {
var i = Math.floor (kPel / width) ;
var j = kPel % width ;
var jFlip = width - j - 1 ;
var kFlip = i * width + jFlip ;
return kFlip ;
}

For anyone stumbling upon this.
If you want to do more complex drawing, the other scale-based answers don't all work. By 'complex' i mean situations where things are more dynamic, like for games.
The problem being that the location is also flipped. So if you want to draw a small image in the top left corner of the canvas and then flip it horizontally, it will relocate to the top right.
The fix is to translate to the center of where you want to draw the image, then scale, then translate back. Like so:
if (flipped) {
ctx.translate(x + width/2, y + width/2);
ctx.scale(-1, 1);
ctx.translate(-(x + width/2), -(y + width/2));
}
ctx.drawImage(img, x, y, width, height);
Here x and y are the location you want to draw the image, and width and height are the width and height you want to draw the image.

I came across this page, and no-one had quite written a function to do what I wanted, so here's mine. It draws scaled, rotated, and flipped images (I used this for rending DOM elements to canvas that have these such transforms applied).
var myCanvas = document.getElementById("myCanvas");
var ctx = myCanvas.getContext("2d");
var img = document.getElementById("myimage.jpg"); //or whatever
var deg = 13; //13 degrees rotation, for example
var flip = "true";
function drawImage(img, x, y, width, height, deg, flip){
//save current context before applying transformations
ctx.save();
//convert degrees to radians
if(flip == "true"){
var rad = deg * Math.PI / 180;
}else{
var rad = 2*Math.PI - deg * Math.PI / 180;
}
//set the origin to the center of the image
ctx.translate(x + width/2, y + height/2);
//rotate the canvas around the origin
ctx.rotate(rad);
if(flip == "true"){
//flip the canvas
ctx.scale(-1,1);
}
//draw the image
ctx.drawImage(img, -width/2, -height/2, width, height);
//restore the canvas
ctx.restore();
}

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

Change dimension of canvas depending on selected option

I was working on canvas and came across the Idea of changing dimensions of the cube. So, by using HTML5 Canvas I made up this cube which has two squares joined by the lines to make it look like a cube.
What I want is when I select a cube type from select the cube should automatically change itself depending on the length and width of the selected option. The height remains constant. Like if the I select the cube of 5x5 which is by default a cube but when the I select the option of 5x10 the width(front) should not be changed but the length(side) of the cube should expand, and vice versa if I select 10x5 my max option is 25x15. As you can see the canvas I created below is in pixels, first I need to convert these pixels into centimeters(cm) then centimeters to cubic meters.
The whole cube should be aligned in the fixed canvas area specified.
Here is fiddle
var canvas = document.querySelector('canvas');
canvas.width = 500;
canvas.height = 300;
var contxt = canvas.getContext('2d');
//squares
/*
contxt.fillRect(x, y, widht, height);
*/
contxt.strokeStyle = 'grey';
var fillRect = false;
contxt.fillStyle = 'rgba(0, 0, 0, 0.2)';
contxt.rect(80, 80, 100, 100);
contxt.rect(120, 40, 100, 100);
if (fillRect) {
contxt.fill();
}
contxt.stroke();
/*Lines
contxt.beginPath();
contxt.moveTo(x, y);
contxt.lineTo(300, 100);
*/
contxt.beginPath();
contxt.moveTo(80, 80);
contxt.lineTo(120, 40);
contxt.moveTo(180, 80);
contxt.lineTo(220, 40);
contxt.moveTo(80, 180);
contxt.lineTo(120, 140);
contxt.moveTo(180, 180);
contxt.lineTo(220, 140);
contxt.stroke();
canvas {
border: 1px solid #000;
}
select {
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<select>
<option>5x5</option>
<option>5x10</option>
<option>10x5</option>
</select>
<canvas></canvas>
Drawing the cube:
To generate a dynamic cube you would have to listen to an onChange event on the <select> element. Every time the selected option changes you would want to redraw your cube.
To redraw the cube you need to create a renderCube function which should take the new dimensions of the cube and as specified an offset for positioning. In this function you have to clear the previously drawn cube and redraw the new one with the given dimensions and offset.
Adding a transition effect:
As you can not apply css transitions to canvas elements you have to implement the transition yourself. You would have to create an animation function which would calculate the dimensions of the cube in the transition phase and rerender it to the screen on each frame.
An implementation of the resizable cube with a transition effect would be:
(if you prefer here is a fiddle too)
(if you do not need the transition effect check the fiddle before it has been implemented)
var canvas = document.querySelector('canvas');
canvas.width = 320;
canvas.height = 150;
var contxt = canvas.getContext('2d');
var currentHeight = 0, currentWidth = 0, currentDepth = 0, animationId = 0;
function renderCube(height, width, depth, offsetX, offsetY) {
currentHeight = height;
currentWidth = width;
currentDepth = depth;
// Clear possible existing cube
contxt.clearRect(0, 0, canvas.width, canvas.height);
contxt.beginPath();
// Calculate depth, width and height based on given input
depth = (depth * 10 * 0.8) / 2;
width = width * 10;
height = height * 10;
// Draw 2 squares to the canvas
contxt.strokeStyle = 'grey';
var fillRect = false;
contxt.fillStyle = 'rgba(0, 0, 0, 0.2)';
contxt.rect(offsetX, offsetY, width, height);
contxt.rect(offsetX + depth, offsetY - depth, width, height);
if (fillRect) {
contxt.fill();
}
contxt.stroke();
// An array which specifies where to draw the depth lines between the 2 rects
// The offset will be applied while drawing the lines
var depthLineCoordinates = [
// posX, posY, posX2, posY2
[0, 0, depth, -depth],
[width, 0, width + depth, -depth],
[0, height, depth, height - depth],
[width, height, width + depth, height - depth]
];
// Draw the depth lines to the canvas
depthLineCoordinates.forEach(function(element) {
contxt.moveTo(offsetX + element[0], offsetY + element[1]);
contxt.lineTo(offsetX + element[2], offsetY + element[3]);
});
contxt.stroke();
}
// As requested by OP an example of a transition to the cube
// The transitionDuration may be a double which specifies the transition duration in seconds
function renderCubeWithTransistion(height, width, depth, offsetX, offsetY, transitionDuration) {
var fps = 60;
var then = Date.now();
var startTime = then;
var finished = false;
var heightDifference = (height - currentHeight);
var widthDifference = (width - currentWidth);
var depthDifference = (depth - currentDepth);
// Get an "id" for the current animation to prevent multiple animations from running at the same time.
// Only the last recently started animation will be executed.
// If a new one should be run, the last one will get aborted.
var transitionStartMillis = (new Date()).getMilliseconds();
animationId = transitionStartMillis;
function animate() {
// Do not continue rendering the current animation if a new one has been started
if (transitionStartMillis != animationId) return;
// request another frame if animation has not been finished
if (!finished) requestAnimationFrame(animate);
// Control FPS
now = Date.now();
elapsed = now - then;
if (elapsed > (1000 / fps)) {
then = now - (elapsed % (1000 / fps));
// Calculate a linear transition effect
if (parseInt(currentHeight, 0) != parseInt(height, 0)) currentHeight += heightDifference / (transitionDuration * fps);
if (parseInt(currentWidth, 0) != parseInt(width, 0)) currentWidth += widthDifference / (transitionDuration * fps);
if (parseInt(currentDepth, 0) != parseInt(depth, 0)) currentDepth += depthDifference / (transitionDuration * fps);
// Render the cube
renderCube(currentHeight, currentWidth, currentDepth, offsetX, offsetY);
// Check if the current dimensions of the cube are equal to the specified dimensions of the cube
// If they are the same, finish the transition
if (parseInt(currentHeight, 0) === parseInt(height, 0) && parseInt(currentWidth, 0) === parseInt(width, 0) && parseInt(currentDepth, 0) === parseInt(depth, 0)) {
finished = true;
}
}
}
// Start the animation process
animate();
return true;
}
// Draw the cube initially with 5x5
renderCube(5, 5, 5, 80, 70);
// Add the onChange event listener to the select element
var cubeSizeSelector = document.getElementById('cubeSizeSelector');
cubeSizeSelector.onchange = function(e) {
var cubeSize = e.target.value.split('x');
renderCubeWithTransistion(5, parseInt(cubeSize[0], 0), parseInt(cubeSize[1], 0), 80, 70, 0.3);
}
canvas {
border: 1px solid #000;
}
select {
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"> </script>
<select id="cubeSizeSelector">
<option>5x5</option>
<option>5x10</option>
<option>10x5</option>
</select>
<canvas></canvas>
Drawing an extruded outline. Axonometric
Ideally you would create a generic axonometric renderer that given a floor plan renders the object to the canvas as needed.
You can then link the plan to a selection box and update the view when the selection has changed.
Best as a code example
The example below uses the object renderIsoPlan to render the shape.
Shapes are set via a plan. eg a box has a floor plan [[-1,-1],[1,-1],[1,1],[-1,1]] representing the 4 bottom corners.
The renderIsoPlan has the following properties
canvas The canvas that the shape is rendered to. Will not draw until this is set. renderIsoPlan will create a 2D context which will be the same if you have one already
height How far up the outline is projected.
style Canvas context style object eg {stokeStyle : "red", lineWidth : 2} draws 2 pixel with red lines.
plan Set of points for the floor. Points are moved to center automatically. eg [[0,-1],[1,1],[-1,1]] draws a triangle
scale Scale say no more
rotate Amount to rotate. If not 0 then projection is dimetric else it is trimetric.
centerY in unit size of canvas. ie 0.5 is center
centerX same as centerY
Call renderIsoPlan.refresh to draw
Note that you can not rotate the projection in the question as it visually appears to warp (change shape) thus if rotate is not 0 then a different projection is used.
Note the object is automatically centered around 0,0 use centerX, centerY to center in the view
setTimeout(start,0); // wait till Javascript parsed and executed
requestAnimationFrame(animate); // Animate checked at start so start anim
// named list of shapes
const boxes = {
box1By1 : {
plan : [[-1,-1],[1,-1],[1,1],[-1,1]],
scale : 35,
centerY : 0.75,
},
box1By2 : {
plan : [[-1,-2],[1,-2],[1,2],[-1,2]],
scale : 30,
centerY : 0.7,
},
box2By2 : {
plan : [[-2,-2],[2,-2],[2,2],[-2,2]],
scale : 25,
centerY : 0.7,
},
box2By1 : {
plan : [[-2,-1],[2,-1],[2,1],[-2,1]],
scale : 30,
centerY : 0.7,
},
box1By3 : {
plan : [[-1,-3],[1,-3],[1,3],[-1,3]],
scale : 22,
centerY : 0.67,
},
box1By4 :{
plan : [[-1,-4],[1,-4],[1,4],[-1,4]],
scale : 20,
centerY : 0.63,
},
lShape : {
plan : [[-2,-4],[0,-4],[0,2],[2,2],[2,4],[-2,4]],
scale : 20,
centerY : 0.65,
},
current : null,
}
// Sets the renderIsoPlan object to the current selection
function setShape(){
boxes.current = boxes[boxShape.value];
Object.assign(renderIsoPlan, boxes.current);
if (!animateCheckBox.checked) { renderIsoPlan.refresh() }
}
// When ready this is called
function start(){
renderIsoPlan.canvas = canvas;
renderIsoPlan.height = 2;
setShape();
renderIsoPlan.refresh();
}
// Add event listeners for checkbox and box selection
boxShape.addEventListener("change", setShape );
animateCheckBox.addEventListener("change",()=>{
if (animateCheckBox.checked) {
requestAnimationFrame(animate);
} else {
renderIsoPlan.rotate = 0;
setShape();
}
});
// Renders animated object
function animate(time){
if (animateCheckBox.checked) {
renderIsoPlan.rotate = time / 1000;
renderIsoPlan.refresh();
requestAnimationFrame(animate);
}
}
// Encasulate Axonometric render.
const renderIsoPlan = (() => {
var ctx,canvas,plan,cx,cy,w,h,scale,height, rotate;
height = 50;
scale = 10;
rotate = 0;
const style = {
strokeStyle : "#000",
lineWidth : 1,
lineJoin : "round",
lineCap : "round",
};
const depthScale = (2/3);
// Transforms then projects the point to 2D
function transProjPoint(p) {
const project = rotate !== 0 ? 0 : depthScale;
const xdx = Math.cos(rotate);
const xdy = Math.sin(rotate);
const y = p[0] * xdy + p[1] * xdx;
const x = p[0] * xdx - p[1] * xdy - y * project;
return [x,y * depthScale];
}
// draws the plan
function draw() {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0,0,w,h);
ctx.setTransform(scale, 0, 0, scale, cx, cy);
var i = plan.length;
ctx.beginPath();
while(i--){ ctx.lineTo(...transProjPoint(plan[i])) }
ctx.closePath();
i = plan.length;
ctx.translate(0,-height);
ctx.moveTo(...transProjPoint(plan[--i]))
while(i--){ ctx.lineTo(...transProjPoint(plan[i])) }
ctx.closePath();
i = plan.length;
while(i--){
const [x,y] = transProjPoint(plan[i]);
ctx.moveTo(x,y);
ctx.lineTo(x,y + height);
}
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.stroke();
}
// centers the plan view on coordinate 0,0
function centerPlan(plan){
var x = 0, y = 0;
for(const point of plan){
x += point[0];
y += point[1];
}
x /= plan.length;
y /= plan.length;
for(const point of plan){
point[0] -= x;
point[1] -= y;
}
return plan;
}
// Sets the style of the rendering
function setStyle(){
for(const key of Object.keys(style)){
if(ctx[key] !== undefined){
ctx[key] = style[key];
}
}
}
// define the interface
const API = {
// setters allow the use of Object.apply
set canvas(c) {
canvas = c;
ctx = canvas.getContext("2d");
w = canvas.width; // set width and height
h = canvas.height;
cx = w / 2 | 0; // get center
cy = h / 2 | 0; // move center down because plan is extruded up
},
set height(hh) { height = hh },
set style(s) { Object.assign(style,s) },
set plan(points) { plan = centerPlan([...points]) },
set scale(s) { scale = s },
set rotate(r) { rotate = r },
set centerY(c) { cy = c * h },
set centerX(c) { cx = c * w },
// getters not used in the demo
get height() { return height },
get style() { return style },
get plan() { return plan },
get scale() { return scale },
get rotate() { return r },
get centerY() { return cy / h },
get centerX() { return cx / w },
// Call this to refresh the view
refresh(){
if(ctx && plan){
ctx.save();
if(style){ setStyle() }
draw();
ctx.restore();
}
}
}
// return the interface
return API;
})();
canvas { border : 2px solid black; }
<select id="boxShape">
<option value = "box1By1">1 by 1</option>
<option value = "box1By2">1 by 2</option>
<option value = "box2By2">2 by 2</option>
<option value = "box2By1">2 by 1</option>
<option value = "box1By3">1 by 3</option>
<option value = "box1By4">1 by 4</option>
<option value = "lShape">L shape</option>
</select>
<input type="checkBox" id="animateCheckBox" checked=true>Animate</input><br>
<canvas id="canvas"></canvas>

How to draw one image multiple times in different direcitons?

I have an array like this, the idea is to output images in different directions and flip them vertically and horizonatly on a canvas by scaling.
[{
"pos":{
"x":411,
"y":401.5
},
"scale":{
"x":1,
"y":1
}
},{
"pos":{
"x":411,
"y":271.59625
},
"scale":{
"x":-1,
"y":1
}
}]
The problem is that I'm scaling the canvas instead of the images, the canvas is multiple times bigger than the images i'm placing on it.
images.forEach((image) => {
// center borde köars innan loopen egentligen
let pos = center(image.pos)
cc.save()
cc.scale(image.scale.x, image.scale.y)
cc.drawImage(window.video, pos.x, pos.y)
cc.restore()
})
How do I scale the image, called window.video, instead of the entire canvas?
To render a scaled image on the canvas.
function drawImage(image,x,y,scaleX,scaleY){
ctx.setTransform(scaleX, 0, 0, scaleY, x, y); // set scale and translation
ctx.drawImage(image,0,0);
}
// when done drawing images you need to reset the transformation back to default
ctx.setTransform(1,0,0,1,0,0); // set default transform
If you want the image flipped but still drawn at the same top left position use
function drawImage(image, x, y, scaleX, scaleY){
ctx.setTransform(scaleX, 0, 0, scaleY, x, y); // set scale and translation
x = scaleX < 0 ? -image.width : 0; // move drawing position if flipped
y = scaleY < 0 ? -image.height : 0;
ctx.drawImage(image, x, y);
}
And to draw about the center of the image
function drawImage(image, x, y, scaleX, scaleY){ // x y define center
ctx.setTransform(scaleX, 0, 0, scaleY, x, y); // set scale and translation
ctx.drawImage(image, -image.width / 2, -image.height / 2);
}
EDIT: As noted below, this doesn't work with negative values...
drawImage can take 2 extra arguments for sizing the image, so the following should work:
images.forEach((image) => {
// center borde köars innan loopen
let pos = center(image.pos)
cc.save()
cc.drawImage(window.video, pos.x, pos.y, window.video.width * image.scale.x, window.video.height * image.scale.y)
cc.restore()
})
Documentation: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Using_images

Reveal image on canvas with draw/strokes/paint

I want to achive the following:
Draw a bg-image to the canvas (once or if needed repeatedly)
The image should not be visible at the beginning
While i "paint" shapes to the canvas the bg-image should get visible where the shapes were drawn
The parts of the image that will be revealed shall be "painted" (like with a brush) so i want to use strokes.
What i tried:
- Do not clear the canvas
- Paint rects to the canvas with globalCompositeOperation = 'destination-in'
This works, the rectangles reveal the image but i need strokes
If i use strokes they are ignored with 'destination-in' while i see them with normal globalCompositeOperation.
Is this intended that the strokes are ignored? Is there a workaround like somehow converting the stroke/shape to a bitmap? Or do i have have to use two canvas elements?
In OpenGL i would first draw the image with its rgb values and with a = 0 and then only "paint" the alpha in.
You can solve it by these steps:
Set the image as a pattern
Set the pattern as fillStyle or strokeStyle
When you now fill/stroke your shapes the image will be revealed. Just make sure the initial image fits the area you want to reveal.
Example showing the principle, you should be able to adopt this to your needs:
var ctx = canvas.getContext("2d"),
img = new Image,
radius = 40;
img.onload = setup;
img.src = "http://i.imgur.com/bnAEEXq.jpg";
function setup() {
// set image as pattern for fillStyle
ctx.fillStyle = ctx.createPattern(this, "no-repeat");
// for demo only, reveals image while mousing over canvas
canvas.onmousemove = function(e) {
var r = this.getBoundingClientRect(),
x = e.clientX - r.left,
y = e.clientY - r.top;
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.arc(x, y, radius, 0, 2*Math.PI);
ctx.fill();
};
}
<canvas id=canvas width=900 height=600></canvas>
Hope this helps!
Alternative solution:
Put the image as a normal image on your website
add a canvas and use CSS positioning to place it right above the image
Fill the canvas with the color you use as the page background
have your paint tools erase the canvas when you draw. By the way, you can set context.globalCompositionOperation = 'destination-out' to turn all drawing operations into an eraser.
Here is an example. As you can see, the alpha properties of your tools are respected.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
//prepare canvas
ctx.fillStyle = '#ffffff'
ctx.fillRect(0, 0, 120, 120);
//prepare a 30% opacity eraser
ctx.globalCompositeOperation = 'destination-out';
ctx.lineWidth = 5;
ctx.strokeStyle = 'rgba(0, 0, 0, 0.3)';
// make random strokes around cursor while mouse moves
canvas.onmousemove = function(e) {
var rect = this.getBoundingClientRect();
var x = e.clientX - rect.left;
var y = e.clientY - rect.top;
ctx.beginPath();
ctx.moveTo(x + Math.random() * 33 - 16, y + Math.random() * 33 - 16);
ctx.lineTo(x + Math.random() * 33 - 16, y + Math.random() * 33 - 16);
ctx.stroke();
}
<span>Move your mouse:</span>
<div>
<img src='https://upload.wikimedia.org/wikipedia/commons/thumb/6/61/HTML5_logo_and_wordmark.svg/120px-HTML5_logo_and_wordmark.svg.png' style='position:absolute'>
<canvas id='canvas' width=120 height=120 style='position:absolute'></canvas>
</div>

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

Categories

Resources