13

IOS6 has been released and I've been testing photo uploading.

It works well, but with larger images over 3G it is SLOW as expected.

Thanks to File API and Canvas, it is possible to resize images using JavaScript. I hope that if I resize the images before I attempt to upload them, they will upload faster - lending itself to a speedy user experience. With smartphone processors improving exponentially faster than the network speeds, I believe this solution is a winner.

Nicolas has offered an excellent solution for image resizing:

Image resize before upload

However, I am having the hardest time implementing it with jQuery's Ajax. Any advice or help is appreciated, as this code will probably be extremely useful for mobile web application development post-IOS6.

var fileType = file.type,
    reader = new FileReader();

reader.onloadend = function () {
    var image = new Image();
    image.src = reader.result;

    image.onload = function () {

        //Detect image size
        var maxWidth = 960,
            maxHeight = 960,
            imageWidth = image.width,
            imageHeight = image.height;
        if (imageWidth > imageHeight) {
            if (imageWidth > maxWidth) {
                imageHeight *= maxWidth / imageWidth;
                imageWidth = maxWidth;
            }
        } else {
            if (imageHeight > maxHeight) {
                imageWidth *= maxHeight / imageHeight;
                imageHeight = maxHeight;
            }
        }

        //Create canvas with new image
        var canvas = document.createElement('canvas');
        canvas.width = imageWidth;
        canvas.height = imageHeight;
        var ctx = canvas.getContext("2d");
        ctx.drawImage(this, 0, 0, imageWidth, imageHeight);

        // The resized file ready for upload
        var finalFile = canvas.toDataURL(fileType);

        if (formdata) {

            formdata.append("images[]", finalFile);

            $.ajax({
                url: "upload.php",
                type: "POST",
                data: formdata,
                dataType: 'json',
                processData: false,
                contentType: false,
                success: function (res) {
                    //successful image upload
                }
            });

        }
    }
}
reader.readAsDataURL(file);
Community
  • 1
  • 1
TaylorMac
  • 8,882
  • 21
  • 76
  • 104
  • Have you found a solution to this? I have found that it is more problematic when resizing large images straight from the camera or one that has been taken from the camera in the past. – NimmoNet Sep 24 '12 at 13:25

3 Answers3

32

I've just developed a jQuery plugin for client side canvas image resizing. It also handles orientation and the iOS6 squashed image issue.

You can try: http://gokercebeci.com/dev/canvasresize

Usage:

$.canvasResize(file, {
               width   : 300,
               height  : 0,
               crop    : false,
               quality : 80,
               callback: function(dataURL, width, height){

                         // your code

               }
});
goker
  • 2,690
  • 22
  • 20
7

I've been working with the upload feature since the second iOS6 beta release. The following code works for me:

Put this in the head of your HTML page -

<script>window.onload = function() {
var canvas = document.createElement('canvas');
var ctx = canvas.getContext("2d");

var fileSelect = document.getElementById("fileSelect"),
    input = document.getElementById("input");

    input.addEventListener("change", handleFiles);

    //hides ugly default file input button  
    fileSelect.addEventListener("click", function (e) {
        if (input) {
            input.click();
        }
        e.preventDefault();
    }, false);

function handleFiles(e) {
    var reader = new FileReader;
    reader.onload = function (event) {
        var img = new Image();
        img.src = reader.result;
        img.onload = function () {
            var maxWidth = 320,
                maxHeight = 350,
                imageWidth = img.width,
                imageHeight = img.height;

            if (imageWidth > imageHeight) {
                if (imageWidth > maxWidth) {
                    imageHeight *= maxWidth / imageWidth;
                    imageWidth = maxWidth;
                }
            } else {
                if (imageHeight > maxHeight) {
                    imageWidth *= maxHeight / imageHeight;
                    imageHeight = maxHeight;
                }
            }
            canvas.width = imageWidth;
            canvas.height = imageHeight;

            ctx.drawImage(this, 0, 0, imageWidth, imageHeight);

            // The resized file ready for upload
            var finalFile = canvas.toDataURL("image/png");

            var postData = 'canvasData=' + finalFile;
            var ajax = new XMLHttpRequest();
            ajax.open('POST', 'save.php', true);
            ajax.setRequestHeader('Content-Type', 'canvas/upload');

            ajax.onreadystatechange = function () {
                if (ajax.readyState == 4) {
                    //just to visually confirm it worked...
                    window.open(canvas.toDataURL("image/png"), "mywindow");
                }
            }
            ajax.send(postData);
        }
    }
    reader.readAsDataURL(e.target.files[0]);
}
}
</script>

Here's the HTML -

 <div style="width:320px;position:absolute;z-index:9;top:387px;">
