I am trying to create a function in Kinetic JS Image to redraw the image. I am trying to create a warp / curve effect and I need to redraw the image.
My approach is slicing the image and moving each slice of the image on the Y coordinate based on the equation of a circle.
I cannot get the context of the image to redraw it. This is what I got:
warp: function(wx, wy){
var width = this.getWidth(),
height = this.getHeight(),
context = this.getContext(),
image = this.getImage();
for(var n=0; n < width; n++){
var sx = n,
sy = 0,
sWidth = 1,
sHeight = height,
dx = 1,
dy = 200 * Math.cos(n * 2 * Math.PI / width);
console.log(dy);
context.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, width, height);
}
}
Please give me any suggestions you may have on how to achieve the redraw.
Thanks!
Related
I would like to create an arc distortion of an image with canvas.
My goal is to do the same thing as imagemagick but in javascript with canvas: https://legacy.imagemagick.org/usage/distorts/#circular_distorts
Here is the expected result with the angle parameter that corresponds to the images below:
60°, 120°, 180°, 270°, 360°
I only found two interesting codes that go in the right direction:
This experimental script
which works directly on the pixel array but does not keep the aspect ratio of the original image and the angle given as a parameter does not work well:
https://github.com/sergiks/canvas-text-arc
This other script
which makes a rotation on each column of the image with drawimage but does not allow to configure the angle of the arc, it is a 360° rotation by default:
http://jsfiddle.net/hto1s6fy/
var cv = document.getElementById('cv');
var ig = document.getElementById('ig');
var ctx = cv.getContext('2d');
// draw the part of img defined by the rect (startX, startY, endX, endY) inside
// the circle of center (cx,cy) between radius (innerRadius -> outerRadius)
// - no check performed -
function drawRectInCircle(img, cx, cy, innerRadius, outerRadius, startX, startY, endX, endY) {
var angle = 0;
var step = 1 * Math.atan2(1, outerRadius);
var limit = 2 * Math.PI;
ctx.save();
ctx.translate(cx, cy);
while (angle < limit) {
ctx.save();
ctx.rotate(angle);
ctx.translate(innerRadius, 0);
ctx.rotate(-Math.PI / 2);
var ratio = angle / limit;
var x = startX + ratio * (endX - startX);
ctx.drawImage(img, x, startY, 1, (endY - startY), 0, 0, 1, (outerRadius - innerRadius));
ctx.restore();
angle += step;
}
ctx.restore();
}
var cx = 300,
cy = 300;
var innerRadius = 0;
var outerRadius = 300;
var startX = 0,
endX = 1361,
startY = 0,
endY = 681;
drawRectInCircle(ig, cx, cy, innerRadius, outerRadius, startX, startY, endX, endY);
Imagemagick source code
Finally, I also looked at the C source code of imagemagick but I don't have the skills to transpose it:
https://github.com/imagemagick/imagemagick/blob/main/magickcore/distort.c
(to see what concerns arc distortion, use the keyword "ArcDistortion")
Though this is an interesting topic and I also like to re-invent the wheel sometimes, it isn't necessary in this case. Someone else had a go at it yet and released a JavaScript library called lens, which replicates some of ImageMagick's filters. Luckily the 'Arc distortion' is among those.
Lens offers a method called distort() which accepts an input like a <canvas> element, applies the transformation requested and outputs raw pixel data, which you can then use to make another <canvas>.
Here's a quick example:
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
ctx.font = "48px sans";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "#0";
ctx.fillText("Around the World", canvas.width / 2, canvas.height / 2);
async function makeArc(image, angle, rotate = 0) {
let result = await lens.distort(
image,
lens.Distortion.ARC, [angle, rotate], {
imageVirtualPixelMethod: angle === 360 ? lens.VirtualPixelMethod.HORIZONTAL_TILE : lens.VirtualPixelMethod.TRANSPARENT
}
);
let tempCanv = document.createElement("canvas");
let tempCtx = tempCanv.getContext("2d");
tempCanv.width = result.image.width;
tempCanv.height = result.image.height;
tempCtx.putImageData(result.image.imageData, 0, 0);
document.body.appendChild(tempCanv);
}
makeArc(canvas, 120, 0);
<script src="https://cdn.jsdelivr.net/npm/#alxcube/lens#1.0.0/dist/lens.min.js"></script>
<canvas id="canvas" width="450" height="50"></canvas>
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 need to wrap an image around another image of a mug using javascript, and I found this:
Wrap an image around a cylindrical object in HTML5 / JavaScript
This helps when loading the image that has the mug handle on the left. However when using the same function (with tweaked position values) the image has an opacity applied to it. I searched endlessly to figure out for what reason this is happening however I found nothing :/
This is the function used to wrap the image for the mug with the right handle:
function canvas2() {
var canvas = document.getElementById('canvas2');
var ctx = canvas.getContext('2d');
var productImg = new Image();
productImg.onload = function() {
var iw = productImg.width;
var ih = productImg.height;
canvas.width = iw;
canvas.height = ih;
ctx.drawImage(
productImg,
0,
0,
productImg.width,
productImg.height,
0,
0,
iw,
ih
);
loadUpperIMage();
};
productImg.src =
'https://i.ibb.co/B2G8y1m/white-right-ear.jpg';
function loadUpperIMage() {
var img = new Image();
img.src =
'https://i.ibb.co/BnQP0TL/my-mug-image.png';
img.onload = function() {
var iw = img.width;
var ih = img.height;
var xOffset = 48, //left padding
yOffset = 68; //top padding
var a = 70; //image width
var b = 8; //round ness
var scaleFactor = iw / (6 * a);
// draw vertical slices
for (var X = 0; X < iw; X += 1) {
var y = (b / a) * Math.sqrt(a * a - (X - a) * (X - a)); // ellipsis equation
if (!isNaN(y)) {
ctx.drawImage(
img,
X * scaleFactor,
0,
iw / 0.78,
ih,
X + xOffset,
y + yOffset,
1,
162
);
}
}
};
}
}
Hope someone can help with this!
Here is a fiddle with the issue https://jsfiddle.net/L20aj5xr/
It is because of the 4th argument you pass to drawImage - iw / 0.78. By multiplying image width by a value lower than one, you get the value larger than image width. The spec for drawImage says:
When the source rectangle is outside the source image, the source rectangle must be clipped to the source image and the destination rectangle must be clipped in the same proportion.
ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
Because the source width (sw) you are using is larger than source image size, the destination rectangle "is clipped in the same proportion". The destination rectangle width is 1px because you chose it as a width for each vertical line you are drawing, and after clipping it's width becomes 1 * 0.78 = 0.78px. The width is now less than 1px and to be honest I am not exactly sure how it actually works under the hood, but my guess is that a browser still needs to draw that 1px, but because the source is 0.78px, it kinda stretches the source to that 1px and adds some anti-aliasing to smooth the transition, which results into added transparency (i.e. browser does not have enough information for that 1px and it tries to fill it up the best it can). You can play around with that by incresing sw even more and observe increasing transparency.
To fix your issue I used the value 20 instead of 0.78 like for the first cup and it seemed to look ok.
Is it possible to make an object orbit around another object that goes from behind and then to the front?
I've seen it being done with rotation animations that do a full 360 around the perimeter, but was wondering if it was possible to do it at an angle.
I couldn't find any resources that could do this, so I've included an image example of what I want to accomplish. The red line would be an object orbiting the blue circle.
Thanks so much - I really appreciate the help!
I figured I'd just write up a solution using the <canvas>
var x, y, scale, state, // Variables we'll use later.
canvas = document.getElementById("canvas"), // Get the canvas,
ctx = canvas.getContext("2d"), // And it's context.
counter = 0, // Counter to increment for the sin / cos functions.
width = 350, // Canvas width.
height = 200, // Canvas height.
centerX = width / 2, // X-axis center position.
centerY = height / 2, // Y-axis center position.
orbit = { // Settings for the orbiting planet:
width: 150, // Orbit width,
height: 50, // Orbit height,
size: 10 // Orbiting planet's size.
};
canvas.width = width; // Set the width and height of the canvas.
canvas.height = height;
function update(){
state = counter / 75; // Decrease the speed of the planet for a nice smooth animation.
x = centerX + Math.sin(state) * orbit.width; // Orbiting planet x position.
y = centerY + Math.cos(state) * orbit.height; // Orbiting planet y position.
scale = (Math.cos(state) + 2) * orbit.size; // Orbiting planet size.
ctx.clearRect(0, 0, width, height); // Clear the canvas
// If the orbiting planet is before the center one, draw the center one first.
(y > centerY) && drawPlanet();
drawPlanet("#f00", x, y, scale); // Draw the orbiting planet.
(y <= centerY) && drawPlanet();
counter++;
}
// Draw a planet. Without parameters, this will draw a black planet at the center.
function drawPlanet(color, x, y, size){
ctx.fillStyle = color || "#000";
ctx.beginPath();
ctx.arc(x || centerX,
y || centerY,
size || 50,
0,
Math.PI * 2);
ctx.fill();
}
// Execute `update` every 10 ms.
setInterval(update, 10);
<canvas id="canvas"></canvas>
If you want to change the roation direction of the orbiting planet, just replace:
x = centerX + Math.sin(state) * orbit.width;
y = centerY + Math.cos(state) * orbit.height;
With:
x = centerX + Math.cos(state) * orbit.width;
y = centerY + Math.sin(state) * orbit.height;
// ^ Those got switched.
The speed of the orbit can be changed by modifying the 75 in:
state = counter / 75;
I can't seem to figure out how to scale pixels on an html5 canvas. Here's where I am so far.
function draw_rect(data, n, sizex, sizey, color, pitch) {
var c = Color2RGB(color);
for( var y = 0; y < sizey; y++) {
var nn = n * 4 * sizex;
for( var x = 0; x < sizex; x++) {
data[nn++] = c[0];
data[nn++] = c[1];
data[nn++] = c[2];
data[nn++] = 0xff;
}
n = n + pitch;;
}
}
function buffer_blit(buffer, width, height) {
var c_canvas = document.getElementById("canvas1");
var context = c_canvas.getContext("2d");
context.scale(2, 2);
var imageData = context.getImageData(0, 0, context.canvas.width, context.canvas.height);
var n = width * height - 1;
while((n--)>=0) draw_rect(imageData.data, n, pixel, pixel, buffer[n], width);
context.putImageData(imageData, 0, 0);
}
Edits:
I updated the code here. Not sure what changed.
Maybe some images will help.
First image has pixel size of one, second pixel size of 2. Note that the image doubles and only fills half the canvas.
Edit2:
I made a webpage showing at least one of the strange behaviors I'm experiencing.
Live example
drawImage can be used to scale any image or image-like thing ( or for instance) without using ImageData -- unless you explicitly want to handle pixel specific scaling you should probably use native support. eg.
var myContext = myCanvas.getContext("2d");
// scale image up
myContext.drawImage(myImage, 0, 0, myImage.naturalWidth * 2, myImage.naturalHeight * 2);
// scale canvas up, can even take the source canvas
myContext.drawImage(myCanvas, 0, 0, myCanvas.width * 2, myCanvas.height * 2);
// scale up a video
myContext.drawImage(myVideo, 0, 0, myVideo.width * 2, myVideo.height * 2);
alternatively you could just do:
myContext.scale(2, 2); // say
//.... draw stuff ...
myContext.scale(.5, .5);
Depending on your exact goal.
You could temporarly use an image with canvas.toDataURL(), then draw it resized with drawImage().
context.putImageData() should do the trick, but the dimensions parameters are not yet implemented in Firefox.
The code, with two canvas for demo purposes:
var canv1 = document.getElementById("canv1"),
canv2 = document.getElementById("canv2"),
ctx1 = canv1.getContext("2d"),
ctx2 = canv2.getContext("2d"),
tmpImg = new Image();
/* Draw the shape */
ctx1.beginPath();
ctx1.arc(75,75,50,0,Math.PI*2,true);
ctx1.moveTo(110,75);
ctx1.arc(75,75,35,0,Math.PI,false);
ctx1.moveTo(65,65);
ctx1.arc(60,65,5,0,Math.PI*2,true);
ctx1.moveTo(95,65);
ctx1.arc(90,65,5,0,Math.PI*2,true);
ctx1.stroke();
/* On image load */
tmpImg.onload = function(){
/* Draw the image on the second canvas */
ctx2.drawImage(tmpImg,0,0, 300, 300);
};
/* Set src attribute */
tmpImg.src = canv1.toDataURL("image/png");
EDIT: olliej is right, there is no need to use a temp image