Friday, April 12, 2013

Using HTML5 Canvas to Manipulate Image in Windows 8

I presented a way to upload multiple images in my previous post. Sometime we need to manipulate the images first, such as resizing, compress, then do the upload. There's a few ways to do such job using Windows 8 JavaScript. The easiest one is use HTML5 canvas:

    // Convert image dataUri format to blob format
    function dataURItoBlob(dataURI) {
        var byteString = atob(dataURI.split(',')[1]);
        var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]

        var byteArray = new Uint8Array(byteString.length);
        for (var i = 0; i < byteString.length; i++) {
            byteArray[i] = byteString.charCodeAt(i) & 0xff;
        }

        return new Blob([byteArray], { type: mimeString });
    }

    // Use HTML5 canvas to resize and compress an image
    function resizeAndCompressImageToBlob(image, length, quality) {
        var isLandScape = image.naturalWidth > image.naturalHeight;
        var width = isLandScape ? length :  parseInt((length / image.naturalHeight) * image.naturalWidth)
        var heigh = isLandScape ? parseInt((width / image.naturalWidth) * image.naturalHeight) : length;
        var cvs = document.createElement('canvas');
        cvs.width = width;
        cvs.height = heigh;
        cvs.getContext("2d").drawImage(image, 0, 0, width, heigh);
        var dataUri = cvs.toDataURL("image/jpeg", quality);
        var imgBlob = dataURItoBlob(dataUri);
        return imgBlob;
    }

    // Load image asynchronously
    function loadImageAsync(src) {
        return new WinJS.Promise(function (c, e, p) {
            var img = new Image();
            img.src = src;
            img.addEventListener("load", function () {
                c(img);
            });
        });
    }

    function uploadMultipleImages(url, formFields) {
        return new WinJS.Promise(function (c, e, p) {
            var dataToSend = new FormData();
            var filePicker = new Windows.Storage.Pickers.FileOpenPicker();
            filePicker.fileTypeFilter.replaceAll([".jpg", ".jpeg"]);
            filePicker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.picturesLibrary;

            filePicker.pickMultipleFilesAsync().then(function (files) {
                if (files.length === 0) {
                    console.log("No file selected");
                    return;
                }
                if (formFields && formFields.length > 0) {
                    formFields.forEach(function (formField) {
                        for (var key in formField) {
                            dataToSend.append(key, formField[key]); // Add field value
                        }
                    });
                }
                var imgPromises = []; // Promises for all image loading
                files.forEach(function (file, index) {
                    var objectUrl = URL.createObjectURL(file, { oneTimeOnly: true });
                    var loadImage = loadImageAsync(objectUrl, file.name);
                    imgPromises.push(loadImage);
                });

                // Wait for all async calls completion 
                WinJS.Promise.join(imgPromises).then(function (data) { 
                    var savePromieses = [];
                    for (var i = 0; i < data.length; i++) {
                        var imageBlob = resizeAndCompressImageToBlob(data[i], 1200, 0.8);
                        dataToSend.append(files[i].name, imageBlob, files[i].name);
                    }

                    var options = { url: url, type: "POST", data: dataToSend };
                    WinJS.xhr(options).then(function (response) {
                        c(response);
                    },
                    function (xhrError) {
                        e(xhrError);
                    });

                },
                function (joinError) {
                    e(joinError);
                });
            });
        });
    }