【问题标题】:Wrap an image around a cylindrical object in HTML5 / JavaScript在 HTML5 / JavaScript 中将图像包裹在圆柱形对象周围
【发布时间】:2017-04-21 05:01:02
【问题描述】:

我想在 web 应用程序中将图像包裹在一个圆柱形物体(如杯子)周围,就像这样

这可能是包含用户上传图像的转换图像的基本图像(例如杯子的 jpeg 图像)。

PhotoShop 中似乎有很多这方面的资源。但是,这当然不适用于 Web、移动或服务器环境。

我也知道这是可能的,因为许多网站已经做得非常好。例如,Vista Print(见图)、Asda Photos(只需在 Google 上搜索个性化马克杯即可在互联网上加载更多内容),使用的似乎只是 HTML5。

但是,奇怪的是,我似乎无法为 Web 应用程序找到任何答案。 StackOverflow 中有很多关于此的问题都没有得到解答,例如: Wrap an image around a cylinderwrap image around a cylindrical cup using html 5 canvas and javascriptHow overlay image over a cup using html5 canvas 等等!

因此,请有人最终提供这个问题的答案。

【问题讨论】:

  • 为什么投反对票?
  • 使用 webGL 和 Three.js。或者您可以使用 2D 画布并将图像切成 1 像素宽的条带。然后将它们映射到sin(ang)*tilt 的y 位置和cos(ang)*mugRadius 的x 位置。将每个图像的杯子旋转量添加到ang 以匹配杯子旋转。如果sin(ang) < 0 你知道不要画条
  • @YahyaUddin:纹理映射是将图像包裹在一般(甚至不可展开)表面的技术。当然,如果您只需要处理特定的表面,那么“假”解决方案可能是更好的选择。
  • 我有一个脚本可以使用 ImageMagick 做类似的事情。见fmwconcepts.com/imagemagick/cylinderize/index.php

标签: image opengl canvas 3d imagick


【解决方案1】:

使用 Canvas 2D 的简单圆柱体包裹

使用 sin 和 cos 创建曲线图的非常简单的示例。图像被切成大约 1 像素宽的条带,然后呈现为半个压扁的圆圈。由于透视是与距离相关的线性效应,我还通过根据 sin(angle) 在 y 方向上放大来添加少量透视(其中角度 = 0 在左侧 Math.PI / 2 在前中心)。

这两个演示动画只是为了表明它不是一个缓慢的过程,但与 webGL 相比它是一个蜗牛。如果你使用这种方法,不要让它实时,否则你会消耗移动设备的电池。实时 3D 应该使用 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);

添加照明。

要进一步扩展它,您可以添加叠加层来进行照明。我找不到白色杯子的公共领域图像,所以我使用相同的函数将照明(一些简单的渐变)渲染到图像上。然后对于最终输出,我将叠加层渲染为背景图像,然后是文本,然后再次使用阴影图像进行两次传递,首先使用“乘法”变暗,然后使用“变亮”进行软高光

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);

【讨论】:

  • 哇,看起来棒极了。绝对让我走上正轨!
  • @YahyaUddin 我知道你也想要一些灯光效果。因此,我添加了另一个示例,展示了如何在 2D 中使用复合操作来完成。
【解决方案2】:

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>

注意:只需使用这些点来校准

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

【讨论】:

    猜你喜欢
    • 2014-12-16
    • 1970-01-01
    • 2018-03-28
    • 2014-10-03
    • 2011-08-23
    • 1970-01-01
    • 2020-08-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多