Windows Store App – Resize Image (JavaScript)

31/10/2012

In this post I’ll demonstrate how to change picture size using JavaScript while constraining the image proportions.

Resize image functionality can be used in several scenarios in Windows Store app, for example: if your app allow the user to download images from the web you might want to resize the image to reduce space usage. Also if you whish to share this image using Roaming Storage you need to make sure the image size is small enough so the Roaming sync will not failed.

The first steps for this demo is using FileOpenPicker to allow the user to choose what picture he want to resize.

Download Demo Project

  • Create new JavaScript Navigation app.
  • Open package.appxmanifest and add “Pictures Library” capability. (We’ll use this later)

image

  • Open home.html file located under pages->home and add the following code:
<div class="preview">

    <div class="btnBrowse">

        <button id="btnBrowse">Pick a Image</button>

    </div>

    <div class="previewImage">

        <img id="previewImage" style="max-height: 400px" src="#" />

    </div>

    <div id="imageDetails"></div>

</div>

The above code added a button, img and div elements to home.html page, the button will invoke the FileOpenPicker, img element will display the selected image and the imageDetails div will display the image properties.

  • Open home.js and add the following code jus under the “use strict” statement:
function $(query) {

    return document.querySelector(query);

}

 

function toDateTime(date) {

    var dateTime = new Date(Date.parse(date));

    if (dateTime.getDate()) {

        var month = dateTime.getMonth() + 1;

        var day = dateTime.getDate();

        var year = dateTime.getFullYear();

        var hours = dateTime.getHours();

        var minutes = dateTime.getMinutes();

        var ampm = hours < 12 ? "am" : "pm";

        return "{0}/{1}/{2} {3}:{4} {5}".format(month, day, year, hours < 10 ? "0" + hours : hours, minutes < 10 ? "0" + minutes : minutes, ampm);

    }

    else

        return date;

}

 

String.prototype.format = function () {

    var str = this;

    for (var i = 0; i < arguments.length; i++) {

        var reg = new RegExp("\\{" + i + "\\}", "gm");

        str = str.replace(reg, arguments[i]);

    }

 

    return str;

};

Those functions are helpers for our project.

  • Under ready function add the following code:
$("#btnBrowse").addEventListener("click", this.openFilePicker.bind(this), false);

We’re adding event listener for click event on “btnBrowse” button, now let implement the FileOpenPicker.

  • Add the following code under the close statement of ready function:
selectedImage:null,

openFilePicker: function () {

    var that = this;

 

    // Create the picker object and set options

    var fp = Windows.Storage.Pickers.FileOpenPicker();

    // Users expect to have a filtered view of their folders depending on the scenario.

    // For example, when choosing a documents folder, restrict the filetypes to documents for your application.

    fp.fileTypeFilter.replaceAll([".png", ".bmp", ".jpg", ".jpeg"]);

    fp.suggestedStartLocation = Windows.Storage.KnownFolders.picturesLibrary;

    fp.commitButtonText = "Select";

    fp.viewMode = Windows.Storage.Pickers.PickerViewMode.thumbnail;

    // Open the picker for the user to pick a file

    fp.pickSingleFileAsync().done(function (file) {

        if (file != null) {

            that.selectedImage = file;

            $("#previewImage").src = URL.createObjectURL(file, { oneTimeOnly: true });

            file.properties.getImagePropertiesAsync().then(function (imgProp) {

                $("#imageDetails").innerHTML = "Width: {0}<br/>Height: {1}<br/>Date Taken: " +

                    "{2}<br/>Camera Model: {3}".format(

                    imgProp.width, imgProp.height, toDateTime(imgProp.dateTaken), imgProp.cameraModel);

 

                        

                $("#width").value = imgProp.width;

                $("#height").value = imgProp.height;

                $("#btnResize").disabled = false;

            });

        }

        else {

            $("#width").textContent = 0;

            $("#height").textContent = 0;

            $("#btnResize").disabled = true;

        }

    });

},

Once you click on the “Pick a Image” button the file picker dialog will appear allowing you to select a single image. As pickSingleFileAsync return a promise, after the FileOpenPicker dialog is closed we received the file you pick.

