image-blob-reduce
Version:
High quality image resize in browser for blobs (`pica` wrapper with some sugar)
216 lines (155 loc) • 5.65 kB
JavaScript
;
var utils = require('./lib/utils');
var pica = require('pica');
function ImageBlobReduce(options) {
if (!(this instanceof ImageBlobReduce)) return new ImageBlobReduce(options);
options = options || {};
this.pica = options.pica || pica({});
this.initialized = false;
this.utils = utils;
}
ImageBlobReduce.prototype.use = function (plugin /*, params, ... */) {
var args = [ this ].concat(Array.prototype.slice.call(arguments, 1));
plugin.apply(plugin, args);
return this;
};
ImageBlobReduce.prototype.init = function () {
this.use(require('./lib/jpeg_plugins').assign);
};
ImageBlobReduce.prototype.toBlob = function (blob, options) {
var opts = utils.assign({ max: Infinity }, options);
var env = {
blob: blob,
opts: opts
};
if (!this.initialized) {
this.init();
this.initialized = true;
}
return Promise.resolve(env)
.then(this._blob_to_image)
.then(this._calculate_size)
.then(this._transform)
.then(this._cleanup)
.then(this._create_blob)
.then(function (_env) {
// Safari 12 workaround
// https://github.com/nodeca/pica/issues/199
_env.out_canvas.width = _env.out_canvas.height = 0;
return _env.out_blob;
});
};
ImageBlobReduce.prototype.toCanvas = function (blob, options) {
var opts = utils.assign({ max: Infinity }, options);
var env = {
blob: blob,
opts: opts
};
if (!this.initialized) {
this.init();
this.initialized = true;
}
return Promise.resolve(env)
.then(this._blob_to_image)
.then(this._calculate_size)
.then(this._transform)
.then(this._cleanup)
.then(function (_env) { return _env.out_canvas; });
};
ImageBlobReduce.prototype.before = function (method_name, fn) {
if (!this[method_name]) throw new Error('Method "' + method_name + '" does not exist');
if (typeof fn !== 'function') throw new Error('Invalid argument "fn", function expected');
var old_fn = this[method_name];
var self = this;
this[method_name] = function (env) {
return fn.call(self, env).then(function (_env) {
return old_fn.call(self, _env);
});
};
return this;
};
ImageBlobReduce.prototype.after = function (method_name, fn) {
if (!this[method_name]) throw new Error('Method "' + method_name + '" does not exist');
if (typeof fn !== 'function') throw new Error('Invalid argument "fn", function expected');
var old_fn = this[method_name];
var self = this;
this[method_name] = function (env) {
return old_fn.call(self, env).then(function (_env) {
return fn.call(self, _env);
});
};
return this;
};
ImageBlobReduce.prototype._blob_to_image = function (env) {
var URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
env.image = document.createElement('img');
env.image_url = URL.createObjectURL(env.blob);
env.image.src = env.image_url;
return new Promise(function (resolve, reject) {
env.image.onerror = function () { reject(new Error('ImageBlobReduce: failed to create Image() from blob')); };
env.image.onload = function () { resolve(env); };
});
};
ImageBlobReduce.prototype._calculate_size = function (env) {
//
// Note, if your need not "symmetric" resize logic, you MUST check
// `env.orientation` (set by plugins) and swap width/height appropriately.
//
var scale_factor = env.opts.max / Math.max(env.image.width, env.image.height);
if (scale_factor > 1) scale_factor = 1;
env.transform_width = Math.max(Math.round(env.image.width * scale_factor), 1);
env.transform_height = Math.max(Math.round(env.image.height * scale_factor), 1);
// Info for user plugins, to check if scaling applied
env.scale_factor = scale_factor;
return Promise.resolve(env);
};
ImageBlobReduce.prototype._transform = function (env) {
env.out_canvas = this.pica.options.createCanvas(env.transform_width, env.transform_height);
// Dim env temporary vars to prohibit use and avoid confusion when orientation
// changed. You should take real size from canvas.
env.transform_width = null;
env.transform_height = null;
// By default use alpha for png only
var pica_opts = { alpha: env.blob.type === 'image/png' };
// Extract pica options if been passed
this.utils.assign(pica_opts, this.utils.pick_pica_resize_options(env.opts));
return this.pica
.resize(env.image, env.out_canvas, pica_opts)
.then(function () { return env; });
};
ImageBlobReduce.prototype._cleanup = function (env) {
env.image.src = '';
env.image = null;
var URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
if (URL.revokeObjectURL) URL.revokeObjectURL(env.image_url);
env.image_url = null;
return Promise.resolve(env);
};
ImageBlobReduce.prototype._create_blob = function (env) {
return this.pica.toBlob(env.out_canvas, env.blob.type)
.then(function (blob) {
env.out_blob = blob;
return env;
});
};
ImageBlobReduce.prototype._getUint8Array = function (blob) {
if (blob.arrayBuffer) {
return blob.arrayBuffer().then(function (buf) {
return new Uint8Array(buf);
});
}
return new Promise(function (resolve, reject) {
var fr = new FileReader();
fr.readAsArrayBuffer(blob);
fr.onload = function () { resolve(new Uint8Array(fr.result)); };
fr.onerror = function () {
reject(new Error('ImageBlobReduce: failed to load data from input blob'));
fr.abort();
};
fr.onabort = function () {
reject(new Error('ImageBlobReduce: failed to load data from input blob (aborted)'));
};
});
};
ImageBlobReduce.pica = pica;
module.exports = ImageBlobReduce;