Don Rhummy
Don Rhummy

Reputation: 25820

When scaling and drawing an image to canvas in iOS Safari, width is correct but height is squished

I am loading an image and then drawing that image to a canvas. I am also scaling the image down to fit the canvas. When I do this, the image draws onto the canvas at the proper width but the height is about 1/4 of what the canvas' actual height is.

//Calculate height from canvas-to-image width ratio
var width = canvas.width;
var height = ( width / img.width ) * img.height;

//Draw scale image (This results in a squished height, despite "height" being correct)
context.drawImage( image, 0, 0, img.width, img.height, 0, 0, width, height );

Even though height is correct (checked in console), the image draws to the canvas at a much smaller height.

Also, when I check the ratio of the canvas.width / canvas.height vs img.width / img.height I get: .707234 vs .707818. That's so close it could not account for the huge height problem.

Why does this happen? How do I fix this?

EDIT: On Chrome, this exact code shows correctly.

Upvotes: 1

Views: 4483

Answers (2)

Don Rhummy
Don Rhummy

Reputation: 25820

This is a bug in iOS Safari. It's described here: Mobile Safari renders <img src="data:image/jpeg;base64..."> scaled on Canvas?

Solution is here: https://github.com/stomita/ios-imagefile-megapixel

Problem

If you have an image with very high resolution (greater than 1024x1024) it will be subsampled (reduces the number of pixels by 16x). When doing this, Safari introduces an error that results in a too-small vertical height.

Solution

To solve, this, you take small chunks of the image (like reading from a file into a buffer in chunks), adjust for the vertical height differential, draw just those to a temporary canvas, and then draw that temporary canvas' pixels to the destination canvas.

Upvotes: 1

candied_orange
candied_orange

Reputation: 7308

var ratio = ( width / img.width ) * img.width; will leave ratio equal to width and nothing learned from img.width since you're undoing the division with multiplication by the same value. This will mess up your height calculation which will mess up the drawn image.

Try it like this:

var ratio = ( width / img.width );

And you'll get a real ratio between the canvas and images height. But I don't think you want that anyway.

What you want is to keep the aspect ratio (width/height) of your image the same and shrink it to fit. That is possible even if the canvas has a different aspect ratio by leaving unused space on the sides or top and bottom.

So what you really need to know is which you should use to scale your image. Width or height.

If the image has a greater aspect ratio than the canvas then scale using width. That means make the widths the same and set the height at whatever preserves the aspect ratio of the image. This will leave blanks top and/or bottom.

If the image has a lower aspect ratio than the canvas then scale using height. Blah blah height same. Scale width. Blah blah blanks on the sides.

var imgRatio = img.width / img.height;
var canRatio = canvas.width / canvas.height;

var scaledWidth = img.width * (canvas.height / img.height);
var scaledHeight = img.height * (canvas.width / img.height);

if (imgRatio > canRatio) {
    context.drawImage( image, 0, 0, canvas.width, scaledHeight );
} else {
    context.drawImage( image, 0, 0, scaledWidth, canvas.height );
}

Also if you aren't doing any clipping of the image, only scaling, then these parameters are all you need.

http://www.w3schools.com/tags/canvas_drawimage.asp

Upvotes: 1

Related Questions