html2canvas icon indicating copy to clipboard operation
html2canvas copied to clipboard

Image filled even div set to object-fit: cover;

Open LangRizkie opened this issue 7 years ago • 23 comments

http://imgur.com/3Zl6oRA | Pic 1 http://imgur.com/Wr006qF | Pic 2

Pic 1 is the div i want to capture, after it captured the image change its size *Pic 2.

Anyone could fix this ?

LangRizkie avatar Feb 27 '17 17:02 LangRizkie

Hello, just to let you know that I needed this and hacked it to work in my project. I provide the code which works for me below. However this is a hack, I don't have the knowledge to integrate this into your project in a more proper way:

    key: 'drawImage',
      value: function drawImage(image, source, destination) {
        /**START CUSTOM CODE**/

          var newWidth = 30;
          var newHeight = 30;
          var newX = destination.left;
          var newY = destination.top;

          // console.log(image, source, destination);
          if (source.width/destination.width > source.height/destination.height) {
              newWidth = destination.width;
              newHeight = source.height * (destination.width / source.width);
              newY = destination.top + (destination.height - newHeight) / 2;
          } else {
              newWidth = source.width * (destination.height / source.height);
              newHeight = destination.height;
              newX = destination.left + (destination.width - newWidth) / 2;
          }
        // console.log(newWidth, newHeight);

          this.ctx.drawImage(image, source.left, source.top, source.width, source.height,
            newX, newY,
            newWidth, newHeight);
          // destination.width,
          // destination.height * (source.height / source.width)
          //   destination.width, destination.height);

        /**END CUSTOM CODE**/
      }

LanderBeeuwsaert avatar May 27 '18 12:05 LanderBeeuwsaert

Take LanderBeeuwsaert's solution (by downloading html2canvas.js so you can edit its code like they did) and replace