First we assign the file to selectedImage object, using URL.createObjectURL we transform the file into blob and assign to the image.src property and finally we call – getImagePropertiesAsync function to retrieve image properties so we can display them on the html page.

When if you run the application you can pick and image using the “Pick a Image” button and the result should be like that:

image

  • Open home.html page and add the following code under the preview div element:
<div class="resizeOptions">

    <div>Max Width:

        <input type="number" id="width" />

    </div>

    <div>Max Height:

        <input type="number" id="height" />

    </div>

    <div>

        <button id="btnResize" disabled="disabled">Resize Image</button>

    </div>

</div>

We add two input elements allowing you to define the desire size of the image and a button element to invoke the resize function.

  • Open home.js and add the following code under the $("#btnBrowse").addEventListener statement.
$("#btnResize").addEventListener("click", this.resizeImage.bind(this), false);

We’re adding event listener for click event on “btnResize” button, now let implement the resizeImage function.

  • We don’t want to change the original image in our case so the resizeImage will open the FileSavePicker allowing the user to choose where he want to save the resized image.
resizeImage: function () {

    var that = this;

    var width = $("#width").value != "" ? parseInt($("#width").value) : 0;

    var height = $("#height").value != "" ? parseInt($("#height").value) : 0;

 

    if (width === 0 || height === 0) {

        var msg = new Windows.UI.Popups.MessageDialog("Please enter width and height values");

        msg.showAsync().done("");

        return;

    }

 

    var fs = new Windows.Storage.Pickers.FileSavePicker();

    fs.suggestedStartLocation = Windows.Storage.KnownFolders.picturesLibrary;

    fs.defaultFileExtension = this.selectedImage.fileType;

    fs.fileTypeChoices.insert(this.selectedImage.fileType, [this.selectedImage.fileType]);

    fs.suggestedFileName = "Resize Demo-" + this.selectedImage.name;

    fs.pickSaveFileAsync().done(function (targetFile) {

        //copy selected image to new file

        that.selectedImage.copyAndReplaceAsync(targetFile).then(function () {

            //call resize function on the new file.

            that._resize(width, height, targetFile).done(function () {

                var msg = new Windows.UI.Popups.MessageDialog("File Resize Completed!");

                msg.showAsync().done("");

            });

 

        });

    });

},

The above code check is the input elements for width and height are not empty and then open the FileSavePicker, once the user choose a target file we’ll use the copyAndReplaceAsync function to copy the selected image to the target file the user has selected, finally we call the _resize function passing the width, height and the copied image object.

  • Now to the final part, let’s implement the resize function, add the following code after resizeImage close statement:
_resize: function (maxWidth, maxHeight, imgFile) {

    var originalWidth;

    var originalHeight;

    var encoder;

    var fileStream;

    var count = 0;

    var that = this;

    return new WinJS.Promise(function (complete) {

        var memStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();

        imgFile.openAsync(Windows.Storage.FileAccessMode.readWrite).then(function (stream) {

            fileStream = stream;

            return Windows.Graphics.Imaging.BitmapDecoder.createAsync(fileStream);

        }).then(function (decoder) {

            that._scale(decoder, memStream, maxWidth, maxHeight).then(function () {

                // Overwrite the contents of the file with the updated image stream.

                memStream.seek(0);

                fileStream.seek(0);

                fileStream.size = 0;

                return Windows.Storage.Streams.RandomAccessStream.copyAsync(memStream, fileStream);

            })

            .done(function () {

                // Finally, close each stream to release any locks.

                memStream && memStream.close();

                fileStream && fileStream.close();

            });

        }

        );

    });

}

the _resize function calling the openAsync function on the target file to obtaint he file stream then creating stream decoder using Windows.Graphics.Imaging.BitmapDecoder. Once we have the image decoder we can scale the image (I’ll talk about that in the next code example), after the scale function has completed we copy the memoryStream (where we did the scale changes) into the fileStream (target file) after the copy is complete we close both streams. it’s important to close the streams otherwise those streams are locked for other processes. (unless you open the stream only for read).

  • The last part and the most important one is _scale function, this function will change the image size based on use max-height and max-width while keeping the image proportion.
