I'm trying to animate a pen filling in a variable width shape, currently working with html5 canvas. Ideally, I want to be able to have the sample below both start light and get colored in dark, as well as not appear at all and get colored in as though being drawn from nothing.
source-in doesn't seem to work, at least in firefox.
The image in question is a simple-ish SVG path, so if there's a reasonable way to generate canvas clip paths from SVG bezier paths, that would work as well.
var img = new Image();
img.src = "data:image/svg+xml;base64,...";
var xRecords = [...];
var yRecords = [...];
var canvas = document.getElementById("topCanvas");
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
ctx.strokeStyle = "#0000ff";
ctx.lineWidth = 90;
ctx.beginPath();
ctx.lineCap = "round";
ctx.moveTo(xRecords[0], yRecords[0]);
for(var i = 0; i < xRecords.length; i++) {
ctx.lineTo(xRecords[i], yRecords[i]);
ctx.stroke();
}
http://codepen.io/matelich/pen/gpLmOW
Generating alternate versions of the image is not a big deal if that would help. Oh and the animation is just a sample.
Update This works for the most part on Chrome and IE, but not on Firefox: http://codepen.io/matelich/pen/pJNeRq
FF doesn't like your SVG dataURL.
Option#1:
You could use a .png image instead.
Option#2:
You can create a cubic Bezier curve (like SVG's "C") in canvas using context.moveTo and context.bezierCurveTo.
Then your compositing will work fine even in FF:
var canvas = document.getElementById("topCanvas");
var ctx = canvas.getContext("2d");
var img = new Image();
img.onload=start;
img.src = 'https://dl.dropboxusercontent.com/u/139992952/multple/svg2png.png';
var xRecords = [117.6970666666671, 137.5037866666671, 139.6247579166671, 138.2627966666671, 134.75555041666712, 130.4406666666671, 126.65579291666711, 124.7385766666671, 126.0266654166671, 131.8577066666671, 155.5330366666671, 191.76562666666712, 224.94953666666714, 239.47882666666712];
var yRecords = [143.95648000000128, 200.21077333333463, 232.21213000000128, 264.735546666668, 296.24260333333467, 325.19488000000126, 350.05395666666794, 369.2814133333347, 381.33883000000134, 384.687786666668, 371.9640133333346, 346.7872000000013, 322.15182666666794, 311.05237333333463];
function start(){
canvas.width=img.width;
canvas.height=img.height;
ctx.beginPath();
ctx.rect(0, 0, 640, 640);
ctx.fillStyle = 'white';
ctx.fill();
ctx.globalCompositeOperation = "darker";
ctx.drawImage(img, 0, 0);
ctx.globalCompositeOperation = "lighter";
ctx.drawImage(img, 0, 0);
ctx.globalCompositeOperation = "xor";
ctx.drawImage(img, 0, 0);
ctx.globalCompositeOperation = "destination-over";
ctx.strokeStyle = "#0000ff";
ctx.lineWidth = 90;
ctx.beginPath();
ctx.lineCap = "round";
ctx.moveTo(xRecords[0], yRecords[0]);
var i = 0;
function drawNext()
{
i++;
console.log(i+"!");
if(i >= xRecords.length) { return; }
ctx.lineTo(xRecords[i], yRecords[i]);
ctx.stroke();
setTimeout(drawNext, 500);
}
drawNext();
}
body{ background-color: white; }
#canvas{border:1px solid red;}
<canvas id="topCanvas" width=300 height=300></canvas>
Related
What I want to do
I want to know how to make background color white.
I built a drawing app with canvas. You can download the canvas image you have drawn by clicking the Download button. But its background color is black (technically transparent).
How can I change it to white?
What I tried
I added the following code to my code, but it didn't work well. I couldn't draw anything.
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, canvas.width, canvas.height);
Here is my code
const canvas = document.querySelector('#draw');
const ctx = canvas.getContext('2d');
ctx.strokeStyle = '#BADA55';
...
function draw(e) {
if (!isDrawing) return;
console.log(e);
ctx.strokeStyle = `hsl(${hue}, 100%, 50%)`;
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(e.offsetX, e.offsetY);
ctx.stroke();
[lastX, lastY] = [e.offsetX, e.offsetY];
...
}
canvas.addEventListener('mousedown', (e) => {
isDrawing = true;
[lastX, lastY] = [e.offsetX, e.offsetY];
});
canvas.addEventListener('mousemove', draw);
...
downloadBtn.addEventListener('click', downloadImage);
function downloadImage() {
if (canvas.msToBlob) {
const blob = canvas.msToBlob();
window.navigator.msSaveBlob(blob, filename);
} else {
downloadLink.href = canvas.toDataURL('image/png');
downloadLink.download = filename;
downloadLink.click();
}
}
I want to make background color of downloaded image white.
You can use the following code to set background color of canvas.
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
context.fillStyle = "green";
context.fillRect(0, 0, canvas.width, canvas.height);
canvas{ border: 1px solid black; }
<canvas width=300 height=150 id="canvas">
On a canvas you can use getAttribute() to retrieve the dimension. Look at my snippet:
let canvas = document.getElementById('canvas');
let cheight = parseInt(canvas.getAttribute("height"));
let cwidth = parseInt(canvas.getAttribute("width"));
let context = canvas.getContext('2d');
context.fillStyle = "green";
context.fillRect(0,0,cwidth,cheight);
<canvas width="200" height="200" id="canvas">
In your draw() function you need to add specifically the background like this:
const canvas = document.querySelector('#draw');
const ctx = canvas.getContext('2d');
ctx.strokeStyle = '#BADA55';
ctx.fillStyle = "#ffffff"; //HERE, use HEX format in 6 digits
ctx.fillRect(0, 0, canvas.width, canvas.height); //HERE
...
function draw(e) {
...
}
Why?
You need to draw the background before everything, otherwise drawing the background over and over, or also above everything would result in the white rectangle overlapping everything on your canvas.
Here is a LIVE DEMO.
created a circle context and added the pattern image to fill in that circle.how can i make that pattern image grayscaled.
Is there any idea that would help me.
code :
ctx.beginPath();
var bg = new Image();
bg.src = image;
bg = function() {
var pattern = ctx.createPattern(this, "no-repeat");
ctx.fillStyle = pattern;
};
ctx.moveTo(canvasCenterHoriz, canvasCenterVert);
ctx.arc(x, y, radius, startAngle, endAngle, direction);
ctx.lineWidth = 0;
ctx.fill();
This might not be the best way to do this, but it's pretty simple and it works.
I have attached a working example below. Here's how it works:
The pattern image is drawn in an invisible canvas. Once the image has loaded, it is drawn on the canvas with a white overlay and saturation set as the global composite operation. The canvas will now contain a grayscale version of your pattern.
The temporary canvas is then converted to an image, with its source set to the canvas data url. Maybe there's a better way to send image data between two canvases, but I haven't found one.
Once the pattern is finished, your original arc is drawn with the new pattern.
let canvas = document.getElementById('grayscale-canvas');
let ctx = canvas.getContext('2d');
function makeGrayscaleBackground(image, onready) {
var bg = new Image();
bg.src = image;
bg.onload = function() {
// Create a canvas that's not attached to the DOM
var canvas = document.createElement('canvas');
canvas.setAttribute('width', this.width);
canvas.setAttribute('height', this.height);
document.body.appendChild(canvas);
let ctx = canvas.getContext('2d');
// Draw the background image
ctx.drawImage(this, 0, 0);
// Then draw a white layer on top, with the saturation composite operation
// which will remove the color from the underlying image
ctx.globalCompositeOperation = "saturation";
ctx.fillStyle = "#FFF";
ctx.fillRect(0, 0, this.width, this.height);
onready(canvas);
};
}
function drawWithImage(image) {
makeGrayscaleBackground(image, function(patternImage) {
// Green background
ctx.fillStyle = "#3f9";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Make a repeating pattern with image we created
var pattern = ctx.createPattern(patternImage, "repeat");
// Make an arc with the pattern
let x = y = canvas.width/2;
ctx.fillStyle = pattern;
ctx.beginPath();
ctx.arc(x, y, canvas.width/2-10, 0, 2*Math.PI);
ctx.fill();
})
}
// Example pattern image
// For security reasons, the image needs to be hosted on the same server as the script!
var bgImage = "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7";
drawWithImage(bgImage);
document.getElementById('bg').src = bgImage;
<canvas width="150" height="150" id="grayscale-canvas"></canvas><br>
Actual background image: <img id="bg"><br>
Modified background image:
I can't see that this had been posted already, so here goes.
Let's say i draw 2 squares on the canvas.
var c = document.getElementById('test'), ctx = c.getContext('2d');
ctx.fillStyle = "rgba(0,0,255,0.5)";
ctx.beginPath();
ctx.moveTo(25, 0);
ctx.lineTo(50, 50);
ctx.lineTo(0, 50);
ctx.lineTo(25, 0);
ctx.fill();
ctx.fillStyle = "rgba(255,0,0,0.5)";
ctx.beginPath();
ctx.moveTo(50, 0);
ctx.lineTo(75, 50);
ctx.lineTo(25, 50);
ctx.lineTo(50, 0);
ctx.fill();
This produces this image:
If i change globalAlpha to 0.5, i get this:
However, i want to produce this:
As in, all pixels are transparent and any images under it will appear, but the pixels created by the red triangle will overwrite the existing blue triangle where it is drawn.
And ctx.globalComposisteOperation doesn't seem to help in this instance due to it also factoring the transparency and the fact i want to keep both squares.
Is there any way to do this with current methods?
Use Compositing to clear the red triangle before drawing it.
Using compositing is slightly better than clipping because you don't have to clear the clip. Clearing a clip requires saving the entire context state and then restoring that context state -- many properties involved. Compositing just requires changing 1 property forth and back.
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// fill the blue rect
ctx.fillStyle = "rgba(0,0,255,0.5)";
ctx.beginPath();
ctx.moveTo(25, 0);
ctx.lineTo(50, 50);
ctx.lineTo(0, 50);
ctx.lineTo(25, 0);
ctx.fill();
// define the red rect path
ctx.beginPath();
ctx.moveTo(50, 0);
ctx.lineTo(75, 50);
ctx.lineTo(25, 50);
ctx.lineTo(50, 0);
// clear the red rect path using compositing
ctx.globalCompositeOperation='destination-out';
ctx.fillStyle='black';
ctx.fill();
ctx.globalCompositeOperation='source-over';
// fill the red rect
ctx.fillStyle = "rgba(255,0,0,0.5)";
ctx.fill();
body{ background-color:white; }
#canvas{border:1px solid red; }
<canvas id="canvas" width=512 height=512></canvas>
Layers
Do it the photoshop way and create layers to do the work for you. Creating a layer (second canvas) is no more trouble than loading an image. You can create dozens and have no problem and it makes this type of work easy.
First create a second canvas (layer)
// canvas is original canvas
var layer = document.createElement("canvas");
layer.width = canvas.width; // same size as original
layer.height = canvas.height;
var ctx1 = layer.getContext("2d");
Then draw your triangles on the second canvas with alpha = 1;
var tri = (x,c)=>{
ctx1.fillStyle = c;
ctx1.beginPath();
ctx1.moveTo(25 + x, 0);
ctx1.lineTo(50 + x, 50);
ctx1.lineTo(0 + x, 50);
ctx1.closePath();
ctx1.fill();
}
tri(0,"#00f");
tri(25,"#f00");
Then just draw that layer on top of the canvas you are working on with the alpha value you want.
ctx.globalAlpha = 0.5;
ctx.drawImage(layer,0,0);
If you don't need the extra layer delete the canvas and context by dereferencing them.
ctx1 = undefined;
layer = undefined;
Or you can keep the layer , and make another layer for the background and mix them in real time to get the FX just right
//
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
var layer = document.createElement("canvas");
layer.width = canvas.width; // same size as original
layer.height = canvas.height;
var ctx1 = layer.getContext("2d");
var tri = (x,c)=>{
ctx1.fillStyle = c;
ctx1.beginPath();
ctx1.moveTo(25 + x, 0);
ctx1.lineTo(50 + x, 50);
ctx1.lineTo(0 + x, 50);
ctx1.fill();
}
tri(0,"#00f");
tri(25,"#0f0");
tri(50,"#f00");
ctx.globalAlpha = 0.5;
ctx.drawImage(layer,0,0);
layer = ctx1 = undefined;
You can clear the area for the second rectangle before drawing it.
var c = document.getElementById('game'),
ctx = c.getContext('2d');
ctx.globalAlpha = 0.5;
ctx.fillStyle = "blue";
ctx.fillRect(0, 0, 50, 50);
// Clear the extra bit of the blue rectangle
ctx.clearRect(25, 25, 25, 25);
ctx.fillStyle = "red";
ctx.fillRect(25, 25, 50, 50);
<canvas id="game" width="320" height="240"></canvas>
I would honestly suggest just changing the colors and avoid using an alpha.
var c = document.getElementById('game'),
ctx = c.getContext('2d');
ctx.fillStyle = "#8080FF";
ctx.fillRect(0, 0, 50, 50);
ctx.fillStyle = "#ff8080";
ctx.fillRect(25, 25, 50, 50);
<canvas id="game" width="320" height="240"></canvas>
var c = document.getElementById('test'), ctx = c.getContext('2d');
ctx.save();
ctx.fillStyle = "rgba(0,0,255,0.5)";
ctx.beginPath();
ctx.moveTo(25, 0);
ctx.lineTo(50, 50);
ctx.lineTo(0, 50);
ctx.lineTo(25, 0);
ctx.fill();
ctx.fillStyle = "rgba(255,0,0,0.5)";
ctx.beginPath();
ctx.moveTo(50, 0);
ctx.lineTo(75, 50);
ctx.lineTo(25, 50);
ctx.lineTo(50, 0);
ctx.clip();
ctx.clearRect(0, 0, 100, 100);
ctx.fill();
ctx.restore();
Just use clip() to take the current path, clear everything in there with clearRect, then draw the path as normal.
#markE is right, use compositing instead of clipping (really heavy).
However, his solution will only work if you've drawn everything with an rgba color. Maybe I read your question wrongly, but if you're going from an opaque shape and want to make it transparent, then you should rather use the copy gCO and the globalAlpha property.
This will have less performance impact since drawImage is faster than fill, and will allow you to perform a fade-out effect ; but it really depends on your needs.
var ctx = c.getContext('2d');
// initial blue
ctx.fillStyle = "#0000FF";
ctx.beginPath();
ctx.moveTo(25, 0);
ctx.lineTo(50, 50);
ctx.lineTo(0, 50);
ctx.lineTo(25, 0);
ctx.fill();
setTimeout(function drawRed() {
ctx.fillStyle = "#FF0000";
ctx.beginPath();
ctx.moveTo(50, 0);
ctx.lineTo(75, 50);
ctx.lineTo(25, 50);
ctx.lineTo(50, 0);
ctx.fill();
}, 500);
btn.onclick = function makeItTransparent() {
// if we weere to make this into an animation, we would set it only once
ctx.globalCompositeOperation = 'copy';
ctx.globalAlpha = .8;
// draw the canvas on itself
ctx.drawImage(c, 0, 0);
// once again, in an animation we won't reset this to default
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = 'source-over';
};
/* checkboard background */
canvas {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAGElEQVQYlWNgYGBowIKxgqGgcJA5h3yFAOI3GQFqqi5ZAAAAAElFTkSuQmCC");
}
<canvas id="c"></canvas>
<button id="btn">make it transparent</button>
I am having a problem with drawing and rotating image on my canvas. Basically, my approach is to create the wheel of fortune which allows customization based on the prizes in form of array. The data in this array makes up the segment inside the wheel based on the number of indexes.
The data is very simple. It is just a simple JSON object like this
var prizes = [
{product:"Axe FX", img: "https://mdn.mozillademos.org/files/5395/backdrop.png"},
{product:"Musicman JPX", img: "https://mdn.mozillademos.org/files/5395/backdrop.png"},
{product:"Ibanez JEM777V", img: "https://mdn.mozillademos.org/files/5395/backdrop.png"}
];
This data is used to create the segments inside the wheel. So I want to place the text which is currently working like a charm for me.
When drawing the wheel, I separate into two main functions. One to draw the wheel and another to draw the segments inside the wheel.
var drawPartial = function(key, lastAngle, angle) {
var value = prizes[key].product;
ctx.save();
ctx.beginPath();
ctx.lineWidth = 6;
ctx.fillStyle = segColors[key];
ctx.moveTo(centerX, centerY);
ctx.arc(centerX, centerY, size, lastAngle, angle);
ctx.lineTo(centerX, centerY);
ctx.closePath();
ctx.stroke();
ctx.fill();
ctx.save();
ctx.translate(centerX, centerY);
ctx.rotate((lastAngle+angle) / 2);
ctx.fillStyle = "#000";
ctx.fillText(value.substr(0,20), size / 2 + 20, 0);
ctx.restore();
ctx.restore();
}
var draw = function() {
var len = prizes.length;
var currentAngle = outCurrentAngle;
var lastAngle = currentAngle;
ctx.strokeStyle = '#000000';
ctx.textBaseline = "middle";
ctx.textAlign = "center";
ctx.font = "1.4em Arial";
for(var i = 1; i <= len; i++) {
var angle = (Math.PI*2) * (i/len) + currentAngle;
drawPartial(i-1, lastAngle, angle);
lastAngle = angle;
}
ctx.beginPath();
ctx.lineWidth = 2;
ctx.fillStyle = "#fff";
ctx.moveTo(centerX, centerY);
ctx.arc(centerX, centerY, size/7, 0, Math.PI*2);
ctx.closePath();
// ctx.stroke();
ctx.fill();
ctx.beginPath();
ctx.lineWidth = 10;
ctx.arc(centerX, centerY, size, 0, Math.PI*2);
ctx.closePath();
// ctx.stroke();
}
With the code above, I just simple call the draw() function and the wheel and all segments will be created accordingly. However, I want to draw the image in each segment but I don't know to make it work. This is the modification of drawPartial() for rendering images along with text
var drawPartial = function(key, lastAngle, angle) {
var value = prizes[key].product;
var img = new Image();
img.src = prizes[key].img;
img.onload = function() {
ctx.save();
ctx.drawImage(img,centerX,centerY);
ctx.save();
ctx.translate(centerX,centerY);
ctx.rotate((lastAngle+angle) / 2);
ctx.drawImage(img,centerX,centerY);
ctx.restore();
ctx.restore();
}
ctx.save();
ctx.beginPath();
ctx.lineWidth = 6;
ctx.fillStyle = segColors[key];
ctx.moveTo(centerX, centerY);
ctx.arc(centerX, centerY, size, lastAngle, angle);
ctx.lineTo(centerX, centerY);
ctx.closePath();
ctx.stroke();
ctx.fill();
ctx.save();
ctx.translate(centerX, centerY);
ctx.rotate((lastAngle+angle) / 2);
ctx.fillStyle = "#000";
ctx.fillText(value.substr(0,20), size / 2 + 20, 0);
ctx.restore();
ctx.restore();
}
You can see that I add image and its src based on the prizes object which should be called in each iteration called by the main draw() function but it never renders any image in any segment.
What I want is. In each iteration of drawPartial(), I want the image to be placed in the segment along with the text and rotated according to the angle.
Please help...
Problem
In your img.onload function you are "double translating" your centerX & centerY.
ctx.translate(centerX,centerY) will move the canvas's [0,0] origin to [centerX,centerY].
So when you ctx.drawImage(img,centerX,centerY) to draw your image, you are really double moving.
As a result your image is really being drawn at [ centerX*2, centerY*2 ].
A additional thought: Preload your images
It's best to preload all your images. That way if an image fails to load you can take reparative action before you begin drawing your wheel.
Here is how to preload all of your images so they are available when you need to draw them onto your Wheel:
// your incoming JSON
var prizesJSON='[{"product":"Axe FX","img":"https://mdn.mozillademos.org/files/5395/backdrop.png"},{"product":"Musicman JPX","img":"https://mdn.mozillademos.org/files/5395/backdrop.png"},{"product":"Ibanez JEM777V","img":"https://mdn.mozillademos.org/files/5395/backdrop.png"}]';
// the JSON converted to a JS array of objects
var prizes=JSON.parse(prizesJSON);
// preload all images
var imageURLs=[];
var imgs=[];
var imagesOK=0;
// add prize images into the image preloader
for(var i=0;i<prizes.length;i++){
imageURLs.push(prizes[i].img);
}
startLoadingAllImages(imagesAreNowLoaded);
//
function startLoadingAllImages(callback){
for (var i=0; i<imageURLs.length; i++) {
var img = new Image();
imgs.push(img);
img.onload = function(){
imagesOK++;
if (imagesOK>=imageURLs.length ) {
callback();
}
};
img.onerror=function(){alert("image load failed");}
img.src = imageURLs[i];
}
}
//
function imagesAreNowLoaded(){
// add the img objects to your prizes array objects
for(var i=0;i<prizes.length;i++){
prizes[i].imageObject=imgs[i];
// just testing (add the img to the DOM)
document.body.appendChild(imgs[i]);
}
// All images are fully loaded
// So draw your wheel now!
}
body{ background-color: ivory; }
<h4>Testing: (1) Preload all images, (2) Add imgs to DOM</h4>
I had this laying around...
I see you already have code to draw your Wheel, but I had this code in my code archive so I offer it here just in case it has some use for you.
Here is an example of how to draw a "Wheel of Fortune" with each blade containing a prize image and text. The techniques used include:
context.translate to set the rotation point to the center of the wheel.
context.rotate to rotate each blade to its desired angle.
context.textAlign & context.textBaseline to draw centered text.
context.globalAlpha to lighten each blades color so the black text has good contrast.
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var PI=Math.PI;
var PI2=PI*2;
var bladeCount=10;
var sweep=PI2/bladeCount;
var cx=cw/2;
var cy=ch/2;
var radius=130;
var img=new Image();
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/house32x32transparent.png";
function start(){
for(var i=0;i<bladeCount;i++){
drawBlade(img,'House'+i,cx,cy,radius,sweep*i,sweep);
}
}
function drawBlade(img,text,cx,cy,radius,angle,arcsweep){
// save the context state
ctx.save();
// rotate the canvas to this blade's angle
ctx.translate(cx,cy);
ctx.rotate(angle);
// draw the blade wedge
ctx.lineWidth=1.5;
ctx.beginPath();
ctx.moveTo(0,0);
ctx.arc(0,0,radius,0,arcsweep);
ctx.closePath();
ctx.stroke();
// fill the blade, but keep the color light
// so the black text has good contrast
ctx.fillStyle='white';
ctx.fill();
ctx.fillStyle=randomColor();
ctx.globalAlpha=0.30;
ctx.fill();
ctx.globalAlpha=1.00;
// draw the text
ctx.rotate(PI/2+sweep/2);
ctx.textAlign='center';
ctx.textBaseline='middle';
ctx.fillStyle='black';
ctx.fillText(text,0,-radius+50);
// draw the img
// (resize to 32x32 so be sure orig img is square)
ctx.drawImage(img,-16,-radius+10,32,32);
// restore the context to its original state
ctx.restore();
}
function randomColor(){
return('#'+Math.floor(Math.random()*16777215).toString(16));
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=300 height=300></canvas>
Is there a way to rotate the existing content of HTML5 canvas by Javascript? I know it's possible to rotate an image that will be drawn on to canvas, but I want to rotate the content that has been drawn on to canvas, for example, a 200x200 corner of a 400x400 canvas, or any specific region of an existing canvas.
Same question to scale the existing canvas content...
I know getImageData/putImageData provide a potential to transform the pixel array, but it's just too slow and inefficient.
It's pretty easy to do with a temp canvas.
Live Demo
Live Demo Animated (just for the heck of it)
The above example draws 2 boxes, then rotates and scales from 0,0 to 200,200
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d");
canvas.width = canvas.height = 400;
// fill the canvas black, and draw 2 boxes
ctx.fillStyle = "#000";
ctx.fillRect(0,0,400,400);
ctx.fillStyle = "rgb(255,0,0)";
ctx.fillRect(10,10,190,190);
ctx.fillStyle = "rgb(255,255,0)";
ctx.fillRect(250,250,90,90);
// Create a temp canvas to store our data (because we need to clear the other box after rotation.
var tempCanvas = document.createElement("canvas"),
tempCtx = tempCanvas.getContext("2d");
tempCanvas.width = canvas.width;
tempCanvas.height = canvas.height;
// put our data onto the temp canvas
tempCtx.drawImage(canvas,0,0,canvas.width,canvas.height);
// Append for debugging purposes, just to show what the canvas did look like before the transforms.
document.body.appendChild(tempCanvas);
// Now clear the portion to rotate.
ctx.fillStyle = "#000";
ctx.fillRect(0,0,200,200);
ctx.save();
// Translate (190/2 is half of the box we drew)
ctx.translate(190/2, 0);
// Scale
ctx.scale(0.5,0.5);
// Rotate it
ctx.rotate(45*Math.PI/180);
// Finally draw the image data from the temp canvas.
ctx.drawImage(tempCanvas,0,0,200,200,10,10,190,190);
ctx.restore();
If you first want to draw on a canvas and then rotate it for use on e.g. corners, you can to that when you "clone" the canvas or by using CSS.
Examples
Get the first canvas element:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
draw on it:
ctx.fillStyle = 'blue';
ctx.fillRect(0,0, 25, 5);
ctx.fill();
ctx.fillStyle = 'red';
ctx.fillRect(25, 0, 25, 5);
ctx.fill();
clone it to another canvas (that is rotated by CSS):
var ctx2 = document.getElementById("canvas2").getContext("2d");
ctx2.drawImage(canvas, 0,0);
or rotate the canvas while you "clone" it:
var ctx3 = document.getElementById("canvas3").getContext("2d");
ctx3.rotate(Math.PI/2);
ctx3.translate(0,-50);
ctx3.drawImage(canvas, 0,0);
here is the CSS for rotating it:
#canvas2 {
-webkit-transform:rotate(90deg);
-moz-transform:rotate(90deg);
-o-transform:rotate(90deg);
-ms-transform:rotate(90deg);
}
Here is the full example:
<!DOCTYPE html>
<html>
<head>
<script>
window.onload = function() {
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.fillStyle = 'blue';
ctx.fillRect(0,0, 25, 5);
ctx.fill();
ctx.fillStyle = 'red';
ctx.fillRect(25, 0, 25, 5);
ctx.fill();
var ctx2 = document.getElementById("canvas2").getContext("2d");
ctx2.drawImage(canvas, 0,0);
var ctx3 = document.getElementById("canvas3").getContext("2d");
ctx3.rotate(Math.PI/2);
ctx3.translate(0,-50);
ctx3.drawImage(canvas, 0,0);
}
</script>
<style>
#canvas2 {
-webkit-transform:rotate(90deg);
-moz-transform:rotate(90deg);
-o-transform:rotate(90deg);
-ms-transform:rotate(90deg);
}
</style>
</head>
<body>
<canvas id="canvas" width="50" height="50"></canvas>
<canvas id="canvas2" width="50" height="50"></canvas>
<canvas id="canvas3" width="50" height="50"></canvas>
</body>
</html>