Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Resize Images in the Browser
let hasBlobConstructor = typeof(Blob) !== 'undefined' && (function () {
try {
return Boolean(new Blob());
} catch (e) {
return false;
}
}());
let hasArrayBufferViewSupport = hasBlobConstructor && typeof(Uint8Array) !== 'undefined' && (function () {
try {
return new Blob([new Uint8Array(100)]).size === 100;
} catch (e) {
return false;
}
}());
let hasToBlobSupport = (typeof HTMLCanvasElement !== "undefined" ? HTMLCanvasElement.prototype.toBlob : false);
let hasBlobSupport = (hasToBlobSupport || (typeof Uint8Array !== 'undefined' && typeof ArrayBuffer !== 'undefined' && typeof atob !== 'undefined'));
let hasReaderSupport = (typeof FileReader !== 'undefined' || typeof URL !== 'undefined');
export default class ImageTools {
static resize(file, maxDimensions, callback) {
if (typeof maxDimensions === 'function') {
callback = maxDimensions;
maxDimensions = {
width: 640,
height: 480
};
}
let maxWidth = maxDimensions.width;
let maxHeight = maxDimensions.height;
if (!ImageTools.isSupported() || !file.type.match(/image.*/)) {
callback(file, false);
return false;
}
if (file.type.match(/image\/gif/)) {
// Not attempting, could be an animated gif
callback(file, false);
// TODO: use https://github.com/antimatter15/whammy to convert gif to webm
return false;
}
let image = document.createElement('img');
image.onload = (imgEvt) => {
let width = image.width;
let height = image.height;
let isTooLarge = false;
if (width >= height && width > maxDimensions.width) {
// width is the largest dimension, and it's too big.
height *= maxDimensions.width / width;
width = maxDimensions.width;
isTooLarge = true;
} else if (height > maxDimensions.height) {
// either width wasn't over-size or height is the largest dimension
// and the height is over-size
width *= maxDimensions.height / height;
height = maxDimensions.height;
isTooLarge = true;
}
if (!isTooLarge) {
// early exit; no need to resize
callback(file, false);
return;
}
let canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
let ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0, width, height);
if (hasToBlobSupport) {
canvas.toBlob((blob) => {
callback(blob, true);
}, file.type);
} else {
let blob = ImageTools._toBlob(canvas, file.type);
callback(blob, true);
}
};
ImageTools._loadImage(image, file);
return true;
}
static _toBlob(canvas, type) {
let dataURI = canvas.toDataURL(type);
let dataURIParts = dataURI.split(',');
let byteString;
if (dataURIParts[0].indexOf('base64') >= 0) {
// Convert base64 to raw binary data held in a string:
byteString = atob(dataURIParts[1]);
} else {
// Convert base64/URLEncoded data component to raw binary data:
byteString = decodeURIComponent(dataURIParts[1]);
}
let arrayBuffer = new ArrayBuffer(byteString.length);
let intArray = new Uint8Array(arrayBuffer);
for (let i = 0; i < byteString.length; i += 1) {
intArray[i] = byteString.charCodeAt(i);
}
let mimeString = dataURIParts[0].split(':')[1].split(';')[0];
let blob = null;
if (hasBlobConstructor) {
blob = new Blob(
[hasArrayBufferViewSupport ? intArray : arrayBuffer],
{type: mimeString}
);
} else {
let bb = new BlobBuilder();
bb.append(arrayBuffer);
blob = bb.getBlob(mimeString);
}
return blob;
}
static _loadImage(image, file, callback) {
if (typeof(URL) === 'undefined') {
let reader = new FileReader();
reader.onload = function(evt) {
image.src = evt.target.result;
if (callback) { callback(); }
}
reader.readAsDataURL(file);
} else {
image.src = URL.createObjectURL(file);
if (callback) { callback(); }
}
};
static isSupported() {
return (
(typeof(HTMLCanvasElement) !== 'undefined')
&& hasBlobSupport
&& hasReaderSupport
);
}
}
'use strict';
if (typeof exports === "undefined") {
var exports = {};
}
if (typeof module === "undefined") {
var module = {};
}
Object.defineProperty(exports, '__esModule', {
value: true
});
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
var hasBlobConstructor = typeof Blob !== 'undefined' && (function () {
try {
return Boolean(new Blob());
} catch (e) {
return false;
}
})();
var hasArrayBufferViewSupport = hasBlobConstructor && typeof Uint8Array !== 'undefined' && (function () {
try {
return new Blob([new Uint8Array(100)]).size === 100;
} catch (e) {
return false;
}
})();
var hasToBlobSupport = typeof HTMLCanvasElement !== "undefined" ? HTMLCanvasElement.prototype.toBlob : false;
var hasBlobSupport = hasToBlobSupport || typeof Uint8Array !== 'undefined' && typeof ArrayBuffer !== 'undefined' && typeof atob !== 'undefined';
var hasReaderSupport = typeof FileReader !== 'undefined' || typeof URL !== 'undefined';
var ImageTools = (function () {
function ImageTools() {
_classCallCheck(this, ImageTools);
}
_createClass(ImageTools, null, [{
key: 'resize',
value: function resize(file, maxDimensions, callback) {
if (typeof maxDimensions === 'function') {
callback = maxDimensions;
maxDimensions = {
width: 640,
height: 480
};
}
var maxWidth = maxDimensions.width;
var maxHeight = maxDimensions.height;
if (!ImageTools.isSupported() || !file.type.match(/image.*/)) {
callback(file, false);
return false;
}
if (file.type.match(/image\/gif/)) {
// Not attempting, could be an animated gif
callback(file, false);
// TODO: use https://github.com/antimatter15/whammy to convert gif to webm
return false;
}
var image = document.createElement('img');
image.onload = function (imgEvt) {
var width = image.width;
var height = image.height;
var isTooLarge = false;
if (width > height && width > maxDimensions.width) {
// width is the largest dimension, and it's too big.
height *= maxDimensions.width / width;
width = maxDimensions.width;
isTooLarge = true;
} else if (height > maxDimensions.height) {
// either width wasn't over-size or height is the largest dimension
// and the height is over-size
width *= maxDimensions.height / height;
height = maxDimensions.height;
isTooLarge = true;
}
if (!isTooLarge) {
// early exit; no need to resize
callback(file, false);
return;
}
var canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0, width, height);
if (hasToBlobSupport) {
canvas.toBlob(function (blob) {
callback(blob, true);
}, file.type);
} else {
var blob = ImageTools._toBlob(canvas, file.type);
callback(blob, true);
}
};
ImageTools._loadImage(image, file);
return true;
}
}, {
key: '_toBlob',
value: function _toBlob(canvas, type) {
var dataURI = canvas.toDataURL(type);
var dataURIParts = dataURI.split(',');
var byteString = undefined;
if (dataURIParts[0].indexOf('base64') >= 0) {
// Convert base64 to raw binary data held in a string:
byteString = atob(dataURIParts[1]);
} else {
// Convert base64/URLEncoded data component to raw binary data:
byteString = decodeURIComponent(dataURIParts[1]);
}
var arrayBuffer = new ArrayBuffer(byteString.length);
var intArray = new Uint8Array(arrayBuffer);
for (var i = 0; i < byteString.length; i += 1) {
intArray[i] = byteString.charCodeAt(i);
}
var mimeString = dataURIParts[0].split(':')[1].split(';')[0];
var blob = null;
if (hasBlobConstructor) {
blob = new Blob([hasArrayBufferViewSupport ? intArray : arrayBuffer], { type: mimeString });
} else {
var bb = new BlobBuilder();
bb.append(arrayBuffer);
blob = bb.getBlob(mimeString);
}
return blob;
}
}, {
key: '_loadImage',
value: function _loadImage(image, file, callback) {
if (typeof URL === 'undefined') {
var reader = new FileReader();
reader.onload = function (evt) {
image.src = evt.target.result;
if (callback) {
callback();
}
};
reader.readAsDataURL(file);
} else {
image.src = URL.createObjectURL(file);
if (callback) {
callback();
}
}
}
}, {
key: 'isSupported',
value: function isSupported() {
return typeof HTMLCanvasElement !== 'undefined' && hasBlobSupport && hasReaderSupport;
}
}]);
return ImageTools;
})();
exports['default'] = ImageTools;
module.exports = exports['default'];
@rubencodes

