I want to turn a rectangular image into a circle using the HTMLCanvas element. (Finally I only need the upper half of the circle but that could be easily managed by cutting the resulting circle in half.)
From this
To this
My idea was to do a simple line by line transformation. So far I have just the basic drawing logic but I'm totally lost with the math for the transformation.
<!DOCTYPE html>
<body>
<canvas id="canvas"></canvas>
</body>
<script type="text/javascript">
var img = new Image();
img.onload = function init() {
var img = this;
var imgH = img.height;
var imgW = img.width;
// make the canvas the same size as the image.
var c = document.getElementById("canvas");
c.width = imgW;
c.height = imgH;
var ctx = c.getContext("2d");
var halfHeight = imgH/2;
// draw the upper part
// line by line
for(var i = 0; i < halfHeight; i++) {
// I'm totally lost here.
// current output without transformation
ctx.drawImage(img, 0, i, imgW, 1, 0, i, imgW, 1);
}
// add the second half which must not be transformed
ctx.drawImage(img, 0, halfHeight, imgW, halfHeight, 0, halfHeight, imgW, halfHeight);
};
img.src = "https://i.stack.imgur.com/52TjZ.png";
</script>
</html>
A fiddle
https://jsfiddle.net/kirschkern/amq7t6ru/2/
(I need it in pure JS and 2d. No three.js, no webgl.)
Any help is highly appreciated.
I don't know much about Javascript but as this seems more of a mathematical question, I'll have my shot.
Replace the lines
// I'm totally lost here.
// current output without transformation
ctx.drawImage(img, 0, i, imgW, 1, 0, i, imgW, 1);
with
var xMargin = -Math.sqrt(1-Math.pow((i-halfHeight)/halfHeight,2))*imgW/2+imgW/2;
ctx.drawImage(img, 0, i, imgW, 1, xMargin, i, imgW-(2*xMargin), 1);
This distorts the upper half of the image as an ellipse (a circle would work only if your input image would be a square) as this:
Does this solve your question?
Explanation
I took the equation of a shifted ellipse from Wikipedia and set c1 and a to be equal to imgW/2 and c2 and b to imgH/2. Taking i for y let me compute x; I saved one of the solutions as xMargin. The width of the picture at the given vertical coordinate would be the original width minus twice the margin.
In the end, I fed drawImage() with these inputs, see the documentation.
Plain 2D JavaScript does not have primitives to distort images like this. So a simple drawImage will not be enough.
What you can do is approximate things. Write a function which for every point in the distorted image (the one with the circle) computes the corresponding position in the original image. Then you can do one of four things, in increasing order of effort and resulting quality.
Iterate over all the pixels in the destination image, and look up the corresponding pixel value in the source image.
Like before, but with subsampling: take several positions inside the square of the source pixel, and average the resuling colors for a smoother appearance.
Approximate the affine transformation in a given point (for this you will likely need partial derivatives of your mapping function) and use it to draw an affinely transformed image.
Same as 3 but with projective instead of affine transforms. That would arguably make it 3D in its formulation.
Like 1 or 2 but implement all of that in WebGL as a fragment shader. I know you said you don't want that, but in terms of performance and resulting quality this should give the best results.
Related
I am experimenting with canvas and I am having some trouble.
Please see this codepen:
http://codepen.io/JonnyBoggon/pen/YGgKqQ
I would like to generate two (or more potentially) floating images - which collide - like the circles in my codepen. So, exactly as it is now, but with images rather than circles.
function makeAmpersands(num) {
var x, y, vx, vy, r, m, ke, colliding, src;
for (var i = 0; i < num; i++) {
x = Math.random() * canvas.width;
y = Math.random() * canvas.height;
vx = Math.random() * 1 - .5;
vy = Math.random() * 1 - .5;
r = 150;
m = density * (4 / 3) * Math.PI * Math.pow(r, 3);
ke = .5 * m * (vx + vx) * (vy + vy);
colliding = false;
src = siteURL+'/assets/img/floating-ampersand-1.png';
B.push(new ampersand(x, y, vx, vy, r, m, ke, colliding, src));
}
}
I have no idea how to turn those objects into an image object, with a different src for each.
Please excuse my lack of knowledge with canvas; this is my first attempt at creating something.
Any help would be greatly appreciated.
To load and render images using canvas 2D
Create and load image
Create an new Image object, assign the src the URL of the image you wish to load. The image will then begin to load. You will not be able to know how long the image may take to load so you need to either wait until the image fires the onload event or if you are sure that the resource will always be available only use the image if its complete property is === true
As I do not know if your images resource is reliable the code below is a mix of the above method, using the onload event to flag that the image has loaded.
var image = new Image(); // a new image object same as <img> tag
image.src = "imageURL"; // the url pointing to the image
image.onload = function(){ this.ready = true; }; // flag image ready
// This will not happen until
// the current code has exited
Draw an image onto the canvas.
To render the image use the 2D context function drawImage. This function has up to 9 arguments many of which are optional. For full details see MDN drawImage.
If you try to render the image before it has loaded then you will of course not see anything. If the image has an error during loading attempting to draw it may throw an error and stop your code from running. So always be sure your image is ready and safe to draw.
From the above image load snippet the following snippet renders the image
if(image.ready){ // is it ready
ctx.drawImage(image,x,y); // draw image with top left at x,y
}
Loading many images.
It is inefficient to check the image for ready each time you render it. Once ready it is always so. This answer shows how you can load many images. If you have an ongoing animation instead of calling the drawImages function when all images have loaded, call a function that starts the animation, or set a flag to indicate that all images have loaded and are safe to render.
A complete image render function.
The 2D API allows you to draw an image that is scaled, rotated, fade in/out. Rendering a image like this is sometimes called a sprite (From the old 16bit days)
Function to draw a scaled rotated faded image / sprite with the rotation around its center. x and y are the position on the canvas where the center will be. scale is 1 for no scale <1 for smaller, and >1 for larger. rot is the rotation with 0 being no rotation. Math.PI is 180 deg. Increasing rot will rotate in a clockwise direction decreasing will rotate the other way. alpha will set how transparent the image will be with 0 being invisible and 1 as fully visible. Trying to set global alpha with a value outside 0-1 range will result in no change. The code below does a check to ensure that alpha is clamped. If you trust the alpha value you can set globalAlpha directly
function drawSprite(image,x,y,scale,rot,alpha){
ctx.setTransform(scale,0,0,scale,x,y);
ctx.rotate(rot);
ctx.globalAlpha = alpha < 0 ? 0 : alpha > 1 ? 1 : alpha; // if you may have
// alpha values outside
// the normal range
ctx.drawImage(image,-image.width / 2, -image.height / 2);
}
// usage
drawSprite(image,x,y,1,0,1); // draws image without rotation or scale
drawSprite(image,x,y,0.5,Math.PI/2,0.5); // draws image rotated 90 deg
// scaled to half its size
// and semi transparent
The function leaves the current transform and alpha as is. If you render elsewhere (not using this function) you need to reset the current state of the 2D context.
To default
ctx.setTransform(1,0,0,1,0,0);
ctx.globalAlpha = 1;
To keep the current state use
ctx.save();
// draw all the sprites
ctx.restore();
I'm trying to simulate brushes with the HTML canvas element. To get brush hardness, I'm using a radial gradient, but I'm not entirely sure whether it's faster to create a new radial gradient for every point or saving the radial gradient as an image and use drawImage() instead.
Current code:
var gradient = context.createRadialGradient(x, y, hardness * brushSize, x, y, brushSize);
gradient.addColorStop(0, color);
gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
context.fillStyle = gradient;
context.fillRect(x - halfBrushSize, y - halfBrushSize, brushSize, brushSize);
drawImage (apart from creating the image):
context.drawImage(img, x, y);
Gradients are expensive to generate contrary to images which are basically copies. They both need to go through transformation matrix and anti-aliasing process though, but there is no calculation involved with images besides from that.
UPDATE From the comments below people seem to get extremely variable test results depending on browser and hardware. The embedded test is not very accurate and was meant as a pointer, so for this reason I created a more accurate test here. Feel free to post results below in comments.
-- update end --
The following is not the world's most accurate test, but the difference is so large that you get a pretty good pointer in any case to which is faster:
window.performance = window.performance || Date;
setTimeout(go, 250);
function go() {
var ctx = c.getContext("2d");
// create radial gradient
var gr = ctx.createRadialGradient(300, 300, 300, 300, 300, 0);
gr.addColorStop(0, "#000");
gr.addColorStop(1, "#f00");
ctx.fillStyle = gr;
// test gradient fill style
var time1 = performance.now();
for (var i = 1000; i--;) ctx.fillRect(0, 0, c.width, c.height);
var time2 = performance.now() - time1;
o.innerHTML = "Gradient: " + time2.toFixed(4) + "<br>";
// test cached gradient (canvas = image source)
ctx = c2.getContext("2d");
time1 = performance.now();
for (i = 1000; i--;) ctx.drawImage(c, 0, 0);
time2 = performance.now() - time1;
o.innerHTML += "drawImage: " + time2.toFixed(4);
}
<output id=o>Running... please wait</output><br>
<canvas id=c width=600 height=600></canvas><br>
<canvas id=c2 width=600 height=600></canvas>
When it comes to render a radial gradient, you can build the gradient on-the-fly, or use a png as you quoted, yet there's a third possibility : you can use a normalised gradient, that you build once, then use at will at any place/size by using the context transforms.
The code used to create the normalized gradient for a given hardness looks like :
var mySingleGradient = ctx.createRadialGradient(0.5, 0.5, 0.5*hardness, 0.5, 0.5, 0.5);
mySingleGradient.addColorStop(0, color);
mySingleGradient.addColorStop(1, '#000');
Just like when you are using png, you'll run into the issue of caching the gradients for any base color + hardness. But you won't have any png resolution issue, and most probably the size of the gradients will be way smaller than the png's.
You use such a normalised gradient with :
function drawThatGradientHere(ctx, x, y, gradient, brushSize) {
ctx.save();
ctx.translate(x,y);
ctx.scale(brushSize,brushSize);
ctx.fillStyle = gradient;
ctx.fillRect(0,0,1,1);
ctx.restore();
}
I won't go into benchmarking, since there are too many chances to compare apples and oranges without knowing more about the use. Because for instance, the drawImage might very well perform very differently if you are using its scaled version. Mind also that by using an image, you might run into resolution issues (too high : perf, too low : aliasing), that you won't have if you are using a gradient. So even if the gradient was proved slower, you might prefer it because of the way it consistently looks.
A few questions : do you change your hardness often ? do you change the brush size often ? do you change the start/end color of your gradient ?
It's only by answering those question and having a random set of rect/hardness that has the same average distribution of your real use case that you'll be able to benchmark/compare anything.
Last word : If it's becoming hard to say which solution is faster, its time to pick the solution relying on... some other good reason... :-)
I want to make responsive canvas with its context.
I have already made the canvas responsive; below you can see my resize function.
$(window).on('resize', function(event) {
event.preventDefault();
d.myScreenSize = {
x: $('.mainCanvasWrapper').width(),
y: $.ratio($('.mainCanvasWrapper').width())
};
console.log('d.myScreenSize ', d.myScreenSize);
var url = canvas.toDataURL();
var image = new Image();
ctx.canvas.width = d.myScreenSize.x;
ctx.canvas.height = d.myScreenSize.y;
image.onload = function() {
ctx.drawImage(image, 0, 0, $('#paper').width(), $('#paper').height());
//the drawing is being blurry (blurred). Please, have a look at the screenshot i have posted.
}
image.src = url;
if (d.drawer == 'true') {
socket.emit('windowResize', d.myScreenSize);
};
});
I have tried many solutions and also I don't want to use any library
can anyone suggest me better solution ?
You are scaling bitmap data which means you will have loss in quality when resizing it. If you went from a small size to a large size then the image will be blurry due to the interpolation that takes place.
What you would want to do is to store your drawings as vectors:
Keep the drawn points in arrays "internally"
When needed, redraw all the points to the canvas
When resized, scale the points accordingly, then redraw as above.
My suggestion when it comes to rescaling the points is to keep and use the original points as a basis every time as this will give you a more accurate scaling.
There are plenty of examples here on SO on how to store drawn points as well as redraw them. One that could be used for basis is for example: HTML canvas art, generate coordinates data from sketch.
My Problem: I've got an ImageObject, that is being used to create a PatternObject. My problem is, that i changed the width and height properties of the Image before creating the pattern, but that doesn't actually remap the image (and that is also no problem). The thing is, that I now have got a pattern, that is of a different size (the original image size) than the image itself. If I want to draw a line with the fillStyle of that pattern, it doesn't fit (because I need a pattern of the new size).
My question: Is there an easy way, to achieve that the pattern's width and height can be adjusted?
My tries and why I dont like them:
1) Render the original image to a canvas with the new size and create the pattern from that. Didn't use this, because the pattern cannot be loaded directly as a result of the canvas being created and rendered to too slowly. But I want that pattern directly
2) Calculate the variance between the new image size and the original one, change the lineWidth of the context, so the patterns height fits exactly and scale the line down, so it has a nice size. Didn't use that because I render in realtime and this is way too slow to be used later in webapps.
Using canvas (your step 1) is the most flexible way.
It's not slower using a canvas to draw on another canvas than using an image directly. They both use the same element basis (you're blitting a bitmap just as you do with an image).
(Update: Drawing using pattern as style do go through an extra step of a local transformation matrix for the pattern in more recent browsers.)
Create a new canvas in the size of the pattern and simple draw the image into it:
patternCtx.drawImage(img, 0, 0, patternWidth, patternHeight);
Then use the canvas of patternCtx as basis for the pattern (internally the pattern caches this image the first time it's drawn, from there, if possible, it just doubles out what it has until the whole canvas is filled).
The other option is to pre-scale the images to all the sizes you need them to be, load them all in, and then choose the image which size is the one you need.
The third is to draw the image yourself as a pattern. This however is not so efficient compared to the built-in method, though using the above mentioned method (internal) you can get a usable result.
Example of manual patterning:
var ctx = canvas.getContext('2d');
var img = new Image();
img.onload = function() {
fillPattern(this, 64, 64);
change.onchange = change.oninput = function() {
fillPattern(img, this.value, this.value);
}
};
img.src = "//i.stack.imgur.com/tkBVh.png";
// Fills canvas with image as pattern at size w,h
function fillPattern(img, w, h) {
//draw once
ctx.drawImage(img, 0, 0, w, h);
while (w < canvas.width) {
ctx.drawImage(canvas, w, 0);
w <<= 1; // shift left 1 = *2 but slightly faster
}
while (h < canvas.height) {
ctx.drawImage(canvas, 0, h);
h <<= 1;
}
}
<input id=change type=range min=8 max=120 value=64><br>
<canvas id=canvas width=500 height=400></canvas>
(or with a video as pattern).
OK, I admit I tried to be clever: I thought if I overrode Shape's drawFunc property I could simply draw whatever inside a rectangle and still use KineticJS's click detection. Here's my attempt:
var shape = new Kinetic.Shape({
drawFunc: function(context) {
var id = 26; // Id of a region inside composite image.
context.beginPath();
context.rect(0, 0, w, h);
context.closePath();
this.fill(context);
this.stroke(context);
context.drawImage(copyCanvas, (id % 8) * w, flr(id / 8) * h,
w, h, 0, 0, w / 2, h / 2);
},
draggable: true
});
So, the idea was to draw a rectangle, and use drawImage() to draw something on top of the rectangle (like a texture, except it changes from time to time because copyCanvas itself changes). All the meanwhile, I expected event handling (drag-n-drop, in particular) to still 'just work'. Well, here's what happens: the part of the rectangle not covered by my drawImage() correctly detects clicks. However, the one fourth of the rectangle that is covered by the image refuses to respond to clicks! Now, my question is why? I dug into the KineticJS code, and looked to me that click detection simply means drawing to a buffer and seeing if a given x, y point has non-zero alpha. I can't see how this could be affected by my drawing an image on top of my rectangle.
Any ideas what's going on?
OK, so I went ahead and looked at the source code. Here's the definitive answer:
KineticJS assigns a random and unique RGB color to each shape that's created using a global map from RGB colors to shape objects. The draw() function of the shape is called twice: once with the 'real' canvas, and once with a 'buffer' canvas used for hit detection. When using the 'buffer' canvas, KineticJS switches the stroke and fill colors to the unique RGB color of the given shape. The same 'buffer' canvas is used for all shapes on a layer. Thus hit detection simply becomes reading the RGB value of a given point and looking up the corresponding shape in the global map. Now, in my example I drew an image in a way that circumvented KineticJS's juggling of colors used for hit detection. Thus, when I clicked on the image area, KineticJS saw some unknown RGB color on the buffer canvas with no known shape assigned to it.
The solution is not to draw the image for the 'buffer' (or 'hit detection') phase: a simple rectangle will do. In case you're wondering, here's the correct code for the drawFunc:
var width = 200;
var height = 100;
var myShape = new Kinetic.Shape({
drawFunc: function(context) {
if (layer.bufferCanvas.context == context) {
context.beginPath();
context.rect(0, 0, width, height);
context.closePath();
this.fill(context);
this.stroke(context);
} else {
context.drawImage(someCanvasWithAnythingOnIt, 0, 0, width, height,
0, 0, width, height);
}
}});
Can I collect my own reward?
I think your problem lies in the order. There is a depth associated with each object that you draw and the default ordering is like a stack, last drawn is on top.
Now that you have modified the code, making 2 draws inside the shape draw function, I still think the ordering is preserved and hence, the object is not able to detect the input. Try changing the order, i.e. draw image first and then the rectangle and see if the problem is solved.
Else, share a jsFiddle for an example.