Jakob
Jakob

Reputation: 151

How to fix warped/distorted and cut-out images in HTML canvas object with javascript?

I'm trying to cut out the largest possible square area of an image with the draw image function of a canvas object in HTML5 with JavaScript. Also, the cut-out area should be the center of the original image.

(I am also rendering a watermark on top of the image.)

This works fine without trying to cut out the center of the image, but whenever I tried to do this, the result gets warped.

This is the warped image and this how the original looks like

An example for the warped image is also given in the code snippet below!

To cut out a part of an image, you have to declare two coordinates P1 and P2 with for each 2 variables.

The coordinate system is like this (i think):

To calculate P1 and P2 I am using:

imagewidth < imageheight


P1:
----
x1 = 0
y1 = (imageheight / 2) - (imagewidth / 2)
// if I'm replacing y1 with 0, it wont get warped
// but it will cut out the top of the image

and

P2:
----
x2 = imagewidth
y2 = y1 + imagewidth

imagewidth > imageheight


P1:
----
x1 = (imagewidth / 2) - (imageheight / 2)
y1 = 0
// if I'm replacing x1 with 0, it wont get warped
// but it will cut out the most left part of the image

and

P2:
----
x2 = x1 + imageheight
y2 = imageheight

Here is my code:

basically it writes into this object:

<div id="buy_imagelist" class=imagelist></div>

javascript:

var numbers = ["images/test_images/bluefireoly-2.jpg"];
var txt = "";
var numCanvas = 0;

for(var src in numbers) {
    numCanvas++;
}

var i = 0;
for(var src in numbers) {
    txt = txt + '<canvas class=imagelistsingle style=padding:7px id=myCanvas' + i + ' width="300" height="300"></canvas>';
    i++;
}

document.getElementById('buy_imagelist').innerHTML = txt;
var imageNumber_canvas = 0;

numbers.forEach(myFunction);

function myFunction(value, index, array) {

    var canvas = document.getElementById('myCanvas' + imageNumber_canvas);
    if(canvas == null) {
        window.alert("Error - Images could not be loaded");
    }
    var context = canvas.getContext('2d');

    function loadImages(imagelist, callback) {
        var images = {};
        var loadedImages = 0;
        var numImages = 0;

        for(var src in imagelist) {
            numImages++;
        }

        for(var src in imagelist) {

            images[src] = new Image();
            images[src].onload = function() {
                if(++loadedImages >= numImages) {
                    callback(images);
                }
            };
            images[src].src = imagelist[src];

        }

    }

    var imagelist = {
        testimage: value,
        wasserzeichen: "images/wasserzeichen/wasserzeichen_1.png"
    };

    loadImages(imagelist, function(images) {

        var testimage = images.testimage;

        var imagewidth = testimage.naturalWidth;
        var imageheight = testimage.naturalHeight;

        var x1 = 0;
        var y1 = 0;
        var x2 = imagewidth;
        var y2 = imageheight;

        if(imagewidth !== imageheight) {

            if(imageheight < imagewidth) {

                x1 = (imagewidth / 2) - (imageheight / 2);
                y1 = 0;

                x2 = x1 + imageheight;
                y2 = imageheight;

            } else {

                x1 = 0;
                y1 = (imageheight / 2) - (imagewidth / 2);

                x2 = imagewidth;
                y2 = y1 + imagewidth;

            }

        }

        context.drawImage(testimage, x1, y1, x2, y2, 0, 0, 300, 300);
        context.drawImage(images.wasserzeichen, 0, 0, 300, 300);
    });

    imageNumber_canvas++;

}

As expected, this works fine for square images.

The error must be somewhere here (expecially where I marked it with "//!")

var testimage = images.testimage;

var imagewidth = testimage.naturalWidth;
var imageheight = testimage.naturalHeight;

var x1 = 0;
var y1 = 0;
var x2 = imagewidth;
var y2 = imageheight;