_scale: function (decoder, memStream, maxWidth, maxHeight) {

    return new WinJS.Promise(function (complete, error) {

        var originalHeight = decoder.pixelHeight;

        var originalWidth = decoder.pixelWidth;

        if (originalHeight <= maxHeight || originalWidth <= maxWidth)

            complete();

        else {

            Windows.Graphics.Imaging.BitmapEncoder.createForTranscodingAsync(memStream, decoder).then(function (_encoder) {

                var encoder = _encoder;

 

                var nPercent = 0;

                var nPercentW = 0;

                var nPercentH = 0;

 

                nPercentW = (maxWidth / originalWidth);

                nPercentH = (maxHeight / originalHeight);

 

                if (nPercentH < nPercentW)

                    nPercent = nPercentH;

                else

                    nPercent = nPercentW;

 

                var destWidth = (originalWidth * nPercent);

                var destHeight = (originalHeight * nPercent);

 

                encoder.bitmapTransform.scaledWidth = destWidth;

                encoder.bitmapTransform.scaledHeight = destHeight;

 

                // Attempt to generate a new thumbnail to reflect any rotation operation.

                encoder.isThumbnailGenerated = true;

 

                encoder.flushAsync().done(function () {

                    complete();

                });

            })

        }

    })

},

First we need to check the max-height or max-width are not smaller or equal to the original size.Before changing the image size we’ll use “createForTranscodingAsync” function – creates a new BitmapEncoder and initializes it using data from an existing BitmapDecoder. We do some math to calculate the new image size and to set the new size we need to call the encoder and change the scaleWidth and scaleHeight properties located under bitmapTransform object.

image

imageimage

Full Code:

