Skip to content

Commit

Permalink
Process images via tiles
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexander Rodin committed Feb 14, 2016
1 parent 39e7643 commit 7ecf600
Show file tree
Hide file tree
Showing 15 changed files with 352 additions and 177 deletions.
2 changes: 1 addition & 1 deletion benchmark/implementations/pica-current-lightness/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

var lightness = require('../../../lib/pure/unsharp').lightness;
var lightness = require('../../../lib/js/unsharp').lightness;

exports.run = function(data) {
var buffer = data.buffer;
Expand Down
2 changes: 1 addition & 1 deletion benchmark/implementations/pica-current-unsharp/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

var unsharp = require('../../../lib/pure/unsharp');
var unsharp = require('../../../lib/js/unsharp');

exports.run = function(data) {
var b;
Expand Down
4 changes: 2 additions & 2 deletions benchmark/implementations/pica-current/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
'use strict';

var pica = require('../../../');
var resize = require('../../../lib/js/resize_array');

exports.run = function(data) {
var out_result;

pica.resizeBuffer({
resize({
src: data.buffer,
width: data.width,
height: data.height,
Expand Down
255 changes: 158 additions & 97 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,97 +34,176 @@ try {
__cvs = null;
}

var resize = require('./lib/resize');
var resizeWorker = require('./lib/resize_worker');
var resizeWebgl = require('./lib/resize_webgl');

var ResizerJS = require('./lib/resizer_js');
var ResizerJSWW = require('./lib/resizer_js_ww');
var ResizerWebgl = require('./lib/resizer_webgl');

////////////////////////////////////////////////////////////////////////////////
// Helpers
function _class(obj) { return Object.prototype.toString.call(obj); }
function isFunction(obj) { return _class(obj) === '[object Function]'; }

/////////////////////////////////////////////////////////////////////////////////
// Making tiles

var SRC_TILE_SIZE = 512;
var DEST_TILE_BORDER = 3;

function createRegions(fromWidth, fromHeight, toWidth, toHeight) {
var scaleX = toWidth / fromWidth;
var scaleY = toHeight / fromHeight;

var innerTileWidth = Math.floor(SRC_TILE_SIZE * scaleX) - 2 * DEST_TILE_BORDER;
var innerTileHeight = Math.floor(SRC_TILE_SIZE * scaleY) - 2 * DEST_TILE_BORDER;

var x, y;
var innerX, innerY, toTileWidth, toTileHeight;
var tiles = [];
var tile;

// we go top-to-down instead of left-to-right to make image displayed from top to
// doesn in the browser
for (innerY = 0; innerY < toHeight; innerY += innerTileHeight) {
for (innerX = 0; innerX < toWidth; innerX += innerTileWidth) {
x = innerX - DEST_TILE_BORDER;
if (x < 0) { x = 0; }
toTileWidth = innerX + innerTileWidth + DEST_TILE_BORDER - x;
if (x + toTileWidth >= toWidth) {
toTileWidth = toWidth - x;
}

////////////////////////////////////////////////////////////////////////////////
// API methods
y = innerY - DEST_TILE_BORDER;
if (y < 0) { y = 0; }
toTileHeight = innerY + innerTileHeight + DEST_TILE_BORDER - y;
if (y + toTileHeight >= toHeight) {
toTileHeight = toHeight - y;
}

tile = {
toX: x,
toY: y,
toWidth: toTileWidth,
toHeight: toTileHeight,

toInnerX: innerX,
toInnerY: innerY,
toInnerWidth: innerTileWidth,
toInnerHeight: innerTileHeight,

offsetX: x / scaleX - Math.floor(x / scaleX),
offsetY: y / scaleY - Math.floor(y / scaleY),
scaleX: scaleX,
scaleY: scaleY,

x: Math.floor(x / scaleX),
y: Math.floor(y / scaleY),
width: Math.ceil(toTileWidth / scaleX),
height: Math.ceil(toTileHeight / scaleY)
};

tiles.push(tile);
}
}

// RGBA buffer async resize
//
function resizeBuffer(options, callback) {
var wr;

var _opts = {
src: options.src,
dest: null,
width: options.width|0,
height: options.height|0,
toWidth: options.toWidth|0,
toHeight: options.toHeight|0,
quality: options.quality,
alpha: options.alpha,
unsharpAmount: options.unsharpAmount,
unsharpRadius: options.unsharpRadius,
unsharpThreshold: options.unsharpThreshold
};
return tiles;
}

// Force flag reset to simplify status check
if (!WORKER) { exports.WW = false; }
function eachLimit(list, limit, iterator, callback) {
if (list.length === 0) {
callback();
}

if (WORKER && exports.WW) {
exports.debug('Resize buffer in WebWorker');
var current = 0;
var failed = false;

wr = require('webworkify')(resizeWorker);

wr.onmessage = function(ev) {
var i, l,
dest = options.dest,
output = ev.data.output;

// If we got output buffer by reference, we should copy data,
// because WW returns independent instance
if (dest) {
// IE ImageData can return old-style CanvasPixelArray
// without .set() method. Copy manually for such case.
if (dest.set) {
dest.set(output);
} else {
for (i = 0, l = output.length; i < l; i++) {
dest[i] = output[i];
}
}
var next = function (err) {
if (err) {
if (!failed) {
failed = true;
callback(err);
}
callback(ev.data.err, output);
wr.terminate();
};
return;
}

if (options.transferable) {
wr.postMessage(_opts, [ options.src.buffer ]);
if (current < list.length) {
iterator(list[current++], next);
} else {
wr.postMessage(_opts);
callback();
}
// Expose worker when available, to allow early termination.
return wr;
};

for (current = 0; current < limit && current < list.length; current++) {
iterator(list[current], next);
}
}

// Fallback to sync call, if WebWorkers not available
exports.debug('Resize buffer sync (freeze event loop)');
function resizeTiled (from, to, options, resizer, callback) {
var regions = createRegions(from.width, from.height, to.width, to.height);
var toCtx = to.getContext('2d');

var canvasPool = [];
var i, tileData;

for (i = 0; i < resizer.concurrency; i++) {
tileData = {
src: document.createElement('canvas'),
dest: document.createElement('canvas')
};
tileData.src.width = SRC_TILE_SIZE;
tileData.src.height = SRC_TILE_SIZE;
tileData.dest.width = Math.floor(SRC_TILE_SIZE * to.width / from.width);
tileData.dest.height = Math.floor(SRC_TILE_SIZE * to.height / from.height);

canvasPool.push(tileData);
}

eachLimit(regions, resizer.concurrency, function (tile, next) {
var canvases = canvasPool.pop();

canvases.src.getContext('2d').drawImage(from,
tile.x, tile.y, tile.width, tile.height,
0, 0, tile.width, tile.height);

var _opts = {
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: options.quality,
alpha: options.alpha,
unsharpAmount: options.unsharpAmount,
unsharpRadius: options.unsharpRadius,
unsharpThreshold: options.unsharpThreshold
};

resizer.resize(canvases.src, canvases.dest, _opts, function (err) {
if (!err) {
toCtx.drawImage(canvases.dest,
tile.toInnerX - tile.toX, tile.toInnerY - tile.toY,
tile.toInnerWidth, tile.toInnerHeight,
tile.toInnerX, tile.toInnerY,
tile.toInnerWidth, tile.toInnerHeight);
}

_opts.dest = options.dest;
resize(_opts, callback);
return null;
canvasPool.push(canvases);
next(err);
});
}, function (err) {
resizer.cleanup();
callback(err);
});
}

////////////////////////////////////////////////////////////////////////////////
// API methods

// Canvas async resize
//
function resizeCanvas(from, to, options, callback) {
var w = from.width,
h = from.height,
w2 = to.width,
h2 = to.height;
var ctxTo, imageDataTo;

if (isFunction(options)) {
callback = options;
options = {};
Expand All @@ -140,52 +219,34 @@ function resizeCanvas(from, to, options, callback) {
if (WEBGL && exports.WEBGL) {
exports.debug('Resize canvas with WebGL');

return resizeWebgl(from, to, options, function (err) {
return resizeTiled(from, to, options, new ResizerWebgl(), function (err) {
if (err) {
exports.debug('WebGL resize failed, do fallback and cancel next attempts');
exports.debug(err);

WEBGL = false;
return resizeCanvas(from, to, options, callback);
resizeCanvas(from, to, options, callback);
}
callback();
});

}

exports.debug('Resize canvas: prepare data');

ctxTo = to.getContext('2d');
imageDataTo = ctxTo.createImageData(w2, h2);

var _opts = {
src: from.getContext('2d').getImageData(0, 0, w, h).data,
dest: imageDataTo.data,
width: from.width,
height: from.height,
toWidth: to.width,
toHeight: to.height,
quality: options.quality,
alpha: options.alpha,
unsharpAmount: options.unsharpAmount,
unsharpRadius: options.unsharpRadius,
unsharpThreshold: options.unsharpThreshold,
transferable: true
};
// Force flag reset to simplify status check
if (!WORKER) { exports.WW = false; }

return resizeBuffer(_opts, function (err/*, output*/) {
if (err) {
callback(err);
return;
}
if (WORKER && exports.WW) {
exports.debug('Resize buffer in WebWorker');

ctxTo.putImageData(imageDataTo, 0, 0);
callback();
});
return resizeTiled(from, to, options, new ResizerJSWW(), callback);
}

// Fallback to sync call, if WebWorkers not available
exports.debug('Resize buffer sync (freeze event loop)');

return resizeTiled(from, to, options, new ResizerJS(), callback);
}


exports.resizeBuffer = resizeBuffer;
exports.resizeCanvas = resizeCanvas;
exports.WW = WORKER;
exports.WEBGL = false; // WEBGL;
Expand Down

0 comments on commit 7ecf600

Please sign in to comment.