I want to wrap an image around a cylindrical object like mugs in a web app, like so
This will likely be a base image (e.g. a jpeg image of a mug) containin
Very simple example using sin and cos to create the curved map. The images is cut into strips approx 1 pixel wide then rendered as half a squashed circle. As perspective is a linear effect related to distance I also add a small amount of perspective by scaling up in the y direction depending on sin(angle) (where angle = 0 on the left Math.PI / 2 at the forward center).
These two demos are animated just to show that it is not a slow process, but compared to webGL it is a snail. If you use such a method, don't make it realtime or you will chew up the batteries of mobile devices. Realtime 3D should be done with webGL
var createImage=function(w,h){var i=document.createElement("canvas");i.width=w;i.height=h;i.ctx=i.getContext("2d");return i;}
var canvas = createImage(400,400);
var ctx = canvas.ctx;
document.body.appendChild(canvas)
ctx.clearRect(0,0,500,500)
var image = createImage(400,200);
image.ctx.font = "60px arial";
image.ctx.textAlign = "center";
image.ctx.fillStyle = "#7F5";
image.ctx.fillRect(0,0,image.width,image.height)
image.ctx.fillStyle = "white";
image.ctx.fillText("Wrap around",200,60)
image.ctx.fillText("Some images",200,140)
function draw(ang,tilt, perspective){
var step = 1/(Math.max(image.width,400));
for(var i = 0; i < 1; i += step){
var a = i * Math.PI;
var a1 = (i+ step*2) * Math.PI ;
var ix = i * image.width*1.2;
var iw = step * image.width*1.2;
a += ang * Math.PI * 2;
a1 += ang * Math.PI * 2;
a = Math.PI -a;
a1 = Math.PI -a1;
var x = canvas.width * 0.5;
var y = canvas.height * 0.1;
var x1 = x + Math.cos(a1) * 110;
var y1 = y + Math.sin(a) * tilt;
x += Math.cos(a) * 110;
y += Math.sin(a) * tilt;
var s = Math.sin(a);
var s1 = Math.sin(a1);
if(s > 0 || s1 > 0){
ctx.drawImage(image,ix,0,iw,image.height,x1,y- s * perspective*0.5,x-x1,200 + s * perspective)
}
}
}
var w = canvas.width;
var h = canvas.height;
// main update function
function update(timer){
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.fillStyle = "black"
ctx.fillRect(0,0,w,h);
draw(timer / 2000, 40,30)
requestAnimationFrame(update);
}
requestAnimationFrame(update);
To extend it a little further you can add overlays to do lighting. I could not find a public domain image of a white cup so I used the same function to render the lighting (A few simple gradients) onto and image. Then for the final output I render the overlay as a backing image, then the text, then two passes again with the shading image, first darken with "multiply", then a soft highlight with "lighten"
var createImage=function(w,h){var i=document.createElement("canvas");i.width=w;i.height=h;i.ctx=i.getContext("2d");return i;}
var canvas = createImage(400,400);
var ctx = canvas.ctx;
document.body.appendChild(canvas)
ctx.clearRect(0,0,500,500)
var image = createImage(400,200);
image.ctx.font = "60px arial";
image.ctx.textAlign = "center";
image.ctx.fillStyle = "#999";
image.ctx.fillRect(0,10,image.width,image.height-20)
image.ctx.fillStyle = "white";
image.ctx.fillText("Wrap around",200,60)
image.ctx.fillText("Some images",200,140)
//---------------------------------------------------------------------
// create shading map
var shading = createImage(400,200);
// left to right shading
var g1 = shading.ctx.createLinearGradient(0,0,400,0);
g1.addColorStop(0,"rgba(245,245,245,1)");
g1.addColorStop(0.05,"rgba(255,255,255,1)");
g1.addColorStop(0.5,"rgba(230,230,230,1)");
g1.addColorStop(0.95,"rgba(255,255,255,1)");
g1.addColorStop(1,"rgba(245,245,245,1)");
shading.ctx.fillStyle = g1;
shading.ctx.fillRect(0,0,400,200);
// bottom to top shading
var g = shading.ctx.createLinearGradient(0,0,0,200);
g.addColorStop(1,"rgba(200,200,200,1)");
g.addColorStop(0.95,"rgba(200,200,200,0.4)");
g.addColorStop(0,"rgba(255,255,255,0.0)");
shading.ctx.globalCompositeOperation = "multiply";
shading.ctx.fillStyle = g;
shading.ctx.fillRect(0,0,400,200);
var g = shading.ctx.createRadialGradient(0,-100,100,0,-100,200);
g.addColorStop(0,"rgba(200,200,200,1)");
g.addColorStop(0.95,"rgba(255,255,255,1)");
g.addColorStop(1,"rgba(255,255,255,0)");
shading.ctx.fillStyle = g;
shading.ctx.globalCompositeOperation = "screen";
shading.ctx.setTransform(1.4,0,0,1,200,0);
shading.ctx.beginPath();
shading.ctx.arc(0,-100,200,0,Math.PI * 2);
shading.ctx.globalAlpha = 0.5;
shading.ctx.fill();
shading.ctx.setTransform(1,0,0,1,0,0);
shading.ctx.fillStyle = g1;
shading.ctx.fillRect(0,0,400,200);
var overlay = createImage(400,400);
draw(shading,overlay.ctx,0, 40,30,110,200,1);
function draw(image,ctx,ang,tilt, perspective, width, height,stretch){
var step = 1/(Math.max(image.width,400));
for(var i = 0; i < 1; i += step){
var a = i * Math.PI;
var a1 = (i+ step*2) * Math.PI ;
var ix = i * image.width*stretch;
var iw = step * image.width*stretch;
a += ang * Math.PI * 2;
a1 += ang * Math.PI * 2;
a = Math.PI -a;
a1 = Math.PI -a1;
var x = canvas.width * 0.5;
var y = canvas.height * 0.1;
var x1 = x + Math.cos(a1) * width;
var y1 = y + Math.sin(a) * tilt;
x += Math.cos(a) * width;
y += Math.sin(a) * tilt;
var s = Math.sin(a);
var s1 = Math.sin(a1);
if(s > 0 || s1 > 0){
ctx.drawImage(image,ix,0,iw,image.height,x1,y- s * perspective*0.5,(x-x1-1),height + s * perspective)
}
}
}
var w = canvas.width;
var h = canvas.height;
// main update function
function update1(timer){
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.fillStyle = "black"
ctx.fillRect(0,0,w,h);
ctx.drawImage(overlay,0,0);
draw(image,ctx,timer / 4000, 40,30,110,200,1)
ctx.globalCompositeOperation = "multiply";
ctx.drawImage(overlay,0,0);
ctx.globalAlpha = 0.2
ctx.globalCompositeOperation = "lighten";
ctx.drawImage(overlay,0,0);
ctx.globalCompositeOperation = "source-over";
requestAnimationFrame(update1);
}
requestAnimationFrame(update1);
function canvas1() {
var canvas = document.getElementById("canvas1");
var ctx = canvas.getContext("2d");
var productImg = new Image();
productImg.onload = function() {
var iw = productImg.width;
var ih = productImg.height;
console.log("height");
canvas.width = iw;
canvas.height = ih;
ctx.drawImage(productImg, 0, 0, productImg.width, productImg.height,
0, 0, iw, ih);
loadUpperIMage()
};
productImg.src = "http://res.cloudinary.com/pussyhunter/image/upload/c_scale,f_auto,h_350/left_handle_cup_i7ztfs.jpg"
function loadUpperIMage() {
var img = new Image();
img.src = "http://res.cloudinary.com/pussyhunter/image/upload/v1488184107/500_F_97150423_M13q2FeAUZxxIx6CaPixHupprmyiVVli_skh6fe.jpg"
img.onload = function() {
var iw = img.width;
var ih = img.height;
var xOffset = 102, //left padding
yOffset = 110; //top padding
//alert(ih)
var a = 75.0; //image width
var b = 10; //round ness
var scaleFactor = iw / (4 * 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
ctx.drawImage(img, X * scaleFactor, 0, iw / 9, ih, X + xOffset, y + yOffset, 1, 174);
}
};
}
};
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;
console.log("height");
canvas.width = iw;
canvas.height = ih;
ctx.drawImage(productImg, 0, 0, productImg.width, productImg.height,
0, 0, iw, ih);
loadUpperIMage()
};
productImg.src = "http://res.cloudinary.com/pussyhunter/image/upload/h_350/canter_handle_cup_xyxhdd.jpg"
function loadUpperIMage() {
var img = new Image();
img.src = "http://res.cloudinary.com/pussyhunter/image/upload/v1488184107/500_F_97150423_M13q2FeAUZxxIx6CaPixHupprmyiVVli_skh6fe.jpg"
img.onload = function() {
var iw = img.width;
var ih = img.height;
// alert(iw)
var xOffset = 101, //left padding
yOffset = 110; //top padding
var a = 75.0; //image width
var b = 10; //round ness
var scaleFactor = iw / (4 * 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
ctx.drawImage(img, X * scaleFactor, 0, iw / 3, ih, X + xOffset, y + yOffset, 1, 174);
}
};
}
};
function canvas3() {
var canvas = document.getElementById("canvas3");
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 = "http://res.cloudinary.com/pussyhunter/image/upload/h_350/right_handle_cup_dsdhr7.jpg"
function loadUpperIMage() {
var img = new Image();
img.src = "http://res.cloudinary.com/pussyhunter/image/upload/v1488184107/500_F_97150423_M13q2FeAUZxxIx6CaPixHupprmyiVVli_skh6fe.jpg"
img.onload = function() {
var iw = img.width;
var ih = img.height;
//alert(iw)
var xOffset = 102, //left padding
yOffset = 110; //top padding
var a = 75.0; //image width
var b = 10; //round ness
var scaleFactor = iw / (3 * 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
ctx.drawImage(img, X * scaleFactor, 0, iw / 1.5, ih, X + xOffset, y + yOffset, 1, 174);
}
};
}
};
setTimeout(function() {
canvas1()
}, 1000);
setTimeout(function() {
canvas2()
}, 2000);
setTimeout(function() {
canvas3()
}, 3000);
<!DOCTYPE html>
<html>
<head>
<script data-require="jquery@*" data-semver="2.1.4" src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body>
<div>
<canvas id="canvas1"></canvas>
</div>
<div>
<canvas id="canvas2"></canvas>
</div>
<div>
<canvas id="canvas3"></canvas>
</div>
</body>
</html>
Note : Just Use these points to calibrate
var scaleFactor = iw / (4*a); //EDIT 4*a TO 6*a
ctx.drawImage(img, X * scaleFactor, 0, iw/3, ih, X + xOffset, y + yOffset, 1, 174); //EDIT iw/3 TO iw/4