(function () {

    "use strict";

 

    function $(query) {

        return document.querySelector(query);

    }

 

    function toDateTime(date) {

        var dateTime = new Date(Date.parse(date));

        if (dateTime.getDate()) {

            var month = dateTime.getMonth() + 1;

            var day = dateTime.getDate();

            var year = dateTime.getFullYear();

            var hours = dateTime.getHours();

            var minutes = dateTime.getMinutes();

            var ampm = hours < 12 ? "am" : "pm";

            return "{0}/{1}/{2} {3}:{4} {5}".format(month, day, year, hours < 10 ? "0" + hours :

                hours, minutes < 10 ? "0" + minutes : minutes, ampm);

        }

        else

            return date;

    }

 

    String.prototype.format = function () {

        var str = this;

        for (var i = 0; i < arguments.length; i++) {

            var reg = new RegExp("\\{" + i + "\\}", "gm");

            str = str.replace(reg, arguments[i]);

        }

 

        return str;

    };

 

    var ERR_OPERATION_UNSUPPORTED = -2003292287;  // The operation is unsupported

 

    WinJS.UI.Pages.define("/pages/home/home.html", {

        // This function is called whenever a user navigates to this page. It

        // populates the page elements with the app's data.

        ready: function (element, options) {

            $("#btnBrowse").addEventListener("click", this.openFilePicker.bind(this), false);

            $("#btnResize").addEventListener("click", this.resizeImage.bind(this), false);

        },

        selectedImage: null,

        openFilePicker: function () {

            var that = this;

 

            // Create the picker object and set options

            var fp = Windows.Storage.Pickers.FileOpenPicker();

            // Users expect to have a filtered view of their folders depending on the scenario.

            // For example, when choosing a documents folder, restrict the filetypes to documents for your application.

            fp.fileTypeFilter.replaceAll([".png", ".bmp", ".jpg", ".jpeg"]);

            fp.suggestedStartLocation = Windows.Storage.KnownFolders.picturesLibrary;

            fp.commitButtonText = "Select";

            fp.viewMode = Windows.Storage.Pickers.PickerViewMode.thumbnail;

            // Open the picker for the user to pick a file

            fp.pickSingleFileAsync().done(function (file) {

                if (file != null) {

                    that.selectedImage = file;

                    $("#previewImage").src = URL.createObjectURL(file, { oneTimeOnly: true });

                    file.properties.getImagePropertiesAsync().then(function (imgProp) {

                        $("#imageDetails").innerHTML = "Width: {0}<br/>Height: {1}<br/>Date Taken: {2}<br/>Camera Model: {3}".format(

                            imgProp.width, imgProp.height, toDateTime(imgProp.dateTaken), imgProp.cameraModel);

 

 

                        $("#width").value = imgProp.width;

                        $("#height").value = imgProp.height;

                        $("#btnResize").disabled = false;

                    });

                }

                else {

                    $("#width").value = 0;

                    $("#height").value = 0;

                    $("#btnResize").disabled = true;

                }

            });

        },

        resizeImage: function () {

            var that = this;

            var width = $("#width").value != "" ? parseInt($("#width").value) : 0;

            var height = $("#height").value != "" ? parseInt($("#height").value) : 0;

 

            if (width === 0 || height === 0) {

                var msg = new Windows.UI.Popups.MessageDialog("Please enter width and height values");

                msg.showAsync().done("");

                return;

            }

 

            var fs = new Windows.Storage.Pickers.FileSavePicker();

            fs.suggestedStartLocation = Windows.Storage.KnownFolders.picturesLibrary;

            fs.defaultFileExtension = this.selectedImage.fileType;

            fs.fileTypeChoices.insert(this.selectedImage.fileType, [this.selectedImage.fileType]);

            fs.suggestedFileName = "Resize Demo-" + this.selectedImage.name;

            fs.pickSaveFileAsync().done(function (targetFile) {

                //copy selected image to new file

                that.selectedImage.copyAndReplaceAsync(targetFile).then(function () {

                    //call resize function on the new file.

                    that._resize(width, height, targetFile).done(function () {

                        var msg = new Windows.UI.Popups.MessageDialog("File Resize Completed!");

                        msg.showAsync().done("");

                    });

 

                });

            });

        },

 

        _scale: function (decoder, memStream, maxWidth, maxHeight) {

            return new WinJS.Promise(function (complete, error) {

                var originalHeight = decoder.pixelHeight;

                var originalWidth = decoder.pixelWidth;

                if (originalHeight <= maxHeight || originalWidth <= maxWidth)

                    complete();

                else {

                    Windows.Graphics.Imaging.BitmapEncoder.createForTranscodingAsync(memStream, decoder).then(function (_encoder) {

                        var encoder = _encoder;

 

                        var nPercent = 0;

                        var nPercentW = 0;

                        var nPercentH = 0;

 

                        nPercentW = (maxWidth / originalWidth);

                        nPercentH = (maxHeight / originalHeight);

 

                        if (nPercentH < nPercentW)

                            nPercent = nPercentH;

                        else

                            nPercent = nPercentW;

 

                        var destWidth = (originalWidth * nPercent);

                        var destHeight = (originalHeight * nPercent);

 

                        encoder.bitmapTransform.scaledWidth = destWidth;

                        encoder.bitmapTransform.scaledHeight = destHeight;

 

                        // Attempt to generate a new thumbnail to reflect any rotation operation.

                        encoder.isThumbnailGenerated = true;

 

                        encoder.flushAsync().done(function () {

                            complete();

                        });

                    })

                }

            })

        },

        _resize: function (maxWidth, maxHeight, imgFile) {

            var originalWidth;

            var originalHeight;

            var encoder;

            var fileStream;

            var count = 0;

            var that = this;

            return new WinJS.Promise(function (complete) {

                var memStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();

                imgFile.openAsync(Windows.Storage.FileAccessMode.readWrite).then(function (stream) {

                    fileStream = stream;

                    return Windows.Graphics.Imaging.BitmapDecoder.createAsync(fileStream);

                }).then(function (decoder) {

                    that._scale(decoder, memStream, maxWidth, maxHeight).then(function () {

                        // Overwrite the contents of the file with the updated image stream.

                        memStream.seek(0);

                        fileStream.seek(0);

                        fileStream.size = 0;

                        return Windows.Storage.Streams.RandomAccessStream.copyAsync(memStream, fileStream);

                    })

                    .done(function () {

                        // Finally, close each stream to release any locks.

                        memStream && memStream.close();

                        fileStream && fileStream.close();

                    });

                }

                );

            });

        }

    });

})();

Download Demo Project

Add comment
facebook linkedin twitter email

Leave a Reply