700 lines
20 KiB
JavaScript
700 lines
20 KiB
JavaScript
'use strict';
|
|
|
|
|
|
const assign = require('object-assign');
|
|
const webworkify = require('webworkify');
|
|
|
|
|
|
const MathLib = require('./lib/mathlib');
|
|
const Pool = require('./lib/pool');
|
|
const utils = require('./lib/utils');
|
|
const worker = require('./lib/worker');
|
|
const createStages = require('./lib/stepper');
|
|
const createRegions = require('./lib/tiler');
|
|
|
|
|
|
// Deduplicate pools & limiters with the same configs
|
|
// when user creates multiple pica instances.
|
|
const singletones = {};
|
|
|
|
|
|
let NEED_SAFARI_FIX = false;
|
|
try {
|
|
if (typeof navigator !== 'undefined' && navigator.userAgent) {
|
|
NEED_SAFARI_FIX = navigator.userAgent.indexOf('Safari') >= 0;
|
|
}
|
|
} catch (e) {}
|
|
|
|
|
|
let concurrency = 1;
|
|
if (typeof navigator !== 'undefined') {
|
|
concurrency = Math.min(navigator.hardwareConcurrency || 1, 4);
|
|
}
|
|
|
|
|
|
const DEFAULT_PICA_OPTS = {
|
|
tile: 1024,
|
|
concurrency,
|
|
features: [ 'js', 'wasm', 'ww' ],
|
|
idle: 2000,
|
|
createCanvas: function (width, height) {
|
|
let tmpCanvas = document.createElement('canvas');
|
|
tmpCanvas.width = width;
|
|
tmpCanvas.height = height;
|
|
return tmpCanvas;
|
|
}
|
|
};
|
|
|
|
|
|
const DEFAULT_RESIZE_OPTS = {
|
|
quality: 3,
|
|
alpha: false,
|
|
unsharpAmount: 0,
|
|
unsharpRadius: 0.0,
|
|
unsharpThreshold: 0
|
|
};
|
|
|
|
let CAN_NEW_IMAGE_DATA = false;
|
|
let CAN_CREATE_IMAGE_BITMAP = false;
|
|
let CAN_USE_CANVAS_GET_IMAGE_DATA = false;
|
|
let CAN_USE_OFFSCREEN_CANVAS = false;
|
|
let CAN_USE_CIB_REGION_FOR_IMAGE = false;
|
|
|
|
|
|
function workerFabric() {
|
|
return {
|
|
value: webworkify(worker),
|
|
destroy: function () {
|
|
this.value.terminate();
|
|
|
|
if (typeof window !== 'undefined') {
|
|
let url = window.URL || window.webkitURL || window.mozURL || window.msURL;
|
|
if (url && url.revokeObjectURL && this.value.objectURL) {
|
|
url.revokeObjectURL(this.value.objectURL);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// API methods
|
|
|
|
function Pica(options) {
|
|
if (!(this instanceof Pica)) return new Pica(options);
|
|
|
|
this.options = assign({}, DEFAULT_PICA_OPTS, options || {});
|
|
|
|
let limiter_key = `lk_${this.options.concurrency}`;
|
|
|
|
// Share limiters to avoid multiple parallel workers when user creates
|
|
// multiple pica instances.
|
|
this.__limit = singletones[limiter_key] || utils.limiter(this.options.concurrency);
|
|
|
|
if (!singletones[limiter_key]) singletones[limiter_key] = this.__limit;
|
|
|
|
// List of supported features, according to options & browser/node.js
|
|
this.features = {
|
|
js: false, // pure JS implementation, can be disabled for testing
|
|
wasm: false, // webassembly implementation for heavy functions
|
|
cib: false, // resize via createImageBitmap (only FF at this moment)
|
|
ww: false // webworkers
|
|
};
|
|
|
|
this.__workersPool = null;
|
|
|
|
// Store requested features for webworkers
|
|
this.__requested_features = [];
|
|
|
|
this.__mathlib = null;
|
|
}
|
|
|
|
|
|
Pica.prototype.init = function () {
|
|
if (this.__initPromise) return this.__initPromise;
|
|
|
|
// Test if we can create ImageData without canvas and memory copy
|
|
if (typeof ImageData !== 'undefined' && typeof Uint8ClampedArray !== 'undefined') {
|
|
try {
|
|
/* eslint-disable no-new */
|
|
new ImageData(new Uint8ClampedArray(400), 10, 10);
|
|
CAN_NEW_IMAGE_DATA = true;
|
|
} catch (__) {}
|
|
}
|
|
|
|
// ImageBitmap can be effective in 2 places:
|
|
//
|
|
// 1. Threaded jpeg unpack (basic)
|
|
// 2. Built-in resize (blocked due problem in chrome, see issue #89)
|
|
//
|
|
// For basic use we also need ImageBitmap wo support .close() method,
|
|
// see https://developer.mozilla.org/ru/docs/Web/API/ImageBitmap
|
|
|
|
if (typeof ImageBitmap !== 'undefined') {
|
|
if (ImageBitmap.prototype && ImageBitmap.prototype.close) {
|
|
CAN_CREATE_IMAGE_BITMAP = true;
|
|
} else {
|
|
this.debug('ImageBitmap does not support .close(), disabled');
|
|
}
|
|
}
|
|
|
|
|
|
let features = this.options.features.slice();
|
|
|
|
if (features.indexOf('all') >= 0) {
|
|
features = [ 'cib', 'wasm', 'js', 'ww' ];
|
|
}
|
|
|
|
this.__requested_features = features;
|
|
|
|
this.__mathlib = new MathLib(features);
|
|
|
|
// Check WebWorker support if requested
|
|
if (features.indexOf('ww') >= 0) {
|
|
if ((typeof window !== 'undefined') && ('Worker' in window)) {
|
|
// IE <= 11 don't allow to create webworkers from string. We should check it.
|
|
// https://connect.microsoft.com/IE/feedback/details/801810/web-workers-from-blob-urls-in-ie-10-and-11
|
|
try {
|
|
let wkr = require('webworkify')(function () {});
|
|
wkr.terminate();
|
|
this.features.ww = true;
|
|
|
|
// pool uniqueness depends on pool config + webworker config
|
|
let wpool_key = `wp_${JSON.stringify(this.options)}`;
|
|
|
|
if (singletones[wpool_key]) {
|
|
this.__workersPool = singletones[wpool_key];
|
|
} else {
|
|
this.__workersPool = new Pool(workerFabric, this.options.idle);
|
|
singletones[wpool_key] = this.__workersPool;
|
|
}
|
|
} catch (__) {}
|
|
}
|
|
}
|
|
|
|
let initMath = this.__mathlib.init().then(mathlib => {
|
|
// Copy detected features
|
|
assign(this.features, mathlib.features);
|
|
});
|
|
|
|
let checkCibResize;
|
|
|
|
if (!CAN_CREATE_IMAGE_BITMAP) {
|
|
checkCibResize = Promise.resolve(false);
|
|
} else {
|
|
checkCibResize = utils.cib_support(this.options.createCanvas).then(status => {
|
|
if (this.features.cib && features.indexOf('cib') < 0) {
|
|
this.debug('createImageBitmap() resize supported, but disabled by config');
|
|
return;
|
|
}
|
|
|
|
if (features.indexOf('cib') >= 0) this.features.cib = status;
|
|
});
|
|
}
|
|
|
|
CAN_USE_CANVAS_GET_IMAGE_DATA = utils.can_use_canvas(this.options.createCanvas);
|
|
|
|
let checkOffscreenCanvas;
|
|
|
|
if (CAN_CREATE_IMAGE_BITMAP && CAN_NEW_IMAGE_DATA && features.indexOf('ww') !== -1) {
|
|
checkOffscreenCanvas = utils.worker_offscreen_canvas_support();
|
|
} else {
|
|
checkOffscreenCanvas = Promise.resolve(false);
|
|
}
|
|
|
|
checkOffscreenCanvas = checkOffscreenCanvas.then(
|
|
result => { CAN_USE_OFFSCREEN_CANVAS = result; }
|
|
);
|
|
|
|
// we use createImageBitmap to crop image data and pass it to workers,
|
|
// so need to check whether function works correctly;
|
|
// https://bugs.chromium.org/p/chromium/issues/detail?id=1220671
|
|
let checkCibRegion = utils.cib_can_use_region().then(
|
|
result => { CAN_USE_CIB_REGION_FOR_IMAGE = result; }
|
|
);
|
|
|
|
// Init math lib. That's async because can load some
|
|
this.__initPromise = Promise.all([
|
|
initMath, checkCibResize, checkOffscreenCanvas, checkCibRegion
|
|
]).then(() => this);
|
|
|
|
return this.__initPromise;
|
|
};
|
|
|
|
|
|
// Call resizer in webworker or locally, depending on config
|
|
Pica.prototype.__invokeResize = function (tileOpts, opts) {
|
|
// Share cache between calls:
|
|
//
|
|
// - wasm instance
|
|
// - wasm memory object
|
|
//
|
|
opts.__mathCache = opts.__mathCache || {};
|
|
|
|
return Promise.resolve().then(() => {
|
|
if (!this.features.ww) {
|
|
// not possible to have ImageBitmap here if user disabled WW
|
|
return { data: this.__mathlib.resizeAndUnsharp(tileOpts, opts.__mathCache) };
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
let w = this.__workersPool.acquire();
|
|
|
|
if (opts.cancelToken) opts.cancelToken.catch(err => reject(err));
|
|
|
|
w.value.onmessage = ev => {
|
|
w.release();
|
|
|
|
if (ev.data.err) reject(ev.data.err);
|
|
else resolve(ev.data);
|
|
};
|
|
|
|
let transfer = [];
|
|
|
|
if (tileOpts.src) transfer.push(tileOpts.src.buffer);
|
|
if (tileOpts.srcBitmap) transfer.push(tileOpts.srcBitmap);
|
|
|
|
w.value.postMessage({
|
|
opts: tileOpts,
|
|
features: this.__requested_features,
|
|
preload: {
|
|
wasm_nodule: this.__mathlib.__
|
|
}
|
|
}, transfer);
|
|
});
|
|
});
|
|
};
|
|
|
|
|
|
// this function can return promise if createImageBitmap is used
|
|
Pica.prototype.__extractTileData = function (tile, from, opts, stageEnv, extractTo) {
|
|
if (this.features.ww && CAN_USE_OFFSCREEN_CANVAS &&
|
|
// createImageBitmap doesn't work for images (Image, ImageBitmap) with Exif orientation in Chrome,
|
|
// can use canvas because canvas doesn't have orientation;
|
|
// see https://bugs.chromium.org/p/chromium/issues/detail?id=1220671
|
|
(utils.isCanvas(from) || CAN_USE_CIB_REGION_FOR_IMAGE)) {
|
|
this.debug('Create tile for OffscreenCanvas');
|
|
|
|
return createImageBitmap(stageEnv.srcImageBitmap || from, tile.x, tile.y, tile.width, tile.height)
|
|
.then(bitmap => {
|
|
extractTo.srcBitmap = bitmap;
|
|
return extractTo;
|
|
});
|
|
}
|
|
|
|
// Extract tile RGBA buffer, depending on input type
|
|
if (utils.isCanvas(from)) {
|
|
if (!stageEnv.srcCtx) stageEnv.srcCtx = from.getContext('2d', { alpha: Boolean(opts.alpha) });
|
|
|
|
// If input is Canvas - extract region data directly
|
|
this.debug('Get tile pixel data');
|
|
extractTo.src = stageEnv.srcCtx.getImageData(tile.x, tile.y, tile.width, tile.height).data;
|
|
return extractTo;
|
|
}
|
|
|
|
// If input is Image or decoded to ImageBitmap,
|
|
// draw region to temporary canvas and extract data from it
|
|
//
|
|
// Note! Attempt to reuse this canvas causes significant slowdown in chrome
|
|
//
|
|
this.debug('Draw tile imageBitmap/image to temporary canvas');
|
|
|
|
let tmpCanvas = this.options.createCanvas(tile.width, tile.height);
|
|
|
|
let tmpCtx = tmpCanvas.getContext('2d', { alpha: Boolean(opts.alpha) });
|
|
tmpCtx.globalCompositeOperation = 'copy';
|
|
tmpCtx.drawImage(stageEnv.srcImageBitmap || from,
|
|
tile.x, tile.y, tile.width, tile.height,
|
|
0, 0, tile.width, tile.height);
|
|
|
|
this.debug('Get tile pixel data');
|
|
|
|
extractTo.src = tmpCtx.getImageData(0, 0, tile.width, tile.height).data;
|
|
|
|
// Safari 12 workaround
|
|
// https://github.com/nodeca/pica/issues/199
|
|
tmpCanvas.width = tmpCanvas.height = 0;
|
|
|
|
return extractTo;
|
|
};
|
|
|
|
|
|
Pica.prototype.__landTileData = function (tile, result, stageEnv) {
|
|
let toImageData;
|
|
|
|
this.debug('Convert raw rgba tile result to ImageData');
|
|
|
|
if (result.bitmap) {
|
|
stageEnv.toCtx.drawImage(result.bitmap, tile.toX, tile.toY);
|
|
return null;
|
|
}
|
|
|
|
if (CAN_NEW_IMAGE_DATA) {
|
|
// this branch is for modern browsers
|
|
// If `new ImageData()` & Uint8ClampedArray suported
|
|
toImageData = new ImageData(new Uint8ClampedArray(result.data), tile.toWidth, tile.toHeight);
|
|
} else {
|
|
// fallback for `node-canvas` and old browsers
|
|
// (IE11 has ImageData but does not support `new ImageData()`)
|
|
toImageData = stageEnv.toCtx.createImageData(tile.toWidth, tile.toHeight);
|
|
|
|
if (toImageData.data.set) {
|
|
toImageData.data.set(result.data);
|
|
} else {
|
|
// IE9 don't have `.set()`
|
|
for (let i = toImageData.data.length - 1; i >= 0; i--) {
|
|
toImageData.data[i] = result.data[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
this.debug('Draw tile');
|
|
|
|
if (NEED_SAFARI_FIX) {
|
|
// Safari draws thin white stripes between tiles without this fix
|
|
stageEnv.toCtx.putImageData(toImageData, tile.toX, tile.toY,
|
|
tile.toInnerX - tile.toX, tile.toInnerY - tile.toY,
|
|
tile.toInnerWidth + 1e-5, tile.toInnerHeight + 1e-5);
|
|
} else {
|
|
stageEnv.toCtx.putImageData(toImageData, tile.toX, tile.toY,
|
|
tile.toInnerX - tile.toX, tile.toInnerY - tile.toY,
|
|
tile.toInnerWidth, tile.toInnerHeight);
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
|
|
Pica.prototype.__tileAndResize = function (from, to, opts) {
|
|
let stageEnv = {
|
|
srcCtx: null,
|
|
srcImageBitmap: null,
|
|
isImageBitmapReused: false,
|
|
toCtx: null
|
|
};
|
|
|
|
const processTile = (tile => this.__limit(() => {
|
|
if (opts.canceled) return opts.cancelToken;
|
|
|
|
let tileOpts = {
|
|
width: tile.width,
|
|
height: tile.height,
|
|
toWidth: tile.toWidth,
|
|
toHeight: tile.toHeight,
|
|
scaleX: tile.scaleX,
|
|
scaleY: tile.scaleY,
|
|
offsetX: tile.offsetX,
|
|
offsetY: tile.offsetY,
|
|
quality: opts.quality,
|
|
alpha: opts.alpha,
|
|
unsharpAmount: opts.unsharpAmount,
|
|
unsharpRadius: opts.unsharpRadius,
|
|
unsharpThreshold: opts.unsharpThreshold
|
|
};
|
|
|
|
this.debug('Invoke resize math');
|
|
|
|
return Promise.resolve(tileOpts)
|
|
.then(tileOpts => this.__extractTileData(tile, from, opts, stageEnv, tileOpts))
|
|
.then(tileOpts => {
|
|
this.debug('Invoke resize math');
|
|
return this.__invokeResize(tileOpts, opts);
|
|
})
|
|
.then(result => {
|
|
if (opts.canceled) return opts.cancelToken;
|
|
stageEnv.srcImageData = null;
|
|
return this.__landTileData(tile, result, stageEnv);
|
|
});
|
|
}));
|
|
|
|
|
|
// Need to normalize data source first. It can be canvas or image.
|
|
// If image - try to decode in background if possible
|
|
return Promise.resolve().then(() => {
|
|
stageEnv.toCtx = to.getContext('2d', { alpha: Boolean(opts.alpha) });
|
|
|
|
if (utils.isCanvas(from)) return null;
|
|
|
|
if (utils.isImageBitmap(from)) {
|
|
stageEnv.srcImageBitmap = from;
|
|
stageEnv.isImageBitmapReused = true;
|
|
return null;
|
|
}
|
|
|
|
if (utils.isImage(from)) {
|
|
// try do decode image in background for faster next operations;
|
|
// if we're using offscreen canvas, cib is called per tile, so not needed here
|
|
if (!CAN_CREATE_IMAGE_BITMAP) return null;
|
|
|
|
this.debug('Decode image via createImageBitmap');
|
|
|
|
return createImageBitmap(from)
|
|
.then(imageBitmap => {
|
|
stageEnv.srcImageBitmap = imageBitmap;
|
|
})
|
|
// Suppress error to use fallback, if method fails
|
|
// https://github.com/nodeca/pica/issues/190
|
|
/* eslint-disable no-unused-vars */
|
|
.catch(e => null);
|
|
}
|
|
|
|
throw new Error('Pica: ".from" should be Image, Canvas or ImageBitmap');
|
|
})
|
|
.then(() => {
|
|
if (opts.canceled) return opts.cancelToken;
|
|
|
|
this.debug('Calculate tiles');
|
|
|
|
//
|
|
// Here we are with "normalized" source,
|
|
// follow to tiling
|
|
//
|
|
|
|
let regions = createRegions({
|
|
width: opts.width,
|
|
height: opts.height,
|
|
srcTileSize: this.options.tile,
|
|
toWidth: opts.toWidth,
|
|
toHeight: opts.toHeight,
|
|
destTileBorder: opts.__destTileBorder
|
|
});
|
|
|
|
let jobs = regions.map(tile => processTile(tile));
|
|
|
|
function cleanup(stageEnv) {
|
|
if (stageEnv.srcImageBitmap) {
|
|
if (!stageEnv.isImageBitmapReused) stageEnv.srcImageBitmap.close();
|
|
stageEnv.srcImageBitmap = null;
|
|
}
|
|
}
|
|
|
|
this.debug('Process tiles');
|
|
|
|
return Promise.all(jobs).then(
|
|
() => {
|
|
this.debug('Finished!');
|
|
cleanup(stageEnv); return to;
|
|
},
|
|
err => { cleanup(stageEnv); throw err; }
|
|
);
|
|
});
|
|
};
|
|
|
|
|
|
Pica.prototype.__processStages = function (stages, from, to, opts) {
|
|
if (opts.canceled) return opts.cancelToken;
|
|
|
|
let [ toWidth, toHeight ] = stages.shift();
|
|
|
|
let isLastStage = (stages.length === 0);
|
|
|
|
opts = assign({}, opts, {
|
|
toWidth,
|
|
toHeight,
|
|
// only use user-defined quality for the last stage,
|
|
// use simpler (Hamming) filter for the first stages where
|
|
// scale factor is large enough (more than 2-3)
|
|
quality: isLastStage ? opts.quality : Math.min(1, opts.quality)
|
|
});
|
|
|
|
let tmpCanvas;
|
|
|
|
if (!isLastStage) {
|
|
// create temporary canvas
|
|
tmpCanvas = this.options.createCanvas(toWidth, toHeight);
|
|
}
|
|
|
|
return this.__tileAndResize(from, (isLastStage ? to : tmpCanvas), opts)
|
|
.then(() => {
|
|
if (isLastStage) return to;
|
|
|
|
opts.width = toWidth;
|
|
opts.height = toHeight;
|
|
return this.__processStages(stages, tmpCanvas, to, opts);
|
|
})
|
|
.then(res => {
|
|
if (tmpCanvas) {
|
|
// Safari 12 workaround
|
|
// https://github.com/nodeca/pica/issues/199
|
|
tmpCanvas.width = tmpCanvas.height = 0;
|
|
}
|
|
|
|
return res;
|
|
});
|
|
};
|
|
|
|
|
|
Pica.prototype.__resizeViaCreateImageBitmap = function (from, to, opts) {
|
|
let toCtx = to.getContext('2d', { alpha: Boolean(opts.alpha) });
|
|
|
|
this.debug('Resize via createImageBitmap()');
|
|
|
|
return createImageBitmap(from, {
|
|
resizeWidth: opts.toWidth,
|
|
resizeHeight: opts.toHeight,
|
|
resizeQuality: utils.cib_quality_name(opts.quality)
|
|
})
|
|
.then(imageBitmap => {
|
|
if (opts.canceled) return opts.cancelToken;
|
|
|
|
// if no unsharp - draw directly to output canvas
|
|
if (!opts.unsharpAmount) {
|
|
toCtx.drawImage(imageBitmap, 0, 0);
|
|
imageBitmap.close();
|
|
toCtx = null;
|
|
|
|
this.debug('Finished!');
|
|
|
|
return to;
|
|
}
|
|
|
|
this.debug('Unsharp result');
|
|
|
|
let tmpCanvas = this.options.createCanvas(opts.toWidth, opts.toHeight);
|
|
|
|
let tmpCtx = tmpCanvas.getContext('2d', { alpha: Boolean(opts.alpha) });
|
|
|
|
tmpCtx.drawImage(imageBitmap, 0, 0);
|
|
imageBitmap.close();
|
|
|
|
let iData = tmpCtx.getImageData(0, 0, opts.toWidth, opts.toHeight);
|
|
|
|
this.__mathlib.unsharp_mask(
|
|
iData.data,
|
|
opts.toWidth,
|
|
opts.toHeight,
|
|
opts.unsharpAmount,
|
|
opts.unsharpRadius,
|
|
opts.unsharpThreshold
|
|
);
|
|
|
|
toCtx.putImageData(iData, 0, 0);
|
|
|
|
// Safari 12 workaround
|
|
// https://github.com/nodeca/pica/issues/199
|
|
tmpCanvas.width = tmpCanvas.height = 0;
|
|
|
|
iData = tmpCtx = tmpCanvas = toCtx = null;
|
|
|
|
this.debug('Finished!');
|
|
|
|
return to;
|
|
});
|
|
};
|
|
|
|
|
|
Pica.prototype.resize = function (from, to, options) {
|
|
this.debug('Start resize...');
|
|
|
|
|
|
let opts = assign({}, DEFAULT_RESIZE_OPTS);
|
|
|
|
if (!isNaN(options)) {
|
|
opts = assign(opts, { quality: options });
|
|
} else if (options) {
|
|
opts = assign(opts, options);
|
|
}
|
|
|
|
opts.toWidth = to.width;
|
|
opts.toHeight = to.height;
|
|
opts.width = from.naturalWidth || from.width;
|
|
opts.height = from.naturalHeight || from.height;
|
|
|
|
// Prevent stepper from infinite loop
|
|
if (to.width === 0 || to.height === 0) {
|
|
return Promise.reject(new Error(`Invalid output size: ${to.width}x${to.height}`));
|
|
}
|
|
|
|
if (opts.unsharpRadius > 2) opts.unsharpRadius = 2;
|
|
|
|
opts.canceled = false;
|
|
|
|
if (opts.cancelToken) {
|
|
// Wrap cancelToken to avoid successive resolve & set flag
|
|
opts.cancelToken = opts.cancelToken.then(
|
|
data => { opts.canceled = true; throw data; },
|
|
err => { opts.canceled = true; throw err; }
|
|
);
|
|
}
|
|
|
|
let DEST_TILE_BORDER = 3; // Max possible filter window size
|
|
opts.__destTileBorder = Math.ceil(Math.max(DEST_TILE_BORDER, 2.5 * opts.unsharpRadius|0));
|
|
|
|
return this.init().then(() => {
|
|
if (opts.canceled) return opts.cancelToken;
|
|
|
|
// if createImageBitmap supports resize, just do it and return
|
|
if (this.features.cib) {
|
|
return this.__resizeViaCreateImageBitmap(from, to, opts);
|
|
}
|
|
|
|
if (!CAN_USE_CANVAS_GET_IMAGE_DATA) {
|
|
let err = new Error('Pica: cannot use getImageData on canvas, ' +
|
|
"make sure fingerprinting protection isn't enabled");
|
|
err.code = 'ERR_GET_IMAGE_DATA';
|
|
throw err;
|
|
}
|
|
|
|
//
|
|
// No easy way, let's resize manually via arrays
|
|
//
|
|
|
|
let stages = createStages(
|
|
opts.width,
|
|
opts.height,
|
|
opts.toWidth,
|
|
opts.toHeight,
|
|
this.options.tile,
|
|
opts.__destTileBorder
|
|
);
|
|
|
|
return this.__processStages(stages, from, to, opts);
|
|
});
|
|
};
|
|
|
|
// RGBA buffer resize
|
|
//
|
|
Pica.prototype.resizeBuffer = function (options) {
|
|
const opts = assign({}, DEFAULT_RESIZE_OPTS, options);
|
|
|
|
return this.init()
|
|
.then(() => this.__mathlib.resizeAndUnsharp(opts));
|
|
};
|
|
|
|
|
|
Pica.prototype.toBlob = function (canvas, mimeType, quality) {
|
|
mimeType = mimeType || 'image/png';
|
|
|
|
return new Promise(resolve => {
|
|
if (canvas.toBlob) {
|
|
canvas.toBlob(blob => resolve(blob), mimeType, quality);
|
|
return;
|
|
}
|
|
|
|
if (canvas.convertToBlob) {
|
|
resolve(canvas.convertToBlob({
|
|
type: mimeType,
|
|
quality
|
|
}));
|
|
return;
|
|
}
|
|
|
|
// Fallback for old browsers
|
|
const asString = atob(canvas.toDataURL(mimeType, quality).split(',')[1]);
|
|
const len = asString.length;
|
|
const asBuffer = new Uint8Array(len);
|
|
|
|
for (let i = 0; i < len; i++) {
|
|
asBuffer[i] = asString.charCodeAt(i);
|
|
}
|
|
|
|
resolve(new Blob([ asBuffer ], { type: mimeType }));
|
|
});
|
|
};
|
|
|
|
|
|
Pica.prototype.debug = function () {};
|
|
|
|
|
|
module.exports = Pica;
|