if(imagewidth !== imageheight) {

    if(imageheight < imagewidth) {

        x1 = (imagewidth / 2) - (imageheight / 2); //!
        y1 = 0;

        x2 = x1 + imageheight;
        y2 = imageheight;

    } else {

        x1 = 0;
        y1 = (imageheight / 2) - (imagewidth / 2); //!

        x2 = imagewidth;
        y2 = y1 + imagewidth;

    }

}

context.drawImage(testimage, x1, y1, x2, y2, 0, 0, 300, 300);
context.drawImage(images.wasserzeichen, 0, 0, 300, 300);

For the non-square image given above (1028x1920) (width < height)

-> y1 = 446
-> so y2 = 446 + 1028 = 1474

so y1 > 0
so y2 < height
-> cut-out square should fit in the original image

Use this code to try it out for yourself: (click on "full page" to see the image)

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Example</title>
</head>

<body>

  <div id="buy_imagelist" class=imagelist></div>

  <script>
    var numbers = ["https://i.pinimg.com/originals/46/1c/01/461c0133da95e315cd4e245f79219ec0.jpg"];
    var txt = "";
    var numCanvas = 0;

    for (var src in numbers) {
      numCanvas++;
    }

    var i = 0;
    for (var src in numbers) {
      txt = txt + '<canvas class=imagelistsingle style=padding:7px id=myCanvas' + i + ' width="300" height="300"></canvas>';
      i++;
    }

    document.getElementById('buy_imagelist').innerHTML = txt;
    var imageNumber_canvas = 0;

    numbers.forEach(myFunction);

    function myFunction(value, index, array) {

      var canvas = document.getElementById('myCanvas' + imageNumber_canvas);
      if (canvas == null) {
        window.alert("Error - Images could not be loaded");
      }
      var context = canvas.getContext('2d');

      function loadImages(imagelist, callback) {
        var images = {};
        var loadedImages = 0;
        var numImages = 0;

        for (var src in imagelist) {
          numImages++;
        }

        for (var src in imagelist) {

          images[src] = new Image();
          images[src].onload = function() {
            if (++loadedImages >= numImages) {
              callback(images);
            }
          };
          images[src].src = imagelist[src];

        }

      }

      var imagelist = {
        testimage: value
      };

      loadImages(imagelist, function(images) {

        var testimage = images.testimage;

        var imagewidth = testimage.naturalWidth;
        var imageheight = testimage.naturalHeight;

        var x1 = 0;
        var y1 = 0;
        var x2 = imagewidth;
        var y2 = imageheight;

        if (imagewidth !== imageheight) {

          if (imageheight < imagewidth) {

            x1 = (imagewidth / 2) - (imageheight / 2);
            y1 = 0;

            x2 = x1 + imageheight;
            y2 = imageheight;

          } else {

            x1 = 0;
            y1 = (imageheight / 2) - (imagewidth / 2);

            x2 = imagewidth;
            y2 = y1 + imagewidth;

          }

        }

        context.drawImage(testimage, x1, y1, x2, y2, 0, 0, 300, 300);

      });

      imageNumber_canvas++;

    }
  </script>

</body>

</html>

Upvotes: 2

Views: 762

Answers (1)

Thomas Altmann
Thomas Altmann

Reputation: 1744

The 4th and 5th argument of the drawImage method are width and height, not x2 and y2.

The documentation on MDN uses this in the first example

ctx.drawImage(image, 33, 71, 104, 124, 21, 20, 87, 104);

with the explanation

The source image is taken from the coordinates (33, 71), with a width of 104 and a height of 124. It is drawn to the canvas at (21, 20), where it is given a width of 87 and a height of 104.

tl;dr

You have to provide the width/height of the cutout image, not the coordinates of the bottom right corner:

context.drawImage(testimage, x1, y1, x2 - x1, y2 - y1, 0, 0, 300, 300);

Upvotes: 2

Related Questions