In canvas I know that I can flip images horizontally and vertically by doing this:
vc.scale(-1, -1)
But is there some way to flip an image
Yes, you can accomplish this using the rotate() method of canvas :
var ctx = document.getElementById("myCanvas").getContext("2d");
var img = new Image();
img.src = "http://photos.the-scientist.com/articleImages/48000/48607-1-t.jpg";
img.onload = function() {
ctx.rotate(30 * Math.PI / 180);
ctx.translate(ctx.canvas.width / 2, ctx.canvas.height / 2);
ctx.scale(1, -1);
ctx.drawImage(img, -65, 65);
}
<canvas id="myCanvas" width="300" height="300">
Rotate image 30 degrees, flip image, rotate -30 degrees.
You can mirror along any line using the following function. It sets the transform so that you can render your scene as normal and it will be mirrored along the line.
function mirrorTransformLine(line){
var x1 = line.x1;
var y1 = line.y1;
var x2 = line.x2;
var y2 = line.y2;
if(x1 > x2){ // to save some messing about with signs make the line always from left to right
x2 = line.x1;
y2 = line.y1;
x1 = line.x2;
y1 = line.y2;
}
var x = x2-x1; // get the vector from line start to end
var y = y2-y1;
var ox = -x1; // get vector from line start to origin
var oy = -y1;
var len = Math.hypot(x,y); // get the length of the line
var nx = x / len; // normalise the line
var ny = y / len;
// We must find the mirrored origin
// get the unit distance along the line where the mirrored y axis intercepts
var u = (ox * x + oy * y)/(y * y + x * x);
var dx = u * len; // get the x dist of the mirrored origin
var dy = Math.hypot(x1 + x * u, y1 + y * u); // get the mirrored y axis distance from line
// the above code does not account for the direction of the origin. We don't know if its above or below the line
// we can get the cross product of the mirror line and the vector to the origin. This will give us the sign (direction) to the origin
dy *= Math.sign(ox * y - oy * x); // flip the y distance if needed
// calculate the the location of the mirrored origin
var mox = dx * nx - dy * ny + x1;
var moy = dx * ny + dy * nx + y1;
// Find the angle of the line to the x axis
// var cross = 1 * ny - 0 * nx; // cross product give the sin of the angle between the line and the x axis
// As the cross product is with 1,0 we can simplify
var ang = Math.asin(ny); // with ny the cross product
// now find the mirrored angle which is 2 times the angle to the x axis
// use that angle to get the new x axis
var axx = Math.cos(ang*2);
var axy = Math.sin(ang*2);
// this represents the x axis of the transform
// you would normally rotate it clockwise 90 for the y axis
// to mirror its anticlockwise
ctx.setTransform(axx,axy,axy,-axx,mox,moy);
}
Thus if you have a line at 30 deg
var line = {
x1 : 100,
y1 : 100,
x2 : 100 + Math.cos((1/6)* Math.PI), // (1/6) *PI is 30 deg
y2 : 100 + Math.sin((1/6)* Math.PI),
}
// draw the scene
ctx.fillRect(50,50,50,100);
mirrorTransformLine(line); // create the mirror transformation
ctx.fillRect(50,50,50,100); // draw the scene again this time its is mirrored
ctx.setTransform(1,0,0,1,0,0); // restore the transform to default
As a demo.
Use mouse to drag ends of red line to see it create the mirror transform. The scene is draw twice using the same coordinates. I have also clipped to the line so that the mirror does not overlap.
function mirrorTransformLine(line){
var x1 = line.x1;
var y1 = line.y1;
var x2 = line.x2;
var y2 = line.y2;
if(x1 > x2){ // to save some messing about with signs make the line always from left to right
x2 = line.x1;
y2 = line.y1;
x1 = line.x2;
y1 = line.y2;
}
var x = x2-x1; // get the vector from line start to end
var y = y2-y1;
var ox = -x1; // get vector from line start to origin
var oy = -y1;
var len = Math.hypot(x,y); // get the length of the line
var nx = x / len; // normalise the line
var ny = y / len;
// We must find the mirrored origin
// get the unit distance along the line where the mirrored y axis intercepts
var u = (ox * x + oy * y)/(y * y + x * x);
var dx = u * len; // get the x dist of the mirrored origin
var dy = Math.hypot(x1 + x * u, y1 + y * u); // get the mirrored y axis distance from line
// the above code does not acount for the direction of the origin. We dont know if its above or below the line
// we can get the cross product of the mirror line and the vector to the origin. This will give us the sign (direction) to the origin
dy *= Math.sign(ox * y - oy * x);
// calculate the the location of the mirrored origin
var mox = dx * nx - dy * ny + x1;
var moy = dx * ny + dy * nx + y1;
// Find the angle of the line to the x axis
// var cross = 1 * ny - 0 * nx; // cross product give the sin of the angle between the line and the x axis
// As the cross product is with 1,0 we can simplify
var ang = Math.asin(ny); // with ny the cross product
// now find the mirrored angle which is 2 time the angle to the x axis
// use that angle to get the new x axis
var axx = Math.cos(ang*2);
var axy = Math.sin(ang*2);
// this represents the x axis of the transform
// you would normally rotate it clockwise 90 for the y axis
// to mirror its anticlockwise
ctx.setTransform(axx,axy,axy,-axx,mox,moy);
}
function clipToLine(line){
var x =line.x2-line.x1; // get the vector from line start to end
var y =line.y2-line.y1;
var len = Math.hypot(x,y); // get the length of the line
var nx = x / len; // normalise the line
var ny = y / len;
// from 1000 px before start to 1000 px after end create dividing line
ctx.beginPath();
ctx.moveTo(line.x1 - nx * 1000, line.y1 - ny * 1000);
ctx.lineTo(line.x2 + nx * 1000, line.y2 + ny * 1000);
ctx.lineTo(line.x2 + nx * 1000 - ny * 1000, line.y2 + ny * 1000 + nx * 1000);
ctx.lineTo(line.x1 - nx * 1000 - ny * 1000, line.y1 - ny * 1000 + nx * 1000);
ctx.clip();
}
var line;
var onResize = function(){ // this is called at start
line = {
x1 : 10,
y1 : canvas.height /2,
x2 : canvas.width -10,
y2 : canvas.height /2,
};
ctx.font = Math.floor(canvas.height /10) + "px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
}
function drawLine(line,sCol,width){
ctx.strokeStyle = sCol;
ctx.lineWidth = width;
ctx.beginPath();
ctx.moveTo(line.x1,line.y1);
ctx.lineTo(line.x2,line.y2);
ctx.stroke();
}
function drawCircle(x,y,r,fCol,sCol,width){
ctx.fillStyle = fCol;
ctx.strokeStyle = sCol;
ctx.lineWidth = width;
ctx.beginPath();
ctx.arc(x,y,r,0,Math.PI*2);
if(fCol) {ctx.fill()}
if(sCol) {ctx.stroke()}
}
var dragging = 0;
function display() {
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0, 0, w, h);
var dist = Math.hypot(line.x1 - mouse.x, line.y1 - mouse.y);
var dist1 = Math.hypot(line.x2 - mouse.x, line.y2 - mouse.y);
var col1 = "blue";
var col2 = "blue";
if(dragging){
if(dragging === 1){
line.x1 = mouse.x;
line.y1 = mouse.y;
canvas.style.cursor = "move";
}else{
line.x2 = mouse.x;
line.y2 = mouse.y;
canvas.style.cursor = "move";
}
if(mouse.buttonRaw !== 1){
dragging = 0;
canvas.style.cursor = "default";
}
}else if(dist1 < dist && dist1 < 40){
col2 = "red";
canvas.style.cursor = "move";
if(mouse.buttonRaw === 1){
dragging = 2;
}
}else
if(dist < dist1 && dist < 40){
col1 = "red"
canvas.style.cursor = "move";
if(mouse.buttonRaw === 1){
dragging = 1;
}
}else{
canvas.style.cursor = "default";
}
ctx.save();
clipToLine(line);
drawCircle(canvas.width /2, canvas.height / 4 , canvas.height / 5, "Green","Blue",10);
drawCircle(canvas.width /2, canvas.height * (3/4) , canvas.height / 5, "Blue","Green",10);
ctx.fillStyle = "black";
ctx.fillText("Mirror about the line.",canvas.width / 2, canvas.height / 2);
ctx.restore();
mirrorTransformLine(line);
ctx.save();
clipToLine(line);
drawCircle(canvas.width /2, canvas.height / 4 , canvas.height / 5, "Green","Blue",10);
drawCircle(canvas.width /2, canvas.height * (3/4) , canvas.height / 5, "Blue","Green",10);
ctx.fillStyle = "#444";
ctx.fillText("Mirror about the line.",canvas.width / 2, canvas.height / 2);
ctx.restore();
drawLine(line,"red",4);
drawCircle(line.x1,line.y1,10,"white",col1,4);
drawCircle(line.x2,line.y2,10,"white",col2,4);
}
//===========================================================================
// Boilerplat code from here down not part of answer
var w, h, cw, ch, canvas, ctx, mouse, globalTime = 0, firstRun = true;
;(function(){
const RESIZE_DEBOUNCE_TIME = 100;
var createCanvas, resizeCanvas, setGlobals, resizeCount = 0;
createCanvas = function () {
var c,
cs;
cs = (c = document.createElement("canvas")).style;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
resizeCanvas = function () {
if (canvas === undefined) {
canvas = createCanvas();
}
canvas.width = innerWidth;
canvas.height = innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") {
setGlobals();
}
if (typeof onResize === "function") {
if(firstRun){
onResize();
firstRun = false;
}else{
resizeCount += 1;
setTimeout(debounceResize, RESIZE_DEBOUNCE_TIME);
}
}
}
function debounceResize() {
resizeCount -= 1;
if (resizeCount <= 0) {
onResize();
}
}
setGlobals = function () {
cw = (w = canvas.width) / 2;
ch = (h = canvas.height) / 2;
}
mouse = (function () {
function preventDefault(e) {
e.preventDefault();
}
var mouse = {
x : 0,
y : 0,
w : 0,
alt : false,
shift : false,
ctrl : false,
buttonRaw : 0,
over : false,
bm : [1, 2, 4, 6, 5, 3],
active : false,
bounds : null,
crashRecover : null,
mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
var m = mouse;
function mouseMove(e) {
var t = e.type;
m.bounds = m.element.getBoundingClientRect();
m.x = e.pageX - m.bounds.left;
m.y = e.pageY - m.bounds.top;
m.alt = e.altKey;
m.shift = e.shiftKey;
m.ctrl = e.ctrlKey;
if (t === "mousedown") {
m.buttonRaw |= m.bm[e.which - 1];
} else if (t === "mouseup") {
m.buttonRaw &= m.bm[e.which + 2];
} else if (t === "mouseout") {
m.buttonRaw = 0;
m.over = false;
} else if (t === "mouseover") {
m.over = true;
} else if (t === "mousewheel") {
m.w = e.wheelDelta;
} else if (t === "DOMMouseScroll") {
m.w = -e.detail;
}
if (m.callbacks) {
m.callbacks.forEach(c => c(e));
}
e.preventDefault();
}
m.addCallback = function (callback) {
if (typeof callback === "function") {
if (m.callbacks === undefined) {
m.callbacks = [callback];
} else {
m.callbacks.push(callback);
}
}
}
m.start = function (element) {
if (m.element !== undefined) {
m.removeMouse();
}
m.element = element === undefined ? document : element;
m.mouseEvents.forEach(n => {
m.element.addEventListener(n, mouseMove);
});
m.element.addEventListener("contextmenu", preventDefault, false);
m.active = true;
}
m.remove = function () {
if (m.element !== undefined) {
m.mouseEvents.forEach(n => {
m.element.removeEventListener(n, mouseMove);
});
m.element.removeEventListener("contextmenu", preventDefault);
m.element = m.callbacks = undefined;
m.active = false;
}
}
return mouse;
})();
function update(timer) { // Main update loop
if(ctx === undefined){
return;
}
globalTime = timer;
display(); // call demo code
requestAnimationFrame(update);
}
setTimeout(function(){
resizeCanvas();
mouse.start(canvas, true);
window.addEventListener("resize", resizeCanvas);
requestAnimationFrame(update);
},0);
})();
/** SimpleFullCanvasMouse.js end **/