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.
Related
page 1:
//Calls the function from page 2 and the callback image is set as the source of the control.
previewImage(current, function(img) {
jQuery(".mapItem").attr("src",img.src);
});
page 2:
//The functions callback returns an image which we use in page 1 (above)
var canvas = document.createElement('canvas');
context = canvas.getContext('2d');
context.drawImage(this.m_Images[i],0,0,canvas.width,canvas.height);
var t = new Image();
t.src = canvas.toDataURL();
callback(t);
The issue:
I have 2 JavaScript pages, the first one has an image control and the second one has a function that returns a callback as an image.
My control in page 1 (.mapItem) has a height and width of 75.2px (fixed). The image that is coming from the callback however will have a different size each time e.g one day it can be 200px * 300px and one day it can be 150px * 200px etc
How can I clip or CUT the image of the callback? I want the image (t) to zero (0) as x and y starting points and then clip the image where ever the .mapItem control height and width is.
I need this to be proportional ratio. So I can't just add the following code:
context.drawImage(this.m_Images[i],0,0,72.5,72.5); because this will ruin the image as we dont even know if it is square shaped.
Thanks in advance :)
You can determine the proportions of callback image and then apply them to the page 1 image thus:
Let's assume that the callback image is 300x200px. The ratio of the image's height-to-width can be expressed as...
var ratio = callbackImage.height / callbackImage.width;
...or, in this case...
var ratio = 200 / 300; // 0.666...
We know the width of the page 1 canvas is 72.5 so we can apply the ratio to that value to determine the proportional height of the callback Image like so...
var canvasWidthHeight = 72.5;
var imageHeight = canvasWidthHeight * ratio; // 48.333...
To center the callback Image on the page 1 canvas calculate the y offset like so..
var y = (canvasWidthHeight - imageHeight) / 2;
...and now you can use the canvas drawImage method with these values...
context.drawImage(
this.m_Images[i],
0, y,
canvasWidthHeight, imageHeight
);
If the callback image was higher than it was wide you'd apply the ratio the page 1 canvas dimensions to work out the x offset rather than the y offest. If the callback image was square then its ratio would be 1.0 and you could simply paint it into the square page 1 canvas at
context.drawImage(
this.m_Images[i],
0, 0,
canvasWidthHeight, canvasWidthHeight
);
All together the code might look something like this...
var image = this.m_Images[i];
var canvas = {
width: 72.5,
height: 72.5
};
var wh = 0;
var ratio = image.height / image.width;
if (image.width > image.height) { // landscape
wh = canvas.width * ratio;
context.drawImage(
image,
0, (canvas.height - wh) / 2,
canvas.width, wh
);
} else if (image.width < image.height) { // portrait
ratio = image.width / image.height;
wh = canvas.height * ratio;
context.drawImage(
image,
(canvas.width - wh) / 2, 0,
wh, canvas.height
);
} else { // square
context.drawImage(
image,
0, 0,
canvas.width, canvas.height
);
}
Hope that helps. ;)
EDIT: You may need to ensure that the new Image() has fully loaded before initiating the callback. You can do this with the following snippet...
// callback preparation code as before...
var t = new Image();
t.addEventListener('load', function() {
callback(this);
});
t.src = canvas.toDataURL();
I'm rendering an image to a canvas. After reading the drawImage MDN API, it looks like the following two lines of code should produce the same result:
var ctx = canvas.getContext('2d');
ctx.drawImage(this.image, 0, 0, this.image.width, this.image.height, 0, 0, this.clip.width, this.clip.height);
ctx.drawImage(this.image, 0, 0, this.clip.width, this.clip.height);
But they DON'T! and I can't figure out why. The first one scales the image to fill up this.clip, which is what I expect. But the second doesn't seem to scale to mater what this.clip is, and the image seems to go outside of its own bounds.
Now, this is an image I generated in javascript, so is there any way I could have screwed up the image creation such that this would happen?
If it helps. This below is the code I use to make the image.
this.image = new Image(this.getSize()[0],this.getSize()[1]);
//this.getSize() = [32, 42, 40];
var c = document.createElement('canvas');
var ctx = c.getContext('2d');
var pixels = ctx.createImageData(this.image.width, this.image.height);
for (var x = 0; x < this.image.width; x++) {
for (var y = 0; y < this.image.height; y++) {
var i = (x + y*this.image.width)*4;
var color = scalarToHue(this.sample(x,y,layer),0,10,50);
pixels.data[i] = Math.round(color[0]*255);
pixels.data[i + 1] = Math.round(color[1]*255);
pixels.data[i + 2] = Math.round(color[2]*255);
pixels.data[i + 3] = 255;
}
}
ctx.putImageData(pixels, 0, 0);
this.image.src = c.toDataURL("image.png");
Also, I'm using chrome.
This is because you are hard-coding your image's width and height properties by setting it in the Image(width, height) constructor.
ctx.drawImage(img, dx, dy [, dwidth ,dheight]) will make swidth and sheight default to your image's naturalWidth and naturalHeight, not to their width and height ones.
So in your code, this means that the first line will use 32 and 42 as values for swidth and sheight, while the second line will use 300 and 150 if I read your code correctly.
Ps: re-reading your code, it seems that all you want is to set your canvas's width and height to these values before drawing on it, then you'll just need the drawImage(x, y) version.
I have a 360 degree panoramic photo that I want transformed into a cube map.
In order to get the top and bottom faces, I cropped large rectangles from the top 25 percent of the photo and bottom 25% of the photo. The image below represents the bottom 25% of the 360 degree panoramic photo.
and I want to transform it into something like this
using HTML5 and Javascript.
The first image was cropped from a much larger one using this code
imagePieces = [];
numColsToCut = 1;
var numRowsToCut = 1;
var widthOfOnePiece = image.width;
var heightOfOnePiece = image.height / 4;
var startHeight = 0;
var canvas = document.createElement('canvas');
canvas.width = widthOfOnePiece;
canvas.height = heightOfOnePiece;
var context = canvas.getContext('2d');
console.log(image.height / 2);
context.drawImage(image, 0, startHeight, widthOfOnePiece, heightOfOnePiece, 0, 0, canvas.width, canvas.height);
imagePieces.push(canvas.toDataURL());
document.getElementById('myImageElementInTheDom6').src = imagePieces[0];
Before placing the cropped image into the DOM element, I want to 'warp' it into the bottom image. Any tips? :)
This affect here (image below) was achieved with a couple simple Photoshop steps takes, the colors parts were turn white, the background (various shades of white gray), was made transparent. Is it possible to achieve this with canvas?
The images inside the circles below is the final result.
The images were originally colored, like the 2nd from top image was this one:
See that circle in the middle, basically all the white was cut out in an aliased way.
Same with this zoho logo:
The 2nd from bottom was originally something like this:
Except the red R was just a Y in the middle and instead of all the text and green strip seen in image here, it just had some grainy texture in shades of gray around it. And via photoshop the Y was made trasnparent, and the texture and stamp was just made solid, removing the 3d shadow etc.
Putting this above yandex stamp through the photoshop algorithm gives this (i replaced the white with black for demo/visibility puproses)
This was jagged after the photoshop algorithm but in final application the image is reduced to around 80x80px and that makes it look real smooth and anti-aliased. So real final result is this which looks very decent.
The problem is multifaceted as there are regions which require different approaches, for example, the last image where the main text needs to be converted to white but keep transparency, while the bottom bar in the same image is solid but need the white text to be retained while the solid background to be removed.
It's doable by implementing tools to select regions and apply various operators manually - automatically will be a much larger challenge than it may appear to be.
You could make requirements to the user to only upload images with an alpha channel. For that you can simply replace each non-transparent pixel with white. It becomes more a policy issue than a technical one in my opinion.
For example
Taking the logo:
var img = new Image();
img.crossOrigin = "";
img.onload = process;
img.src = "http://i.imgur.com/HIhnb4A.png"; // load the logo
function process() {
var canvas = document.querySelector("canvas"), // canvas
ctx = canvas.getContext("2d"), // context
w = this.width, // image width/height
h = this.height,
idata, data32, len, i, px; // iterator, pixel etc.
canvas.width = w; // set canvas size
canvas.height = h;
ctx.drawImage(this, 0, 0); // draw in image
idata = ctx.getImageData(0, 0, w, h); // get imagedata
data32 = new Uint32Array(idata.data.buffer); // use uint32 view for speed
len = data32.length;
for(i = 0; i < len; i++) {
// extract alpha channel from a pixel
px = data32[i] & 0xff000000; // little-endian: ABGR
// any non-transparency? ie. alpha > 0
if (px) {
data32[i] = px | 0xffffff; // set this pixel to white, keep alpha level
}
}
// done
ctx.putImageData(idata, 0, 0);
}
body {background:gold}
<canvas></canvas>
Now the problem is easy to spot: the "#" character is just solid because there is no transparency behind it. To automate this would require first to knock out all whites, then apply the process demoed above. However, this may work in this single case but probably not be a good thing for most.
There will also be anti-aliasing issues as it's not possible to know how much of the white you want to knock out as we don't analyze the edges around the white pixels. Another possible challenge is ICC corrected image where white may not be white depending on ICC profile used, browser support and so forth.
But, it's doable to some degree - taking the code above with a prestep to knock out entirely white pixels for this logo:
var img = new Image();
img.crossOrigin = "";
img.onload = process;
img.src = "http://i.imgur.com/HIhnb4A.png"; // load the logo
function process() {
var canvas = document.querySelector("canvas"), // canvas
ctx = canvas.getContext("2d"), // context
w = this.width, h = this.height,
idata, data32, len, i, px; // iterator, pixel etc.
canvas.width = w; // set canvas size
canvas.height = h;
ctx.drawImage(this, 0, 0); // draw in image
idata = ctx.getImageData(0, 0, w, h); // get imagedata
data32 = new Uint32Array(idata.data.buffer); // use uint32 view for speed
len = data32.length;
for(i = 0; i < len; i++) {
px = data32[i]; // pixel
// is white? then knock it out
if (px === 0xffffffff) data32[i] = px = 0;
// extract alpha channel from a pixel
px = px & 0xff000000; // little-endian: ABGR
// any non-transparency? ie. alpha > 0
if (px) {
data32[i] = px | 0xffffff; // set this pixel to white, keep alpha level
}
}
ctx.putImageData(idata, 0, 0);
}
body {background:gold}
<canvas></canvas>
Use this
private draw(base64: string) {
// example size
const width = 200;
const height = 70;
const image = new Image();
image.onload = () => {
const canvas = document.createElement("canvas");
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0);
const imageData = ctx.getImageData(0, 0, width, height);
for (let x = 0; x < imageData.width; x++) {
for (let y = 0; y < imageData.height; y++) {
const offset = (y * imageData.width + x) * 4;
const r = imageData.data[offset];
const g = imageData.data[offset + 1];
const b = imageData.data[offset + 2];
// if it is pure white, change its alpha to 0
if (r == 255 && g == 255 && b == 255) {
imageData.data[offset + 3] = 0;
}
}
}
ctx.putImageData(imageData, 0, 0);
// output base64
const result = canvas.toDataURL();
};
image.src = base64;
}
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