I'm trying to flip/mirror an image as I paint it on an HTML canvas; I found a game tutorial showing a sprite sheet per direction a character has to face, but this doesn't seem quite right to me. Especially since each frame has a different size.
What would be the best technique to reach this goal?
I tried to call the setScale(-1, 1); on my canvas with no success. Maybe that isn't meant for this.
Thanks
You can do this by transforming the context with myContext.scale(-1,1) before drawing your image, however
This is going to slow down your game. It's a better idea to have a separate, reversed sprite.
You need to set the scale of the canvas as well as inverting the width.
drawToCanvas : function(v, context, width, height){
context.save();
context.scale(-1, 1);
context.drawImage(v, 0, 0, width*-1, height);
context.restore();
}
There are probably some performance issues with this but for me that was not an issue.
If you just flip it horizontally it will get off of bounds... so use translate to adjust its position:
ctx.translate(canvas.width, 0);
ctx.scale(-1, 1);
ctx.drawImage(img, 0, 0);
For a shorter code you can remove the translate and use the image size as negative offset in the second parameter of the drawImage (x coordinate) instead:
ctx.scale(-1, 1);
ctx.drawImage(img, canvas.width * -1, 0);
If you want to restore the context later, add save/restore before and after it all:
ctx.save();
ctx.scale(-1, 1);
ctx.drawImage(img, canvas.width * -1, 0);
ctx.restore();
You don't need to redraw the entire image when creating a reflection. An original reflection simply shows the bottom part of the image. This way you are redrawing a smaller part of the image which provides better performance and also you don't need to create linear gradient to hide the lower part of the image (since you never draw it).
var img = new Image();
img.src = "//vignette2.wikia.nocookie.net/tomandjerryfan/images/9/99/Jerry_Mouse.jpg/revision/latest?cb=20110522075610";
img.onload = function() {
var thumbWidth = 250;
var REFLECTION_HEIGHT = 50;
var c = document.getElementById("output");
var ctx = c.getContext("2d");
var x = 1;
var y = 1;
//draw the original image
ctx.drawImage(img, x, y, thumbWidth, thumbWidth);
ctx.save();
//translate to a point from where we want to redraw the new image
ctx.translate(0, y + thumbWidth + REFLECTION_HEIGHT + 10);
ctx.scale(1, -1);
ctx.globalAlpha = 0.25;
//redraw only bottom part of the image
//g.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
ctx.drawImage(img, 0, img.height - REFLECTION_HEIGHT, img.width, REFLECTION_HEIGHT, x, y, thumbWidth, REFLECTION_HEIGHT);
// Revert transform and scale
ctx.restore();
};
body {
background-color: #FFF;
text-align: center;
padding-top: 10px;
}
<canvas id="output" width="500" height="500"></canvas>
Related
I'm trying to rotate a image using javascript and it should be simple but I can't figure how to draw the image rotated at specific coordinate on the canvas.
Here's the code I found and am trying to use:
ctx.save();
// Translate to the center point of our image
ctx.translate(selectedImg.width * 0.5, selectedImg.height * 0.5);
// Perform the rotation
ctx.rotate( rotAngle * 0.01745 );
// Translate back to the top left of our image
ctx.translate(-selectedImg.width * 0.5, -selectedImg.height * 0.5);
// Finally we draw the image
ctx.drawImage(selectedImg, 0, 0);
// And restore the context ready for the next loop
ctx.restore();
it just rotates the image on top-left corner. how can I draw the image to let's say bottom-right?
As it is documented on MSDN.
The function drawImage has three signatures.
void ctx.drawImage(image, dx, dy);
void ctx.drawImage(image, dx, dy, dWidth, dHeight);
void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
Where d stands for destination and s stands for source.
The solution here should be calculating the image resolution and canvas resolution and make sure to feet the image inside the canvas, then calculate the x,y axes where we should to draw the image.
ctx.save();
// Translate to the center point of our image
ctx.translate(selectedImg.width * 0.5, selectedImg.height * 0.5);
// Perform the rotation
ctx.rotate(rotAngle * 0.01745);
// Translate back to the top left of our image
ctx.translate(-selectedImg.width * 0.5, -selectedImg.height * 0.5);
// Finally we calculate the destination and draw the image
var selectedImgWidth = selectedImg.width;
var selectedImgHeight = selectedImg.height;
var xOffset = selectedImgWidth < canvas.width ? (canvas.width - selectedImgWidth) : 0;
var yOffset = selectedImgHeight < canvas.height ? (canvas.height - selectedImgHeight) : 0;
ctx.drawImage(selectedImg, xOffset, yOffset);
// instead of ctx.drawImage(selectedImg, 0, 0);
// And restore the context ready for the next loop
ctx.restore();
I hope that resolve your issue.
Edit 1: Chenged destination target
This function works for me:
CanvasRenderingContext2D.prototype.drawImageRotated = function (img,x,y,angle)
{
this.save();
var cposX = x + img.width / 2;
var cposY = y + img.height / 2;
this.translate(cposX,cposY); // move canvas center
this.rotate(degToRad(angle));
this.translate(-cposX,-cposY);
this.drawImage(img, x, y);
this.restore();
}
I'm using node-canvas and I was wonder how style an imported image in canvas similar to how you would an image in CSS.
For example, how would I crop a square image in canvas to a circle. In CSS, all you need to do is set border radius to 50%.
Well obviously you cannot use CSS in this case since CSS is applied to the DOM and not the the pixel based content of a Canvas element.
However the Canvas element has its own set of draw functions which allow to you replicate or at least approximate CSS rules.
Since you mentioned cropping an image to a circle I'll focus on this example. To achieve this effect you want to specify a clipping region before drawing the image. Every pixel outside of the clipped region will not be drawn. Effectively this will crop the image to the clipped region.
In code:
// Retrieve canvas and get context
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
// Save the context so we can undo the clipping region at a later time
context.save();
// Define the clipping region as an 360 degrees arc at point x and y
context.beginPath();
context.arc(x, y, radius, 0, 2 * Math.PI, false);
// Clip!
context.clip();
// Draw the image at imageX, imageY.
context.drawImage(image, imageX, imageY);
// Restore context to undo the clipping
context.restore();
I'd advice taking a look at this page to give you an idea of what you can do with the Canvas element and the 2D rendering context.
I don't know if this would work in node, However you can do this with canvas;
The simplest way of doing it is using, as you intended, border-radius:
canvas{border-radius:50%;}
An other way of doing it is by using the ctx.clip() method.
let canvas = document.querySelector("canvas");
let ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.arc(125,120,100,0,2*Math.PI);
// you clip the context
ctx.clip();
let img = document.querySelector("#testImg");
ctx.drawImage(img, 0, 20);
<canvas width="250" height="240" >
<img id="testImg" src="theImage.jpg">
</canvas>
Yet an other way of doing it is by using ctx.globalCompositeOperation = "destination-atop"in this way:
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
let cw = canvas.width = 400,
cx = cw / 2;
let ch = canvas.height = 400,
cy = ch / 2;
ctx.globalCompositeOperation = "destination-atop";
let img = document.querySelector("#testImg");
ctx.drawImage(img, 0, 0);
ctx.beginPath();
ctx.fillStyle = "#f00";
ctx.arc(cx, cx, 100, 0, 2 * Math.PI);
ctx.fill();
I've tried 3 ways to make it, but the effect doesn't looks well.
copy and fill image then make offset. The demo is
var ctx = canvas.getContext('2d'),
img = new Image;
img.onload = draw;
img.src = "http://i.stack.imgur.com/UFBxY.png";
function draw() {
var dArr = [-1,-1, 0,-1, 1,-1, -1,0, 1,0, -1,1, 0,1, 1,1], // offset array
s = 20, // thickness scale
i = 0, // iterator
x = 5, // final position
y = 5;
// draw images at offsets from the array scaled by s
for(; i < dArr.length; i += 2)
ctx.drawImage(img, x + dArr[i]*s, y + dArr[i+1]*s);
// fill with color
ctx.globalCompositeOperation = "source-in";
ctx.fillStyle = "red";
ctx.fillRect(0,0,canvas.width, canvas.height);
// draw original image in normal mode
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(img, x, y);
}
<canvas id=canvas width=500 height=500></canvas>
. When the outline width is large, the outline result will be wrong.
check the edge of image base on the Marching Squares algorithm. When the image shape is circle, the outline is with sawtooth. If make the outline more smoother, it won't fit the sharp shape like star.
copy and fill the image then scale it. When a image width is not equal with height, it doesn't work.
You can try with a math approach, without the offset array
var ctx = canvas.getContext('2d'),
img = new Image;
img.onload = draw;
img.src = "http://i.stack.imgur.com/UFBxY.png";
function draw() {
var s = 20, // thickness scale
x = 5, // final position
y = 5;
for (i=0; i < 360; i++)
ctx.drawImage(img, x + Math.sin(i) * s, y + Math.cos(i) * s);
// fill with color
ctx.globalCompositeOperation = "source-in";
ctx.fillStyle = "red";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// draw original image in normal mode
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(img, x, y);
}
<canvas id=canvas width=500 height=500></canvas>
My idea comes from the way we draw a circle using a string:
https://www.wikihow.com/Draw-a-Perfect-Circle-Using-a-Pin
Imagine that instead of a pencil at the end of the string we just have a shape
Here is a visual comparison of my approach and yours, also I'm showing a third approach scaling the image, there is really not a best one, it's just a matter of personal preference.
You could create a hybrid mode, if the hairline is important to you, get that portion of the image scaling it, then use a different way for the rest of the body.
I have two canvases. When I use drawImage() to copy from one canvas to the other, it is blurring the image slightly.
Why is this happening?
This seems like the kind of thing that occurs when theres some sub-pixel rounding. Maybe this is caused by the 45 degree 'rotation'?
Here is an example showing it occur:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var tempCanvas = document.getElementById("tmpCanvas");
var tempCtx = tempCanvas.getContext("2d");
canvas.width = canvas.height = 200;
tempCanvas.width = canvas.width;
tempCanvas.height = canvas.height;
// translate origins
ctx.translate(canvas.width / 2, canvas.height / 2);
tempCtx.translate(canvas.width / 2, canvas.height / 2);
// Create a red square
ctx.fillStyle = "rgba(255,0,0, 0.1)";
ctx.fillRect(-50, -50, 100, 100);
var angle = 0;
// Each draw we copy the current canvas to the tmpCanvas. Then copy it back to the original canvas.
function draw() {
angle += 45;
tempCtx.save();
tempCtx.rotate(angle * Math.PI / 180);
tempCtx.drawImage(
canvas,
0, // sourceX
0, // sourceY - note that source ignores translation. It's not a canvas context, so we choose top left corner of the canvas to start copying pixels.
canvas.width, // sourceWidth
canvas.height, // sourceHeight
-0.5 * canvas.width, // destinationX
-0.5 * canvas.height, // destinationY
canvas.width, // destinationWidth
canvas.height // destinationHeight
);
tempCtx.restore();
ctx.drawImage(
tempCanvas,
0,
0,
canvas.width,
canvas.height,
-0.5 * canvas.width,
-0.5 * canvas.height,
canvas.width,
canvas.height
);
// requestAnimationFrame(draw);
}
document.addEventListener("click", draw);
canvas {
border: 1px solid blue;
}
<p>
Click to trigger a "draw".
<br/>
A draw will do this:<br/>
1. rotate the bottom canvas by 45 degrees.<br/>
2. copy the top canvas to the bottom canvas<br/>
3. copy the bottom canvas to the top canvas<br/>
</p>
<br/>
<p>
Clicking repeatedly will "blur" the squares. Why?
</p>
<br/>
</p>
<canvas id="canvas"></canvas>
<canvas id="tmpCanvas"></canvas>
This is just antialiasing in action. When rotating twice by 45 degrees, the antialised edges fall slightly outside the original square, and these add up over time.
My goal is to make a canvas where you can draw on it, as the existing contents rotate about the origin.
You can make the drawing actions happen on the original canvas (apply the inverse rotation to the position of the mouse), and then repeatedly draw the original canvas rotated to the output canvas. Data flows in just one direction, from the original to the output, so there's no degradation.
I have a canvas layered over a div and I am trying to paint a rectangle at position 0, 0 on load and move it to another position x, y when needed. The x, y positions I need are returning perfectly and I am using the clearRect(0, 0, canvas.width, canvas.height) method to clear the canvas when I need to move and use the fillRect(x, y, width, height) again to redraw at those specific positions. However although the x, y positions are good and fillRect(..) is being called (I debugged in chrome) the rectangle is only being removed and painted when I repaint it at position 0, 0. Otherwise it just removes. At first I thought that it is being painted but maybe the layering of the div and canvas is being lost but I positioned it somewhere else and no this was not the problem.
This is the code I have maybe someone could kindly see something wrong in my code! Thanks
function removeCursor(connectionId) {
var canvas = document.getElementById(connectionId);
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
function paintCursor(x, y, connectionId, color) {
var canvas = document.getElementById(connectionId);
var context = canvas.getContext('2d');
context.fillStyle = color;
context.fillRect(x, y, 0.75, 5);
}
// the canvas is created on someone connecting to a specific page
if (someoneConnected) {
var canvas = document.createElement("canvas");
canvas.id = connectionId;
canvas.className = 'canvases';
canvas.style.zIndex = zindex;
zindex++;
var parentDiv = document.getElementById("editor-section");
parentDiv.appendChild(canvas);
paintCursor(0, 0, connectionId, color);
} else { // someone disconnected
var canvas = document.getElementById(connectionId);
canvas.parentNode.removeChild(canvas);
}
I call the methods removeCursor(connectionId) and paintCursor(x, y, connectionId, color) on a user event such as keypress and click. X, Y are the coordinates of the current selection.
Any ideas what's wrong here?
Why don't you re-factor to
function rePaintCursor(x, y, connectionId, color) {
var canvas = document.getElementById(connectionId);
var context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = color;
context.fillRect(x, y, 0.75, 5);
}
my guess is that, if x and y are correct, the execution order might be in your way.