55

I want to resize the image taken from the iOS camera on the client side with HTML5 Canvas but I keep running in this weird bug where the image has a wrong ratio if bigger than ~1.5mb

It works on the desktop but not in the latest iOS version with the media upload API.

You can see an example here: http://jsbin.com/ekuros/1

Any idea how to fix this please? Is this a memory issue?

$('#file').on('change', function (e) {
    var file = e.currentTarget.files[0];
    var reader = new FileReader();
    reader.onload = function (e) {
        var image = $('<img/>');
        image.on('load', function () {
            var square = 320;
            var canvas = document.createElement('canvas');

            canvas.width = square;
            canvas.height = square;

            var context = canvas.getContext('2d');
            context.clearRect(0, 0, square, square);
            var imageWidth;
            var imageHeight;
            var offsetX = 0;
            var offsetY = 0;

            if (this.width > this.height) {
                imageWidth = Math.round(square * this.width / this.height);
                imageHeight = square;
                offsetX = - Math.round((imageWidth - square) / 2);
            } else {
                imageHeight = Math.round(square * this.height / this.width);
                imageWidth = square;    
                offsetY = - Math.round((imageHeight - square) / 2);            
            }

            context.drawImage(this, offsetX, offsetY, imageWidth, imageHeight);
            var data = canvas.toDataURL('image/jpeg');

            var thumb = $('<img/>');
            thumb.attr('src', data);
            $('body').append(thumb);
        });
        image.attr('src', e.target.result);
    };
    reader.readAsDataURL(file);
});
Brice Lechatellier
  • 653
  • 2
  • 7
  • 9

5 Answers5

54

If you still need to use the long version of the drawImage function you can change this:

context.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);

to this:

drawImageIOSFix(context, img, sx, sy, sw, sh, dx, dy, dw, dh);

You just need to include these two functions somewhere:

/**
 * Detecting vertical squash in loaded image.
 * Fixes a bug which squash image vertically while drawing into canvas for some images.
 * This is a bug in iOS6 devices. This function from https://github.com/stomita/ios-imagefile-megapixel
 * 
 */
function detectVerticalSquash(img) {
    var iw = img.naturalWidth, ih = img.naturalHeight;
    var canvas = document.createElement('canvas');
    canvas.width = 1;
    canvas.height = ih;
    var ctx = canvas.getContext('2d');
    ctx.drawImage(img, 0, 0);
    var data = ctx.getImageData(0, 0, 1, ih).data;
    // search image edge pixel position in case it is squashed vertically.
    var sy = 0;
    var ey = ih;
    var py = ih;
    while (py > sy) {
        var alpha = data[(py - 1) * 4 + 3];
        if (alpha === 0) {
            ey = py;
        } else {
            sy = py;
        }
        py = (ey + sy) >> 1;
    }
    var ratio = (py / ih);
    return (ratio===0)?1:ratio;
}

/**
 * A replacement for context.drawImage
 * (args are for source and destination).
 */
function drawImageIOSFix(ctx, img, sx, sy, sw, sh, dx, dy, dw, dh) {
    var vertSquashRatio = detectVerticalSquash(img);
 // Works only if whole image is displayed:
 // ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh / vertSquashRatio);
 // The following works correct also when only a part of the image is displayed:
    ctx.drawImage(img, sx * vertSquashRatio, sy * vertSquashRatio, 
                       sw * vertSquashRatio, sh * vertSquashRatio, 
                       dx, dy, dw, dh );
}

This will work fine whether it is run on iOS or other platforms.

This is based on the great work by stomita and you should credit him in your work.

Jekapa
  • 169
  • 15
matt burns
  • 24,742
  • 13
  • 105
  • 107
  • 1
    The above solution fixes the vertical squishing issue in iOS 7. I should note that megapixel-image.js, which this code is based on, for some reason does not work with iOS 7 but this does. Now if I can find a solution to the orientation issue I will be set. – Obi Wan Oct 11 '13 at 15:26
  • 1
    Awesome fix. I made a function that applies this fix to a specific canvas instance, in a way that you can use all your existing canvas 2d context drawImage calls. Perhaps it is of use to someone else? http://jsfiddle.net/gWY2a/24/ – L0LN1NJ4 Nov 27 '13 at 13:30
  • @L0LN1NJ4 I see you are overriding the drawimage function with your implementation. Thanks for sharing – matt burns Nov 27 '13 at 17:08
  • Unfortunately, the fix won't work for drawing remote images as this will throw a security error for the getImageData call – Bjorn Apr 29 '14 at 09:18
  • Messes Android 4.x up :/ – mike3996 Sep 12 '14 at 09:44
  • Thanks for this fix, saves me lots of pain ^^ – arlg Sep 25 '14 at 21:25
  • Really would love to implement this, as I have this bug which is making my app useless on iOS. Unfortunately, I'm rotating and scaling the context before drawing the image and I can't seem to figure out how to make it work. See here: http://stackoverflow.com/questions/26130560/fixing-the-ios7-squished-image-in-canvas-with-rotation-and-scaling – mpsyp Sep 30 '14 at 21:40
  • On iOS 8 the drawImage sometimes fails (blank image drawn) for source width and height larger than 1000*600. This saved my ass. – MK Yung Jan 29 '15 at 07:42
  • is this working for ios9?, i am still having issue with ios – Marty Krishner May 26 '16 at 20:29