This comment has been minimized.

Copy link

commented Dec 3, 2015

Left a comment on S.O., but I noticed when using this, resizing images also caused a loss of quality/severe pixelation with both jpg and png images (though it worked in the sense that it resized to the right size).

@dewetvdm

This comment has been minimized.

Copy link

commented Sep 29, 2016

On line 55 (es6) and line 79 (es5)
if (width > height && width > maxDimensions.width) { needs to be if (width >= height && width > maxDimensions.width) {
I found that by trying to resize an image with width = height, it fails at this part.

Thanks for the awesome snippet!

@mgks

This comment has been minimized.

Copy link

commented Oct 24, 2016

Really helpful, but can you please help me with the image quality? I'm not able to resolve that.

@LFRCORP

This comment has been minimized.

Copy link

commented Feb 22, 2017

Esto de verdad está "chingón".

Gracias por dejar libre el código.

Saludos desde Tijuana, BC, México.

@dcollien

This comment has been minimized.

Copy link
Owner Author

commented Aug 14, 2017

For better quality downscaling, can combine with this: https://github.com/viliusle/Hermite-resize

e.g. as per
https://stackoverflow.com/questions/18922880/html5-canvas-resize-downscale-image-high-quality

and replace the ctx.drawImage call with resample_single

@satyavh

This comment has been minimized.

Copy link

commented Aug 16, 2017

Great script. Some issues:

line 122 let bb = new BlobBuilder();

BlobBuilder is deprecated in favour of new Blob(). Change this line to let bb = new Blob(); and the script works fine.

line 57

            var maxWidth = maxDimensions.width;
            var maxHeight = maxDimensions.height;

These vars are never used.

@zomars

This comment has been minimized.

Copy link

commented Oct 26, 2017

Are there any demos of this? Thanks for sharing BTW!

@sean-perkins

This comment has been minimized.

Copy link

commented Nov 22, 2017

Here's an updated example for TypeScript that addresses the BlobBuilder to Blob and fixes the problem with loss-of-quality with the resampling:

const hasBlobConstructor = typeof (Blob) !== 'undefined' && (function () {
    try {
        return Boolean(new Blob());
    } catch (e) {
        return false;
    }
}());

const hasArrayBufferViewSupport = hasBlobConstructor && typeof (Uint8Array) !== 'undefined' && (function () {
    try {
        return new Blob([new Uint8Array(100)]).size === 100;
    } catch (e) {
        return false;
    }
}());

const hasToBlobSupport = (typeof HTMLCanvasElement !== 'undefined' ? HTMLCanvasElement.prototype.toBlob : false);

const hasBlobSupport = (hasToBlobSupport ||
    (typeof Uint8Array !== 'undefined' && typeof ArrayBuffer !== 'undefined' && typeof atob !== 'undefined'));

const hasReaderSupport = (typeof FileReader !== 'undefined' || typeof URL !== 'undefined');

export class ImageTools {

    static resize(file: File, maxDimensions: { width: number, height: number }, callback) {
        if (typeof maxDimensions === 'function') {
            callback = maxDimensions;
            maxDimensions = {
                width: 640,
                height: 480
            };
        }


        if (!ImageTools.isSupported() || !file.type.match(/image.*/)) {
            callback(file, false);
            return false;
        }

        if (file.type.match(/image\/gif/)) {
            // Not attempting, could be an animated gif
            callback(file, false);
            // TODO: use https://github.com/antimatter15/whammy to convert gif to webm
            return false;
        }

        const image = document.createElement('img');

        image.onload = (imgEvt) => {
            let width = image.width;
            let height = image.height;
            let isTooLarge = false;

            if (width >= height && width > maxDimensions.width) {
                isTooLarge = true;
            } else if (height > maxDimensions.height) {
                isTooLarge = true;
            }

            if (!isTooLarge) {
                // early exit; no need to resize
                callback(file, false);
                return;
            }

            const scaleRatio = maxDimensions.width / width;

            // TODO number of resampling steps
            // const steps = Math.ceil(Math.log(width / (width * scaleRatio)) / Math.log(2));

            width *= scaleRatio;
            height *= scaleRatio;

            const canvas = document.createElement('canvas');
            canvas.width = width;
            canvas.height = height;

            const ctx = canvas.getContext('2d');
            ctx.imageSmoothingEnabled = true;
            (ctx as any).imageSmoothingQuality = 'high';
            ctx.drawImage(image, 0, 0, width, height);

            if (hasToBlobSupport) {
                canvas.toBlob((blob) => {
                    callback(blob, true);
                }, file.type);
            } else {
                const blob = ImageTools._toBlob(canvas, file.type);
                callback(blob, true);
            }
        };
        ImageTools._loadImage(image, file);

        return true;
    }

    static _toBlob(canvas, type) {
        const dataURI = canvas.toDataURL(type);
        const dataURIParts = dataURI.split(',');
        let byteString;
        if (dataURIParts[0].indexOf('base64') >= 0) {
            // Convert base64 to raw binary data held in a string:
            byteString = atob(dataURIParts[1]);
        } else {
            // Convert base64/URLEncoded data component to raw binary data:
            byteString = decodeURIComponent(dataURIParts[1]);
        }
        const arrayBuffer = new ArrayBuffer(byteString.length);
        const intArray = new Uint8Array(arrayBuffer);

        for (let i = 0; i < byteString.length; i += 1) {
            intArray[i] = byteString.charCodeAt(i);
        }

        const mimeString = dataURIParts[0].split(':')[1].split(';')[0];
        let blob = null;

        if (hasBlobConstructor) {
            blob = new Blob(
                [hasArrayBufferViewSupport ? intArray : arrayBuffer],
                { type: mimeString }
            );
        } else {
            blob = new Blob([arrayBuffer]);
        }
        return blob;
    }

    static _loadImage(image, file, callback?: any) {
        if (typeof (URL) === 'undefined') {
            const reader = new FileReader();
            reader.onload = function (evt) {
                image.src = (evt.target as any).result;
                if (callback) { callback(); }
            };
            reader.readAsDataURL(file);
        } else {
            image.src = URL.createObjectURL(file);
            if (callback) {
                callback();
            }
        }
    };

    static _toFile = (theBlob: Blob, fileName: string): File => {
        const b: any = theBlob;
        b.lastModifiedDate = new Date();
        b.name = fileName;
        return <File>theBlob;
    }

    static isSupported() {
        return (
            (typeof (HTMLCanvasElement) !== 'undefined')
            && hasBlobSupport
            && hasReaderSupport
        );
    }
}
@leventengin

This comment has been minimized.

Copy link

commented Dec 12, 2017

thanks for the script..
i plan to use it in direct uploads from mobile devices. but i get the results of all portrait images in landscape form (turned down) . is there a way to change it, i could not find a way in the code... my images are raw image files from mobile phones...landscapes are ok.
thanks again..........

@frutality

This comment has been minimized.

Copy link

commented Jan 17, 2018

@dcollien have you tried to use Hermite? I tried and see no difference in output. Files are same, byte-to-byte, both in FF and Chrome. It makes me think I'm doing something wrong.

P.S. You wrote "and replace the ctx.drawImage call with resample_single", but in that case we are not drawing image. Result is black screen. I added this after "ctx.drawImage":

HERMITE.resample_single(canvas, width, height, true);

And nothing changed. I'm using ES6 version.

@frutality

This comment has been minimized.

Copy link

commented Jan 17, 2018

My bad, it was hot reloading. It was not doing right. After manual page refresh everything works. Thanks.

@qomi210

This comment has been minimized.

Copy link

commented May 5, 2018

I use this js in my site but in some pc its hang out when generating resized blob who know what I must do ?
reply to qomi210@gmail.com

@MatthewLHolden

This comment has been minimized.

Copy link

commented May 30, 2018

Is there anyway to pass a base64 string into this?

ImageTools.resize(this.selectedFiles[0], { width: 1000, height: 1000 }, ((blob, didItResize) => {

I'm trying to resolve a scenario whereby an image taken from an iPhone has been transferred to desktop. When using that image to upload through my web app, the orientation is incorrect. I have some code that fixes the orientation, but by that point it's already a base64 string. It'd be really great to know whether I can resize that base64 variable using your script. It that possible at all?

@talhakhankhalil

This comment has been minimized.

Copy link

commented Jun 28, 2018

How should I send the resized blob to server in POST request.

@SagiMedina

This comment has been minimized.

Copy link

commented Jul 8, 2018

I'v wrote one with orientation fix using exif, @leventengin or anyone else how looks for a solution
https://gist.github.com/SagiMedina/f00a57de4e211456225d3114fd10b0d0

@TonyC5

This comment has been minimized.

Copy link

commented Aug 25, 2018

@SagiMedina - thanks for posting your solution. I'm new to js programming and trying to figure things out. I could be wrong (very easily), but it appears to me that your ImageTools.js is not a direct replacement for @dcollien's ImageTools.js. Am I missing something? Did you change the calling signature of ImageTools.resize()? If so, how do you use your solution? Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.