缩放图像以适合画布

问题描述 投票:0回答:6

我有一个允许用户上传图像的表单。

加载图像后,我们会对其执行一些缩放,以减小其文件大小,然后再将其传回服务器。

为此,我们将其放置在画布上并在那里对其进行操作。

此代码将在画布上渲染缩放后的图像,画布尺寸为 320 x 240px:

ctx.drawImage(img, 0, 0, canvas.width, canvas.height)

...其中canvas.width和canvas.height是图像的高度和宽度x基于原始图像大小的缩放因子。

但是当我去使用代码时:

ctx.drawImage(img, 0, 0, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height

...我只在画布上获得图像的一部分,在本例中是左上角。尽管实际图像尺寸大于 320x240 画布尺寸,但我需要将整个图像“缩放”以适合画布。

所以对于上面的代码,宽度和高度是1142x856,因为这是最终的图像尺寸。我需要保持该大小,以便在提交表单时将请求传递给服务器,但只想在用户的画布中显示较小的视图。

我在这里缺少什么?有人能指出我正确的方向吗?

javascript html canvas html5-canvas
6个回答
201
投票

您在第二次调用时犯了错误,将源的大小设置为目标的大小。
不管怎样,我打赌你想要缩放图像具有相同的纵横比,所以你需要计算它:

var hRatio = canvas.width / img.width    ;
var vRatio = canvas.height / img.height  ;
var ratio  = Math.min ( hRatio, vRatio );
ctx.drawImage(img, 0,0, img.width, img.height, 0,0,img.width*ratio, img.height*ratio);

我还假设你想将图像居中,所以代码是:

function drawImageScaled(img, ctx) {
   var canvas = ctx.canvas ;
   var hRatio = canvas.width  / img.width    ;
   var vRatio =  canvas.height / img.height  ;
   var ratio  = Math.min ( hRatio, vRatio );
   var centerShift_x = ( canvas.width - img.width*ratio ) / 2;
   var centerShift_y = ( canvas.height - img.height*ratio ) / 2;  
   ctx.clearRect(0,0,canvas.width, canvas.height);
   ctx.drawImage(img, 0,0, img.width, img.height,
                      centerShift_x,centerShift_y,img.width*ratio, img.height*ratio);  
}

您可以在此处的 jsbin 中看到它: http://jsbin.com/funewofu/1/edit?js,输出


190
投票

提供源图像(img)大小作为第一个矩形:

ctx.drawImage(img, 0, 0, img.width,    img.height,     // source rectangle
                   0, 0, canvas.width, canvas.height); // destination rectangle

第二个矩形将是目标尺寸(源矩形将缩放到什么大小)。

更新 2016/6:有关宽高比和定位(ala CSS 的“覆盖”方法),请查看:
模拟背景大小:覆盖在画布中


5
投票

我猜您希望将图像缩放到较小的尺寸,而不丢失尺寸的比例。我有一个解决办法。

var ratio = image.naturalWidth / image.naturalHeight;
var width = canvas.width;
var height = width / ratio;
ctx.drawImage(image, 0, 0, width, height);

该比例将维持不变。并且在画布上绘制的图像将具有相同的比例。如果图像的高度很长,可以使用 if 循环,可以将 canvas.width 替换为其他宽度


3
投票

您可以在致电

ctx.scale()
之前先致电
ctx.drawImage

var factor  = Math.min ( canvas.width / img.width, canvas.height / img.height );
ctx.scale(factor, factor);
ctx.drawImage(img, 0, 0);
ctx.scale(1 / factor, 1 / factor);

这应该保留纵横比。


1
投票

HTML:

<div id="root"></div>

JavaScript:

const images = [
    'https://cdn.pixabay.com/photo/2022/07/25/15/18/cat-7344042_960_720.jpg',
    'https://cdn.pixabay.com/photo/2022/06/27/08/37/monk-7287041_960_720.jpg',
    'https://cdn.pixabay.com/photo/2022/07/18/19/57/dog-7330712_960_720.jpg',
    'https://cdn.pixabay.com/photo/2022/05/22/18/25/spain-7214284_960_720.jpg',
];

const root = document.getElementById('root');

const image = new Image();
image.crossOrigin = 'anonumys';
image.src = images[3];

const canvas = document.createElement('canvas');
canvas.classList.add('track');
canvas.width = 600;
canvas.height = 400;
const ctx = canvas.getContext('2d');

const meta = {
    ratio: image.width / image.height,
    width: 0,
    height: 0,
    offsetX: 0,
    offsetY: 0,
};

if (meta.ratio >= 1) {
    meta.width = canvas.width > image.width ? image.width : canvas.width;
    meta.height = meta.width / meta.ratio;
} else {
    meta.height = canvas.height > image.height ? image.height : canvas.height;
    meta.width = meta.height * meta.ratio;
}

meta.offsetX = canvas.width > meta.width ? (canvas.width - meta.width) / 2 : 0;
meta.offsetY = canvas.height > meta.height ? (canvas.height - meta.height) / 2 : 0;

image.addEventListener('load', () => {
    ctx.drawImage(image, meta.offsetX, meta.offsetY, meta.width, meta.height);
    root.append(canvas);
});

console.log(meta);

0
投票

当我应用视口变换(称为transform)时,我的源画布使用fabric js库按比例缩小,然后当我尝试将其用作源画布进行

drawImage
操作时,我遇到了这样的情况我没有得到预期的宽度和高度。

这个(使用比原始尺寸更小的尺寸),这里的问题是,当源画布(

__canvas.getElement()
)本身缩小时,即使对于
drawimage
方法,它也使用不足的宽度和高度,这对于我来说还不够
canvasCropWidth
canvasCropHeight
,因此,新目标画布的右侧始终有一些空间。

const cropSelectionRect = __canvas.getObjects().find(obj => obj.altName === 'cropRectangle');

// convert point from space with transform to space without it.
let cropStartPoint = fabric.util.transformPoint({x: cropSelectionRect.left, y: cropSelectionRect.top}, __canvas.viewportTransform);


// if i set this.drawingCanvas.width = this.canvasCropWidth and this.drawingCanvas.height = this.canvasCropHeight  then
// there was always transparenrt space on right side of destination canvas (drawingCanvas) as source canvas was itself scaled down
// using it like this fixed that but in the end i got lower dimension for image
this.drawingCanvas.width = this.canvasCropWidth * __canvas.viewportTransform[0];
this.drawingCanvas.height = this.canvasCropHeight * __canvas.viewportTransform[3];

drwingContext.clearRect(0, 0, this.drawingCanvas.width, this.drawingCanvas.height)

drwingContext.drawImage(__canvas.getElement(), Math.round(cropStartPoint.x), Math.round(cropStartPoint.y), this.canvasCropWidth, this.canvasCropHeight, 
    0, 0,  this.canvasCropWidth, this.canvasCropHeight);

当我将视口变换分别乘以该宽度/高度时,空间问题得到解决,但我没有获得足够的高度或宽度。

所以我最终在进行

drawImage
操作之前重置了变换,并在完成后将其重置回来。因此,用户可以显示与图像显示相同的裁剪尺寸。

// store original transform before resetting it to normal
const currentVpt = __canvas.viewportTransform;
// and then reset
__canvas.setViewportTransform([1,0,0,1,0,0])

// as transform here is reset, now no need to worry about transforming or anything
// we can directly use width/height/left and top
const cropSelectionRect = __canvas.getObjects().find(obj => obj.altName === 'cropRectangle');
this.drawingCanvas.width = this.canvasCropWidth;
this.drawingCanvas.height = this.canvasCropHeight;

drwingContext.clearRect(0, 0, this.drawingCanvas.width, this.drawingCanvas.height)

drwingContext.drawImage(__canvas.getElement(), cropSelectionRect.left, cropSelectionRect.top, this.canvasCropWidth, this.canvasCropHeight, 
    0, 0,  this.canvasCropWidth, this.canvasCropHeight);

this.savedImageDownloadUrl = this.drawingCanvas.toDataURL();

// and when all is done, set this again to what was earlier
__canvas.setViewportTransform(currentVpt);
© www.soinside.com 2019 - 2024. All rights reserved.