admin管理员组

文章数量:1134083

I have a form that allows a user to upload an image.

Once the image is loaded, we perform some scaling on it in order to reduce its filesize before we pass it back to the server.

To do this, we place it on the canvas and manipulate it there.

This code will render the scaled image on the canvas, with the canvas of size 320 x 240px:

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

... where canvas.width and canvas.height is the image height and width x a scaling factor based on the size of the original image.

But when I go to use the code:

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

... I only get part of the image on the canvas, in this case the top left corner. I need the whole image 'scaled' to fit on the canvas, despite the actual image size being larger than the 320x240 canvas size.

So for the code above, the width and heights are 1142x856, as that is the final image size. I need to maintain that size to pass beck to the server when the form is submitted, but only want a smaller view of it to appear in the canvas for the user.

What am I missing here? Can anyone point me in the right direction please?

I have a form that allows a user to upload an image.

Once the image is loaded, we perform some scaling on it in order to reduce its filesize before we pass it back to the server.

To do this, we place it on the canvas and manipulate it there.

This code will render the scaled image on the canvas, with the canvas of size 320 x 240px:

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

... where canvas.width and canvas.height is the image height and width x a scaling factor based on the size of the original image.

But when I go to use the code:

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

... I only get part of the image on the canvas, in this case the top left corner. I need the whole image 'scaled' to fit on the canvas, despite the actual image size being larger than the 320x240 canvas size.

So for the code above, the width and heights are 1142x856, as that is the final image size. I need to maintain that size to pass beck to the server when the form is submitted, but only want a smaller view of it to appear in the canvas for the user.

What am I missing here? Can anyone point me in the right direction please?

Share Improve this question edited Dec 18, 2022 at 22:38 starball 49.3k28 gold badges194 silver badges865 bronze badges asked Apr 16, 2014 at 8:58 dradddradd 1,3073 gold badges10 silver badges10 bronze badges
Add a comment  | 

6 Answers 6

Reset to default 202

You made the error, for the second call, to set the size of source to the size of the target.
Anyway i bet that you want the same aspect ratio for the scaled image, so you need to compute it :

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

i also suppose you want to center the image, so the code would be :

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

you can see it in a jsbin here : http://jsbin.com/funewofu/1/edit?js,output

Provide the source image (img) size as the first rectangle:

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

The second rectangle will be the destination size (what source rectangle will be scaled to).

Update 2016/6: For aspect ratio and positioning (ala CSS' "cover" method), check out:
Simulation background-size: cover in canvas

I guess that you want the image to be scaled to a smaller size, without losing the ratio of the dimensions. I have a solution.

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

the ratio will be maintained. And the image drawn on the canvas will be of the same ratio. you can use the if loop if the height of the image is long, you can replace the canvas.width to some other width

You can call ctx.scale() before calling 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);

This should preserve the aspect ratio.

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

I was in a situation when i had source canvas which was scaled down using fabric js library when i had applied viewport transform (known as transform) and then when i was trying to use it as a source canvas for drawImage operation i was not getting width and height as expected.

This (which was working with lower size then original) and the problem here is that when source canvas (__canvas.getElement()) itself is scaled down, it uses insufficient width and height even for drawimage method which was not simply enough for my canvasCropWidth and canvasCropHeight and thus, there was always some space on right side of new destination canvas.

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

As i multiplied viewport transform with that width/height respectively, space issue was fixed but i was not getting enough height or width.

So i ended up resetting transform before doing drawImage operation and resetting it back when it has been completed. So user can shown the same crop dimension at which image is going to come out.

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

本文标签: javascriptScaling an image to fit on canvasStack Overflow