if (source.width/destination.width > source.height/destination.height) {

with

if (source.width/destination.width < source.height/destination.height) {

That should get you your desired results. It worked for me, at least.

ColinAmos avatar Jun 29 '18 13:06 ColinAmos

Any update on this?

miwelc avatar Dec 19 '18 18:12 miwelc

@LanderBeeuwsaert muchas gracias funciono perfecto , deberian agregarlo a la master es un muy buen aporte

@niklasvh Seems you've been updating the project. I would be eager to get this one in. Let me know if you would need more exact code than this somehow.

LanderBeeuwsaert avatar Apr 09 '19 08:04 LanderBeeuwsaert

@LanderBeeuwsaert where do i suppose to implement this hack of yours?

PetrovStark avatar Oct 17 '19 22:10 PetrovStark

@SamuraiPetrus Hey, yeah, the structure of the project has been completely changed from 0.5 to 1.0.0 due to conversion to typescript. I've searched a time ago where it should go in 1.0.0 so I coul possibly do a PR, but didn't find it. So I'm just still using 0.5 with this hack.

LanderBeeuwsaert avatar Oct 17 '19 22:10 LanderBeeuwsaert

Use version 1.0.0-alpha.9 and add a patch-package with @LanderBeeuwsaert code like this one html2canvas+1.0.0-alpha.9.patch:

diff --git a/node_modules/html2canvas/dist/html2canvas.js b/node_modules/html2canvas/dist/html2canvas.js
index 5f34081..66d1909 100644
--- a/node_modules/html2canvas/dist/html2canvas.js
+++ b/node_modules/html2canvas/dist/html2canvas.js
@@ -2443,7 +2443,22 @@ var CanvasRenderer = function () {
     }, {
         key: 'drawImage',
         value: function drawImage(image, source, destination) {
-            this.ctx.drawImage(image, source.left, source.top, source.width, source.height, destination.left, destination.top, destination.width, destination.height);
+            var newWidth = 30;
+            var newHeight = 30;
+            var newX = destination.left;
+            var newY = destination.top;
+
+            if (source.width/destination.width < source.height/destination.height) {
+                newWidth = destination.width;
+                newHeight = source.height * (destination.width / source.width);
+                newY = destination.top + (destination.height - newHeight) / 2;
+            } else {
+                newWidth = source.width * (destination.height / source.height);
+                newHeight = destination.height;
+                newX = destination.left + (destination.width - newWidth) / 2;
+            }
+
+            this.ctx.drawImage(image, source.left, source.top, source.width, source.height, newX, newY, newWidth, newHeight);
         }
     }, {
         key: 'drawShape',

Don't forget to import the version that it is not minified by providing the full path:

const html2canvas = require('html2canvas/dist/html2canvas.js');

diogocapela avatar Jul 07 '20 13:07 diogocapela

I cannot install the alpha build and patch-package isn't working for me. ~~Is there not a way to get this implemented? It's the only thing stopping my app from being finished.~~

Suitable workaround for my situation is to just use background images.

hyperjerk avatar Sep 10 '20 15:09 hyperjerk

Hello, just to let you know that I needed this and hacked it to work in my project. I provide the code which works for me below. However this is a hack, I don't have the knowledge to integrate this into your project in a more proper way:

    key: 'drawImage',
      value: function drawImage(image, source, destination) {
        /**START CUSTOM CODE**/

          var newWidth = 30;
          var newHeight = 30;
          var newX = destination.left;
          var newY = destination.top;

          // console.log(image, source, destination);
          if (source.width/destination.width > source.height/destination.height) {
              newWidth = destination.width;
              newHeight = source.height * (destination.width / source.width);
              newY = destination.top + (destination.height - newHeight) / 2;
          } else {
              newWidth = source.width * (destination.height / source.height);
              newHeight = destination.height;
              newX = destination.left + (destination.width - newWidth) / 2;
          }
        // console.log(newWidth, newHeight);

          this.ctx.drawImage(image, source.left, source.top, source.width, source.height,
            newX, newY,
            newWidth, newHeight);
          // destination.width,
          // destination.height * (source.height / source.width)
          //   destination.width, destination.height);

        /**END CUSTOM CODE**/
      }

Where should i need to paste this code?

nurulalamador avatar Sep 19 '20 07:09 nurulalamador

After a bit of fiddling, I managed to get it to work for my purposes in the current version.

        CanvasRenderer.prototype.renderReplacedElement = function (container, curves, image) {
        if (image && container.intrinsicWidth > 0 && container.intrinsicHeight > 0) {
            var box = contentBox(container);
            /*CUSTOM CODE*/
            var newWidth = 30;
            var newHeight = 30;
            var newX = box.left;
            var newY = box.top;

            if (container.intrinsicWidth / box.width < container.intrinsicHeight / box.height) {
                    newWidth = box.width;
                    newHeight = container.intrinsicHeight * (box.width / container.intrinsicWidth);
                    newY = box.top + (box.height - newHeight) / 2;
            }
            else {
                newWidth = container.intrinsicWidth * (box.height / container.intrinsicHeight);
                newHeight = box.height;
                newX = box.left + (box.width - newWidth) / 2;
            }
            var path = calculatePaddingBoxPath(curves);
            this.path(path);
            this.ctx.save();
            this.ctx.clip();
            this.ctx.drawImage(image, 0, 0, container.intrinsicWidth, container.intrinsicHeight, newX, newY, newWidth, newHeight);
            this.ctx.restore();
        }
    };

Search for the first few lines in the non-minified file. Thanks to @LanderBeeuwsaert for the base code.

canoncarlson avatar Sep 30 '20 21:09 canoncarlson

@canoncarlson my pleasure, thanks for the update. We'll integrate it and be able to update in the coming weeks because of this.

LanderBeeuwsaert avatar Oct 03 '20 12:10 LanderBeeuwsaert

I've managed to get it working also, change CanvasRenderer.prototype.renderReplacedElement in node_modules/html2canvas/dist/html2canvas.js around line 6274, I'm using RC 5 version, it is basically the same code as @canoncarlson, just to confirm that object-fit cover is working with this modification

CanvasRenderer.prototype.renderReplacedElement = function (container, curves, image) {
  if (image && container.intrinsicWidth > 0 && container.intrinsicHeight > 0) {
      var box = contentBox(container);
      var path = calculatePaddingBoxPath(curves);

      this.path(path);
      this.ctx.save();
      this.ctx.clip();

      let newWidth;
      let newHeight;
      let newX = box.left;
      let newY = box.top;

      if(container.intrinsicWidth / box.width < container.intrinsicHeight / box.height) {
        newWidth = box.width;
        newHeight = container.intrinsicHeight * (box.width / container.intrinsicWidth);
        newY = box.top + (box.height - newHeight) / 2;
      } else {
        newWidth = container.intrinsicWidth * (box.height / container.intrinsicHeight);
        newHeight = box.height;
        newX = box.left + (box.width - newWidth) / 2;
      }

      this.ctx.drawImage(image, 0, 0, container.intrinsicWidth, container.intrinsicHeight, newX, newY, newWidth, newHeight);
      this.ctx.restore();
  }
};

Thanks to @LanderBeeuwsaert for initial solution, and hope this fix end up in main version soon.

plavet avatar Nov 23 '20 15:11 plavet

Any updates on the issue?

pavliukpetro avatar Jan 25 '22 10:01 pavliukpetro

For someone who are hesitated to override files in node_modules because of their installation script running every single time the app deployed : use this tool to keep your monkey-patch to be persisted

ENvironmentSet avatar Sep 06 '22 03:09 ENvironmentSet

Where should i need to paste this code? I don't find any non-minified file

Kamlesh-62 avatar Sep 16 '22 15:09 Kamlesh-62

@Kamlesh-62 if you have installed this module in node_modules, you may find non-minified file in following paths.

/node_modules/html2canvas/dist/html2canvas.esm.js /node_modules/html2canvas/dist/html2canvas.js

ENvironmentSet avatar Sep 17 '22 15:09 ENvironmentSet

I've managed to get it working also, change CanvasRenderer.prototype.renderReplacedElement in node_modules/html2canvas/dist/html2canvas.js around line 6274, I'm using RC 5 version, it is basically the same code as @canoncarlson, just to confirm that object-fit cover is working with this modification

CanvasRenderer.prototype.renderReplacedElement = function (container, curves, image) {
  if (image && container.intrinsicWidth > 0 && container.intrinsicHeight > 0) {
      var box = contentBox(container);
      var path = calculatePaddingBoxPath(curves);

      this.path(path);
      this.ctx.save();
      this.ctx.clip();

      let newWidth;
      let newHeight;
      let newX = box.left;
      let newY = box.top;

      if(container.intrinsicWidth / box.width < container.intrinsicHeight / box.height) {
        newWidth = box.width;
        newHeight = container.intrinsicHeight * (box.width / container.intrinsicWidth);
        newY = box.top + (box.height - newHeight) / 2;
      } else {
        newWidth = container.intrinsicWidth * (box.height / container.intrinsicHeight);
        newHeight = box.height;
        newX = box.left + (box.width - newWidth) / 2;
      }

      this.ctx.drawImage(image, 0, 0, container.intrinsicWidth, container.intrinsicHeight, newX, newY, newWidth, newHeight);
      this.ctx.restore();
  }
};

Thanks to @LanderBeeuwsaert for initial solution, and hope this fix end up in main version soon.

Why did it change without any changes, the version number is 1.4.1

Iitianci avatar Jul 01 '23 03:07 Iitianci

For those who don't bother to patch the package, here's a hack based on plavet's solution. Just create a patched canvas and pass it to html2canvas:

const root = document.body

// we need the scale to manually resize the canvas as well as passing it to html2canvas
const scale = window.devicePixelRatio

const canvas = document.createElement("canvas")
canvas.width = root.offsetWidth * scale
canvas.height = root.offsetHeight * scale

const ctx = canvas.getContext("2d")
const originalDrawImage = ctx.drawImage

ctx.drawImage = function (image, sx, sy, sw, sh, dx, dy, dw, dh) {
  if (image instanceof HTMLImageElement) {
    if (sw / dw < sh / dh) {
      const _dh = dh
      dh = sh * (dw / sw)
      dy = dy + (_dh - dh) / 2
    } else {
      const _dw = dw
      dw = sw * (dh / sh)
      dx = dx + (_dw - dw) / 2
    }
  }

  return originalDrawImage.call(ctx, image, sx, sy, sw, sh, dx, dy, dw, dh)
}

await html2canvas(root, {
  canvas,
  scale,
})

~~I'm not sure if this works in all cases though, it just worked well in my project.~~

Edit: this applies object-fit: cover to every image. If you change if (sw / dw < sh / dh) to if (dw / sw < dh / sh) it will instead apply object-fit: contain to every image.

guansss avatar Nov 28 '23 10:11 guansss

For those who don't bother to patch the package, here's a hack based on plavet's solution. Just create a patched canvas and pass it to html2canvas:

const root = document.body

// we need the scale to manually resize the canvas as well as passing it to html2canvas
const scale = window.devicePixelRatio

const canvas = document.createElement("canvas")
canvas.width = root.offsetWidth * scale
canvas.height = root.offsetHeight * scale

const ctx = canvas.getContext("2d")
const originalDrawImage = ctx.drawImage

ctx.drawImage = function (image, sx, sy, sw, sh, dx, dy, dw, dh) {
  if (image instanceof HTMLImageElement) {
    if (sw / dw < sh / dh) {
      const _dh = dh
      dh = sh * (dw / sw)
      dy = dy + (_dh - dh) / 2
    } else {
      const _dw = dw
      dw = sw * (dh / sh)
      dx = dx + (_dw - dw) / 2
    }
  }

  return originalDrawImage.call(ctx, image, sx, sy, sw, sh, dx, dy, dw, dh)
}

await html2canvas(root, {
  canvas,
  scale,
})

I'm not sure if this works in all cases though, it just worked well in my project.

I think there are a mistake in if (sw / dw < sh / dh) , I replace with if (dw / sw < dh / sh) and works for object-fit

dimitrycastex avatar Dec 13 '23 18:12 dimitrycastex

@dimitrycastex actually the original code works for object-fit: cover only, and changing it to if (dw / sw < dh / sh) works for object-fit: contain only. Added a note for that.

There seems no simple patch or hack that works for both. Using a fork like in https://github.com/niklasvh/html2canvas/pull/2904 would be better.

guansss avatar Dec 14 '23 06:12 guansss

For those who don't bother to patch the package, here's a hack based on plavet's solution. Just create a patched canvas and pass it to html2canvas:

const root = document.body

// we need the scale to manually resize the canvas as well as passing it to html2canvas
const scale = window.devicePixelRatio

const canvas = document.createElement("canvas")
canvas.width = root.offsetWidth * scale
canvas.height = root.offsetHeight * scale

const ctx = canvas.getContext("2d")
const originalDrawImage = ctx.drawImage

ctx.drawImage = function (image, sx, sy, sw, sh, dx, dy, dw, dh) {
  if (image instanceof HTMLImageElement) {
    if (sw / dw < sh / dh) {
      const _dh = dh
      dh = sh * (dw / sw)
      dy = dy + (_dh - dh) / 2
    } else {
      const _dw = dw
      dw = sw * (dh / sh)
      dx = dx + (_dw - dw) / 2
    }
  }

  return originalDrawImage.call(ctx, image, sx, sy, sw, sh, dx, dy, dw, dh)
}

await html2canvas(root, {
  canvas,
  scale,
})

~I'm not sure if this works in all cases though, it just worked well in my project.~

Edit: this applies object-fit: cover to every image. If you change if (sw / dw < sh / dh) to if (dw / sw < dh / sh) it will instead apply object-fit: contain to every image.

ahhh finally something that worked!

armanrozika avatar Jan 25 '24 15:01 armanrozika

For those who don't bother to patch the package, here's a hack based on plavet's solution. Just create a patched canvas and pass it to html2canvas:

const root = document.body

// we need the scale to manually resize the canvas as well as passing it to html2canvas
const scale = window.devicePixelRatio

const canvas = document.createElement("canvas")
canvas.width = root.offsetWidth * scale
canvas.height = root.offsetHeight * scale

const ctx = canvas.getContext("2d")
const originalDrawImage = ctx.drawImage

ctx.drawImage = function (image, sx, sy, sw, sh, dx, dy, dw, dh) {
  if (image instanceof HTMLImageElement) {
    if (sw / dw < sh / dh) {
      const _dh = dh
      dh = sh * (dw / sw)
      dy = dy + (_dh - dh) / 2
    } else {
      const _dw = dw
      dw = sw * (dh / sh)
      dx = dx + (_dw - dw) / 2
    }
  }

  return originalDrawImage.call(ctx, image, sx, sy, sw, sh, dx, dy, dw, dh)
}

await html2canvas(root, {
  canvas,
  scale,
})

~I'm not sure if this works in all cases though, it just worked well in my project.~

Edit: this applies object-fit: cover to every image. If you change if (sw / dw < sh / dh) to if (dw / sw < dh / sh) it will instead apply object-fit: contain to every image.

Thank you for this. I took the liberty and modified it to handle the properties set on the original image. This is a verry hacky way to do it I guess but it works for my project. I am first creating a map of all images on the page with the image src as key. (There might be a better way to identify the img elements, since in some cases you might have images with the same src but different properties on the same page) Then I use this map to get the object-fit and object-position properties so I can then determine how I want to manipulate the dimensions. Anyways here is my implementation hope this helps someone:

 const root = document.body;

const customCanvas = document.createElement('canvas');
const scale = window.devicePixelRatio;
customCanvas.width = 1920 * scale;
customCanvas.height = 1080 * scale;

const ctx = customCanvas.getContext('2d');
const originalDrawImage = ctx.drawImage;

const images: { [key: string]: HTMLImageElement } = Array.from(root.getElementsByTagName('img')).reduce(
  (map, img) => {
    map[img.src] = img;
    return map;
  },
  {} as { [key: string]: HTMLImageElement },
);

ctx.drawImage = function (image, sx, sy, sw, sh, dx, dy, dw, dh) {
  if (image instanceof HTMLImageElement) {
    const objectFit = images[image.src].style.objectFit || 'fill'; // Default to 'fill'
    const objectPosition = images[image.src].style.objectPosition || 'center center'; // Default to 'center center'
    sh = image.height;
    sw = image.width;

    const sourceRatio = sw / sh;
    const destinationRatio = dw / dh;

    // Parse objectPosition to determine alignment
    let [horizontalPosition, verticalPosition] = objectPosition.split(' ');
    if (!verticalPosition) {
      // If only one value is provided, use it for both directions
      verticalPosition = horizontalPosition;
    }

    // Adjust source dimensions and positions based on object-fit
    switch (objectFit) {
      case 'cover':
        if (sourceRatio > destinationRatio) {
          const newSw = sh * destinationRatio;
          let offsetX = (sw - newSw) / 2; // Default center
          if (horizontalPosition === 'left') offsetX = 0;
          if (horizontalPosition === 'right') offsetX = sw - newSw;
          sx += offsetX;
          sw = newSw;
        } else {
          const newSh = sw / destinationRatio;
          let offsetY = (sh - newSh) / 2; // Default center
          if (verticalPosition === 'top') offsetY = 0;
          if (verticalPosition === 'bottom') offsetY = sh - newSh;
          sy += offsetY;
          sh = newSh;
        }
        break;
      case 'contain':
        if (sourceRatio > destinationRatio) {
          const newDh = dw / sourceRatio;
          let offsetY = (dh - newDh) / 2; // Default center
          if (verticalPosition === 'top') offsetY = 0;
          if (verticalPosition === 'bottom') offsetY = dh - newDh;
          dy += offsetY;
          dh = newDh;
        } else {
          const newDw = dh * sourceRatio;
          let offsetX = (dw - newDw) / 2; // Default center
          if (horizontalPosition === 'left') offsetX = 0;
          if (horizontalPosition === 'right') offsetX = dw - newDw;
          dx += offsetX;
          dw = newDw;
        }
        break;
      case 'fill':
        // No adjustments needed for object-position
        break;
      // Implement other object-fit values if needed
    }
  }

  return originalDrawImage.call(ctx, image, sx, sy, sw, sh, dx, dy, dw, dh);
};

const canvas = await html2canvas(root, {
  canvas: customCanvas,
  windowHeight: 1080,
  windowWidth: 1920,
});

Tekknoman avatar Feb 07 '24 08:02 Tekknoman