<button style="width:60px;" id="fileSelect">upload</button>
<input type="file" id="input" name="input" accept="image/*" style="display:none;"></div>

Here's the PHP -

<?php
if (isset($GLOBALS["HTTP_RAW_POST_DATA"]))
{
    // Get the data
   $imageData=$GLOBALS['HTTP_RAW_POST_DATA'];

   // Remove the headers (data:,) part.  
   // A real application should use them according to needs such as to check image type
   $filteredData=substr($imageData, strpos($imageData, ",")+1);

   // Need to decode before saving since the data we received is already base64 encoded
   $unencodedData=base64_decode($filteredData);

   // Save file.  This example uses a hard coded filename for testing, 
   // but a real application can specify filename in POST variable
   $fp = fopen( 'users/user_photo.png', 'wb' );
   fwrite( $fp, $unencodedData);
   fclose( $fp );
 }
 ?>

The only issue I've battled with is getting images to load from the camera without being rotated 90 degrees.

Hope this helps, let me know if you've any issues with the code (it's my first post).

rich
  • 71
  • 1
  • Fantastic. I will work with the code and see if I can come up with a solution for the 90 degree rotation. Anyone else have a solution for this? – TaylorMac Sep 23 '12 at 01:14
  • 2
    Have you found this solution to work on the iPhone IO6 with pictures taken from the camera. I have found that any images that are resized using this method works but the image is squashed and not as expected. – NimmoNet Sep 24 '12 at 13:27
  • Have a look at this implementation. This works fine on Chrome on a normal OS but on IOS when resizing the image it does something weird http://jsfiddle.net/Untd8/ – NimmoNet Sep 24 '12 at 13:38
  • I've found a solution for the rotation issue. I use EXIF orientation data, it works well but I still have the squashed problem and I do not know the reason.. You can check it at http://orientation.gokercebeci.com – goker Sep 28 '12 at 11:40
  • Yes I could not get the images to not squash and rotate. This is an odd problem as in order to fix it an exception would need to be made for iOS, no other browsers seem to have this problem. – TaylorMac Oct 01 '12 at 00:52
  • I sent a bug report to Apple for the squash problem. – goker Oct 02 '12 at 07:36
  • I've got the squashed image problem too. @goker.cebeci Do you have a link to the bug report so that we can follow along? – aaronfalloon Oct 02 '12 at 10:51
  • Also, [this](http://books.google.co.uk/books?id=CVKbLHQ35ToC&pg=PA201&lpg=PA201&dq=javascript+canvas+squashes+images&source=bl&ots=GTYIYrKIl1&sig=zirWztKwA0FbGZueIfbp_N_DtCk&hl=en&sa=X&ei=ksdqUOmvJKbN0QWF8YHwBA&ved=0CC0Q6AEwAA#v=onepage&q=javascript%20canvas%20squashes%20images&f=false) could be a reason why the images are squashed. I tried setting the width and height of the canvas using JavaScript but it didn't work. – aaronfalloon Oct 02 '12 at 10:59
  • @aaronfalloon I tried the instruction in your reason link but I can not fix the problem. Apple replied me " Hello Goker, This is a follow up to Bug ID# 12394220. This bug has been closed as a Duplicate. The issue is being tracked under the original Bug ID# 12292459 which is also listed in the Related Problem section of your bug report. .." The bug ID# 12292459 still open and I can not see it's content. – goker Oct 11 '12 at 10:34
  • @goker.cebeci I used this library to work around the bug - https://github.com/stomita/ios-imagefile-megapixel. Hope it helps! – aaronfalloon Oct 13 '12 at 01:48
1

Given we're dealing with large images and memory issues on a mobile browser, I wanted to see if a light-weight solution can be found that avoids creating a duplicate canvas and performing other image operations just for detection and resizing.

It seems if Mobile Safari does vertically squash an image that's too large, the ratio by how much it does it, stays the same.

So right now before I even render the image on the canvas, I use a very fast rule of thumb whereby I simply check if the browser is a mobile iDevice navigator.userAgent.match(/(iPod|iPhone|iPad)/) ... AND the image height or width is larger than 2000 pix, in which case I know it will be squashed. In that case, in canvasContext.drawImage() I specify the image height to be 4x taller than what it should have normally been for a given image resizing objective. Based on what I've seen, Mobile Safari squashes the image by a factor of 4.

THEN I render the image, and it renders undistorted the first time around, squashing a pre-stretched image back down to what then becomes a normal X:Y proportion. Without any additional canvas elements or contexts or test renders or pixel iterations that a 100% solution mentioned above, uses.

I am sure there may be some edge cases, and the image size limit may not be exact, but for my app I wanted a solution that was FAST.

SashaK
  • 165
  • 6