1

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 :/

Mug image with left handle

Mug image with right handle

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/

1 Answers1

2

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

enter image description here

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.

Michael Radionov
  • 12,859
  • 1
  • 55
  • 72
  • Thanks for your answer Michael. However `iw` represents the width of the image so for example if the image is 1080px wide the value of `iw / 0.78` is equal to 1384.62. If I understood correctly this is the value that determines how much of the image will be drawn. The value you are referring to which should be 1px is the parameter before last `dWidth`. Although it looks OK when you change the value to `iw / 20` this is not the case as for it to be correct the right part of the original image should be the end of the drawn canvas. – Alex Portelli May 02 '20 at 18:52
  • Yes, if your `sWidth = 1384.64` and `dWidth = 1`, you are drawing more than entire image into one pixel for each call of `ctx.drawImage()`. I guess it will help to know why you changed this value to `0.78` and what are you trying to achieve. – Michael Radionov May 02 '20 at 19:03
  • I came to `0.78` by tweaking the value, so that the right side of the image starts from the right side of the mug. However as you pointed out it does not make sense. In the end I am trying to simulate the image being wrapped around the mug as it would be in real life. But I really do not know what is making the image transparent/distorted on the right side. I have been fighting with this all day, and cannot seem to get to the bottom of it! – Alex Portelli May 02 '20 at 19:09
  • If you want to move the image you should tweak `sx` and `sy` parameter - it will be the top-left corner of the rectangle from the source image. For example you could use `sx = (X * scaleFactor) + 400`. I've added an explanation image to my answer how source and destination rectangles relate to each other, maybe it will help a little bit. Transparency comes as a side-effect of tweaking the wrong parameter. – Michael Radionov May 02 '20 at 19:15
  • Worked like a charm!! Thanks so much for your help! – Alex Portelli May 02 '20 at 19:23