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.
Related
I was reading this post HTML5 canvas fill circle with image and was curious about doing something a little different.
Step by step I'd like to:
1. Take this image I have of a shield.
2. Trace the outline of the shield to get the shape.
3. Use a second, larger, image to fill the outline of the shield. I don't want it to fit. I just want it to observe the boundaries set by the outline of the shield and not draw outside them.
Here's what I have so far: JSFiddle - outline shape.
var img = new Image();
img.crossOrigin = "anonymous";
img.onload = function () {
// draw the image
// (this time to grab the image's pixel data
ctx.drawImage(img, canvas.width / 2 - img.width / 2, canvas.height / 2 - img.height / 2);
// grab the image's pixel data
imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
data = imgData.data;
// call the marching ants algorithm
// to get the outline path of the image
// (outline=outside path of transparent pixels
points = geom.contour(defineNonTransparent);
ctx.strokeStyle = "red";
ctx.lineWidth = 2;
redraw();
}
img.src = "https://cdn1.iconfinder.com/data/icons/shield-4/744/1-512.png";
// redraw the canvas
// user determines if original-image or outline path or both are visible
function redraw() {
// clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw the image
ctx.drawImage(img, canvas.width / 2 - img.width / 2, canvas.height / 2 - img.height / 2);
// draw the path (consisting of connected points)
// draw outline path
ctx.beginPath();
ctx.moveTo(points[0][0], points[0][1]);
for (var i = 1; i < points.length; i++) {
var point = points[i];
ctx.lineTo(point[0], point[1]);
}
ctx.closePath();
ctx.stroke();
ctx.fillStyle="yellow"; // Just filling with yellow temporarily but would like to use an image
ctx.fill();
}
I'm not sure how to draw inside the outline without overwriting it.
I figured it out using context save, clip, and restore. new jsfiddle
ctx.save();
ctx.clip();
loadnewimage();
ctx.restore();
I want to change color a Image in canvas
this is the Image
You can see there is a Image transparent I was try using PutImgData but my transparent is changing color
Is there anyway to change color the car and money only ?
I was using this code :
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
image = document.getElementById("testImage");
canvas.height = canvas.width = 100;
ctx.fillStyle = 'red';
ctx.fillRect(10,10,20,10);
ctx.drawImage(image,0,0);
var imgd = ctx.getImageData(0, 0, 45, 45),
pix = imgd.data;
for (var i = 0, n = pix.length; i <n; i += 4) {
if(pix[i+3]==0)
{continue;}
pix.length[i]=r|pix.length[i];
pix.length[i+1]=g|pix.length[i+1];
pix.length[i+2]=b|pix.length[i+2];
pix[i + 3] = 255;
}
ctx.putImageData(imgd, 0, 0);
To mix manually you would have to apply a different formula to mix foreground (new color) and background (image) to preserve anti-aliased pixels (and just in case: the image included in the question is not actually transparent, but I guess you just tried to illustrate transparency using the solid checkerboard background?).
I would suggest a different approach which is CORS safe and much faster (and simpler) -
There are a couple of ways to do this: one is to draw in the color you want, then set composite mode to destination-in and then draw the image, or draw the image, set composite mode to source-in and then draw the color.
Example using the first approach coloring the following image blue:
var img = new Image; img.onload = draw; img.src = "//i.stack.imgur.com/cZ0gC.png";
var ctx = c.getContext("2d");
function draw() {
// draw color
ctx.fillStyle = "#09f";
ctx.fillRect(0, 0, c.width, c.height);
// set composite mode
ctx.globalCompositeOperation = "destination-in";
// draw image
ctx.drawImage(this, 0, 0);
}
<canvas id=c></canvas>
Example using second approach:
var img = new Image; img.onload = draw; img.src = "//i.stack.imgur.com/cZ0gC.png";
var ctx = c.getContext("2d");
function draw() {
// draw image
ctx.drawImage(this, 0, 0);
// set composite mode
ctx.globalCompositeOperation = "source-in";
// draw color
ctx.fillStyle = "#09f";
ctx.fillRect(0, 0, c.width, c.height);
}
<canvas id=c></canvas>
To reset comp. mode back to normal use:
// reset comp. mode
ctx.globalCompositeOperation = "source-over";
As with getImageData(), the drawback with this technique is that your canvas must only hold the content of this image while doing this process. A workaround if the image needs to be colored and mixed with other content is to use an off-screen canvas to do the processing, then draw that canvas back onto the main canvas.
Say I have created a polygon-shaped image by creating a shape in HTML5 canvas and then filling it with an image, e.g. as below:
Now I want to round the corners on this hexagon.
There is a lineJoin = "round" property available but this doesn't seem to work (I believe because the shape is filled and there is no outer line to round).
Does anyone have any idea how to do this with HTML5 canvas or any other means?
Here is the code used to create the image:
var ctx = document.getElementById('myCanvas').getContext('2d');
var a = ctx.canvas.width, r = a / 5;
var side = Math.sqrt((4/3) * r * r);
// Draw your image onto the canvas (here I'll just fill the
// surface with red
var img = new Image();
img.src = "https://upload.wikimedia.org/wikipedia/commons/0/03/Mountain_Bluebird.jpg";
img.onload = function () {
var pattern = ctx.createPattern(img, "no-repeat");
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, a, a);
// Switch the blending mode
ctx.globalCompositeOperation = 'destination-in';
// Draw the hexagon shape to mask the image
ctx.beginPath();
ctx.moveTo(0, side/2);
ctx.lineTo(0, 3*side/2);
ctx.lineTo(r, 2*side);
ctx.lineTo(2*r, 3*side/2);
ctx.lineTo(2*r, side/2);
ctx.lineTo(r, 0);
ctx.closePath();
ctx.fill();
};
<canvas width="1000" height="1000" id="myCanvas"></canvas>
Just change the order you draw in and then change globalCompositeOperation to 'source-in'.
I made some adjustments because some of the corners in your code were getting clipped off but didn't adjust the image position (I hope that's easy enough to do)
Preview
You need to adjust the image position by the way like I said
Snippet
var ctx = document.getElementById('myCanvas').getContext('2d');
var a = ctx.canvas.width, r = a / 5;
var side = Math.sqrt((4/3) * r * r) - 20;
// Draw your image onto the canvas (here I'll just fill the
// surface with red
var img = new Image();
img.src = "https://upload.wikimedia.org/wikipedia/commons/0/03/Mountain_Bluebird.jpg";
img.onload = function () {
ctx.lineJoin = "round";
ctx.lineWidth = 50;
// Draw the hexagon shape to mask the image
ctx.beginPath();
ctx.moveTo(50, 50 + side/2);
ctx.lineTo(50, 50 + 3*side/2);
ctx.lineTo(50 + r, 50 + 2*side);
ctx.lineTo(50 + 2*r, 50 + 3*side/2);
ctx.lineTo(50 + 2*r, 50 + side/2);
ctx.lineTo(50 + r, 50);
ctx.closePath();
ctx.stroke();
ctx.fill();
// Switch the blending mode
ctx.globalCompositeOperation = 'source-in';
var pattern = ctx.createPattern(img, "no-repeat");
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, a, a);
};
<canvas width="1000" height="1000" id="myCanvas"></canvas>
There is no automatic way of doing this. The canvas API is pretty low-level, so it doesn't know that you've drawn a shape and want to detect where all the edges are. What you can do, but it would be a bit of a hassle to do is use bezierCurveTo. This method takes size arguments and can create curves. Combine that with your lines, and you can create rounded corners.
bezierCurveTo
You could also add circles at all the corners, with the arc method.
I'm developing web app using canvas and I made three. canvas, canvas_panorama and canvas_image.
First one is something like main canvas, conteiner for the others. canvas_panorama is a background for canvas_image.
After canvas is right clicked, I'm computing angle to rotate canvas_image:
function getAngle( e, pw /*canvas*/ ){
var offset = pw.offset();
var center_x = (offset.left) + ($(pw).width() / 2);
var center_y = (offset.top) + ($(pw).height() / 2);
var mouse_x = e.pageX;
var mouse_y = e.pageY;
var radians = Math.atan2(mouse_x - center_x, mouse_y - center_y);
angle = radians;
}
After I have an angle I'm trying to rotate canvas_image like this:
function redraw(){
var p1 = ctx.transformedPoint(0,0);
var p2 = ctx.transformedPoint(canvas.width,canvas.height);
ctx.clearRect( p1.x, p1.y, p2.x-p1.x, p2.y-p1.y );
canvas_image_ctx.drawImage(image_img, 0, 0, 150, 150);
canvas_panorama_ctx.drawImage(panorama_img, 0, 0, 600, 300);
canvas_panorama_ctx.drawImage(canvas_image, 20, 20);
// rotate panorama_img around its center
// x = x + 0.5 * width
// y = y + 0.5 * height
canvas_panorama_ctx.translate(95, 95);
canvas_panorama_ctx.rotate(angle);
// translate to back
canvas_panorama_ctx.translate(-95, -95);
ctx.drawImage(canvas_panorama, 0, 0);
}
But this rotates both canvas_image and canvas_panorama. It should only rotate canvas_image
JSFiddle to show you my problem
I think you are confusing yourself with this idea of multiple canvases.
Once in the drawImage() method, every of your canvases are just images, and could be just one or even just plain shapes.
Transformation methods do apply to the canvas' context's matrix, and will have effect only if you do some drawing operations when they are set.
Note : To reset your context matrix, you can either use save(); and restore() methods which will also save all other properties of your context, so if you only need to reset the transform, then it's preferred to simply reset the transformation matrix to its default : ctx.setTransform(1,0,0,1,0,0).
Here is a simplified example to make things clearer :
var ctx = canvas.getContext('2d');
// a single shape, with the border of the context matrix
var drawRect = function(){
ctx.beginPath();
ctx.rect(10, 10, 50, 20);
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.stroke();
};
// set the color of our shapes
var gradient = ctx.createLinearGradient(0,0,70,0);
gradient.addColorStop(0,"green");
gradient.addColorStop(1,"yellow");
ctx.fillStyle = gradient;
// here comes the actual drawings
//we don't have modified the transform matrix yet
ctx.strokeStyle = "green";
drawRect();
// here we translate of 100px then we do rotate the context of 45deg
ctx.translate(100, 0)
ctx.rotate(Math.PI/4)
ctx.strokeStyle = "red";
drawRect();
// reset the matrix
ctx.setTransform(1,0,0,1,0,0);
// here we move of 150px to the right and 25px to the bottom
ctx.translate(150, 25)
ctx.strokeStyle = "blue";
drawRect();
// reset the matrix
ctx.setTransform(1,0,0,1,0,0);
<canvas id="canvas" width="500" height="200"></canvas>
In your code, you are setting the transformations on the canvas that does represent your image, and you do draw every of your canvases at each call.
What you want instead, is to set the transformation on the main canvas only, and draw the non-transformed image :
var main_ctx = canvas.getContext('2d');
var img_canvas = canvas.cloneNode();
var bg_canvas = canvas.cloneNode();
var angle = 0;
// draw on the main canvas, and only on the main canvas
var drawToMain = function(){
// first clear the canvas
main_ctx.clearRect(0,0,canvas.width, canvas.height);
// draw the background image
main_ctx.drawImage(bg_canvas, 0,0);
// do the transforms
main_ctx.translate(img_canvas.width/2, img_canvas.height/2);
main_ctx.rotate(angle);
main_ctx.translate(-img_canvas.width/2, -img_canvas.height/2);
// draw the img with the transforms applied
main_ctx.drawImage(img_canvas, 0,0);
// reset the transforms
main_ctx.setTransform(1,0,0,1,0,0);
};
// I changed the event to a simple onclick
canvas.onclick = function(e){
e.preventDefault();
angle+=Math.PI/8;
drawToMain();
}
// a dirty image loader
var init = function(){
var img = (this.src.indexOf('lena')>0);
var this_canvas = img ? img_canvas : bg_canvas;
this_canvas.width = this.width;
this_canvas.height = this.height;
this_canvas.getContext('2d').drawImage(this, 0,0);
if(!--toLoad){
drawToMain();
}
};
var toLoad = 2;
var img = new Image();
img.onload = init;
img.src = "http://pgmagick.readthedocs.org/en/latest/_images/lena_scale.jpg";
var bg = new Image();
bg.onload = init;
bg.src = 'http://www.fnordware.com/superpng/pnggradHDrgba.png';
<canvas id="canvas" width="500" height="300"></canvas>
I am very nerd in this topic. But still I got some solution from this answer. I need covered area by non-transparent part of image on canvas. Its possible to draw outline for sprite image using globalCompositeOperation .
Now Is it possible to get that area covered by outline for that non-transparent part of image? Is it possible to get covered area in x,y position that I can save it for further use ? OR Is there any way to restrict touch event on source-out area from globalCompositeOperation ?
Currently using code from this answer to draw outline :
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 = 2, // 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);
}
EDIT:
Using #Kaiido solution.Its taking pixels from non-transparent + outline. I need only non transparent area.
var ctx = canvas.getContext('2d'),
img = new Image;
img.onload = draw;
img.crossOrigin = 'anonymous';
img.src = "drawing/templates/drawing-pic4.png";
var outline, origOutline,
outlineCtx;
function draw(color) {
ctx.clearRect(0,0,canvas.width,canvas.height);
// onload
if(typeof color !== 'string') color = 'white';
var dArr = [-1,-1, 0,-1, 1,-1, -1,0, 1,0, -1,1, 0,1, 1,1], // offset array
s = 5, // 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 = color;
ctx.fillRect(0,0,canvas.width, canvas.height);
// keep only the outline
ctx.globalCompositeOperation = "destination-out";
ctx.drawImage(img, x, y);
origOutline = ctx.getImageData(0,0,canvas.width, canvas.height).data;
// store the imageData in a new Canvas
outline = canvas.cloneNode(true);
outlineCtx = outline.getContext('2d')
outlineCtx.drawImage(canvas,0,0);
// draw image in original mode
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(img, x, y);
}
var w= 10;
canvas.onclick = function(e){
var rect = canvas.getBoundingClientRect();
var x = e.clientX-rect.left,
y = e.clientY-rect.top;
var pixels = ((y*canvas.width)+x)*4;
showLog("pixels: "+pixels);
if(origOutline[pixels+3]!==0)
{
showLog("in out line: "+origOutline[pixels+3]);
}
// not transparent ?
if(outlineCtx.getImageData(x,y,1,1).data[3]!==0){
ctx.strokeStyle = "#0000ff";
ctx.lineWidth = w;
ctx.lineJoin = ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(x,y);
ctx.lineTo(x,y);
ctx.stroke();
ctx.closePath();
}
else{
showLog("else");
}
}
Here is one way :
Use Ken's method to draw the shape, but for the last drawing, instead of setting the globalCompositeOperation property to 'source-out', set it to 'destination-out'. This way you will have only the outline :
var ctx = canvas.getContext('2d'),
img = new Image;
img.onload = draw;
img.src = "https://dl.dropboxusercontent.com/s/1alt1303g9zpemd/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 = 2, // 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 dest-out mode to keep only the outline
ctx.globalCompositeOperation = "destination-out";
ctx.drawImage(img, x, y);
}
<canvas id=canvas width=500 height=500></canvas>
Now, you can store this outline in a new canvas, and every time you click on your canvas, compare the click event's position to the pixel at same position in your stored canvas :
var ctx = canvas.getContext('2d'),
img = new Image;
img.onload = draw;
img.crossOrigin = 'anonymous';
img.src = "https://dl.dropboxusercontent.com/s/1alt1303g9zpemd/UFBxY.png";
var outline,
outlineCtx;
function draw(color) {
ctx.clearRect(0,0,canvas.width,canvas.height);
// onload
if(typeof color !== 'string') color = 'red';
var dArr = [-1,-1, 0,-1, 1,-1, -1,0, 1,0, -1,1, 0,1, 1,1], // offset array
s = 5, // 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 = color;
ctx.fillRect(0,0,canvas.width, canvas.height);
// keep only the outline
ctx.globalCompositeOperation = "destination-out";
ctx.drawImage(img, x, y);
// store the imageData in a new Canvas
outline = canvas.cloneNode(true);
outlineCtx = outline.getContext('2d')
outlineCtx.drawImage(canvas,0,0);
// draw image in original mode
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(img, x, y);
}
canvas.onclick = function(e){
var rect = canvas.getBoundingClientRect();
var x = e.clientX-rect.left,
y = e.clientY-rect.top;
// not transparent ?
if(outlineCtx.getImageData(x,y,1,1).data[3]===255){
draw('green');
}
else
draw('red');
}
<canvas id=canvas width=500 height=500></canvas>
If your outline is unlikely to change very often, it may be interesting to store the imageData instead of calling getImageData every click.
// in the draw function
ctx.globalCompositeOperation = "destination-out";
ctx.drawImage(img, x, y);
outline = ctx.getImageData(0,0,canvas.width, canvas.height).data;
// in the click event
var pixels = ((y*canvas.width)+x)*4;
if(outline[pixels+3]===255)
// in the outline
else
// out