30

There is a JavaScript canvas resize library which works around the subsampling and vertical squash issues encountered when drawing scaled images on canvas on iOS devices: http://github.com/stomita/ios-imagefile-megapixel

There are side issues when scaling images with alpha channel (as it uses the alpha channel for the issues detection) and when trying to resize existing canvas elements, however it's the first solution I've found that actually works with the issue at hand.

stomita is also a StackOverflow user and posted his solution here: https://stackoverflow.com/a/12615436/644048

Community
  • 1
  • 1
Sebastian Tschan
  • 1,444
  • 15
  • 12
  • 1
    Created an online demo of another file upload widget I just wrote. It can detect the iOS rendering issue. In case the image does not appear to have been rendered correctly, the image is re-rendered with a fix applied. Doubling the height scale by two seems to fix it too... http://sandbox.juurlink.org/html5imageuploader/ – Rob Juurlink Feb 13 '13 at 20:04
  • I tried stomita's solution and it works. However, once i run his library and the picture is displayed, how do i get access to that canvas context in order to further run image processing on it? – Chaz May 10 '13 at 15:56
  • I just tested megapix-image.js on my new iPhone 5C running under iOS 7 and it fails to convert the image. It appears is not compatible with iPhone 5 and/or iOS 7. – Obi Wan Oct 09 '13 at 14:12
6

It looks like this is an iOS 6 bug. There is no reason for the aspect to get out of whack from your code. I have the same problem which was only introduced in iOS 6. It seems that their sub-sampling routine gives the wrong height. I submitted a bug report to Apple, and you should do the same. The more bug reports they get for this the better.

Torchify
  • 134
  • 7
2

I've experienced the same problem. It seems that this is an iOS limitation, jpg over 2 megapixel are subsampled.

See Creating Compatible Web Content for Safari on IPhone

rationalboss
  • 5,330
  • 3
  • 30
  • 50
Paul
  • 21
  • 2
-2

A modified version of the above code.

Edit: saw L0LN1NJ4's code at http://jsfiddle.net/gWY2a/24/ .. guess that one's a bit better...

function drawImageIOSFix (ctx, img) {
 var vertSquashRatio = detectVerticalSquash (img)
 var arg_count = arguments.length
 switch (arg_count) {
  case 4  : ctx.drawImage (img, arguments[2], arguments[3] / vertSquashRatio); break
  case 6  : ctx.drawImage (img, arguments[2], arguments[3], arguments[4], arguments[5] / vertSquashRatio); break
  case 8  : ctx.drawImage (img, arguments[2], arguments[3], arguments[4], arguments[5], arguments[6], arguments[7] / vertSquashRatio); break
  case 10 : ctx.drawImage (img, arguments[2], arguments[3], arguments[4], arguments[5], arguments[6], arguments[7], arguments[8], arguments[9] / vertSquashRatio); break
 }

 // Detects vertical squash in loaded image.
 // Fixes a bug which squash image vertically while drawing into canvas for some images.
 // This is a bug in iOS6 (and IOS7) devices. This function from https://github.com/stomita/ios-imagefile-megapixel
 function detectVerticalSquash (img) {
  var iw = img.naturalWidth, ih = img.naturalHeight
  var canvas = document.createElement ("canvas")
  canvas.width  = 1
  canvas.height = ih
  var ctx = canvas.getContext('2d')
  ctx.drawImage (img, 0, 0)
  var data = ctx.getImageData(0, 0, 1, ih).data
  // search image edge pixel position in case it is squashed vertically.
  var sy = 0, ey = ih, py = ih
  while (py > sy) {
   var alpha = data[(py - 1) * 4 + 3]
   if (alpha === 0) {ey = py} else {sy = py}
   py = (ey + sy) >> 1
  }
  var ratio = (py / ih)
  return (ratio === 0) ? 1 : ratio
 }
}
Agamemnus
  • 1,395
  • 4
  • 17
  • 41
  • 1
    This isn't an answer, it should have been a comment. Please don't treat answers like forum posts! Read the StackOverflow rules. – Soviut Nov 28 '13 at 18:39