//JavaScript Image Resizer (c) 2012 - Grant Galitz var scripts = document.getElementsByTagName("script"); var sourceOfWorker = scripts[scripts.length-1].src; function Resize(widthOriginal, heightOriginal, targetWidth, targetHeight, blendAlpha, interpolationPass, useWebWorker, resizeCallback) { this.widthOriginal = Math.abs(parseInt(widthOriginal) || 0); this.heightOriginal = Math.abs(parseInt(heightOriginal) || 0); this.targetWidth = Math.abs(parseInt(targetWidth) || 0); this.targetHeight = Math.abs(parseInt(targetHeight) || 0); this.colorChannels = (!!blendAlpha) ? 4 : 3; this.interpolationPass = !!interpolationPass; this.useWebWorker = !!useWebWorker; this.resizeCallback = (typeof resizeCallback == "function") ? resizeCallback : function (returnedArray) {}; this.targetWidthMultipliedByChannels = this.targetWidth * this.colorChannels; this.originalWidthMultipliedByChannels = this.widthOriginal * this.colorChannels; this.originalHeightMultipliedByChannels = this.heightOriginal * this.colorChannels; this.widthPassResultSize = this.targetWidthMultipliedByChannels * this.heightOriginal; this.finalResultSize = this.targetWidthMultipliedByChannels * this.targetHeight; this.initialize(); } Resize.prototype.initialize = function () { //Perform some checks: if (this.widthOriginal > 0 && this.heightOriginal > 0 && this.targetWidth > 0 && this.targetHeight > 0) { if (this.useWebWorker) { this.useWebWorker = (this.widthOriginal != this.targetWidth || this.heightOriginal != this.targetHeight); if (this.useWebWorker) { this.configureWorker(); } } if (!this.useWebWorker) { this.configurePasses(); } } else { throw(new Error("Invalid settings specified for the resizer.")); } } Resize.prototype.configureWorker = function () { try { var parentObj = this; this.worker = new Worker(sourceOfWorker.substring(0, sourceOfWorker.length - 3) + "Worker.js"); this.worker.onmessage = function (event) { parentObj.heightBuffer = event.data; parentObj.resizeCallback(parentObj.heightBuffer); } this.worker.postMessage(["setup", this.widthOriginal, this.heightOriginal, this.targetWidth, this.targetHeight, this.colorChannels, this.interpolationPass]); } catch (error) { this.useWebWorker = false; } } Resize.prototype.configurePasses = function () { if (this.widthOriginal == this.targetWidth) { //Bypass the width resizer pass: this.resizeWidth = this.bypassResizer; } else { //Setup the width resizer pass: this.ratioWeightWidthPass = this.widthOriginal / this.targetWidth; if (this.ratioWeightWidthPass < 1 && this.interpolationPass) { this.initializeFirstPassBuffers(true); this.resizeWidth = (this.colorChannels == 4) ? this.resizeWidthInterpolatedRGBA : this.resizeWidthInterpolatedRGB; } else { this.initializeFirstPassBuffers(false); this.resizeWidth = (this.colorChannels == 4) ? this.resizeWidthRGBA : this.resizeWidthRGB; } } if (this.heightOriginal == this.targetHeight) { //Bypass the height resizer pass: this.resizeHeight = this.bypassResizer; } else { //Setup the height resizer pass: this.ratioWeightHeightPass = this.heightOriginal / this.targetHeight; if (this.ratioWeightHeightPass < 1 && this.interpolationPass) { this.initializeSecondPassBuffers(true); this.resizeHeight = this.resizeHeightInterpolated; } else { this.initializeSecondPassBuffers(false); this.resizeHeight = (this.colorChannels == 4) ? this.resizeHeightRGBA : this.resizeHeightRGB; } } } Resize.prototype.resizeWidthRGB = function (buffer) { var ratioWeight = this.ratioWeightWidthPass; var ratioWeightDivisor = 1 / ratioWeight; var weight = 0; var amountToNext = 0; var actualPosition = 0; var currentPosition = 0; var line = 0; var pixelOffset = 0; var outputOffset = 0; var nextLineOffsetOriginalWidth = this.originalWidthMultipliedByChannels - 2; var nextLineOffsetTargetWidth = this.targetWidthMultipliedByChannels - 2; var output = this.outputWidthWorkBench; var outputBuffer = this.widthBuffer; do { for (line = 0; line < this.originalHeightMultipliedByChannels;) { output[line++] = 0; output[line++] = 0; output[line++] = 0; } weight = ratioWeight; do { amountToNext = 1 + actualPosition - currentPosition; if (weight >= amountToNext) { for (line = 0, pixelOffset = actualPosition; line < this.originalHeightMultipliedByChannels; pixelOffset += nextLineOffsetOriginalWidth) { output[line++] += buffer[pixelOffset++] * amountToNext; output[line++] += buffer[pixelOffset++] * amountToNext; output[line++] += buffer[pixelOffset] * amountToNext; } currentPosition = actualPosition = actualPosition + 3; weight -= amountToNext; } else { for (line = 0, pixelOffset = actualPosition; line < this.originalHeightMultipliedByChannels; pixelOffset += nextLineOffsetOriginalWidth) { output[line++] += buffer[pixelOffset++] * weight; output[line++] += buffer[pixelOffset++] * weight; output[line++] += buffer[pixelOffset] * weight; } currentPosition += weight; break; } } while (weight > 0 && actualPosition < this.originalWidthMultipliedByChannels); for (line = 0, pixelOffset = outputOffset; line < this.originalHeightMultipliedByChannels; pixelOffset += nextLineOffsetTargetWidth) { outputBuffer[pixelOffset++] = output[line++] * ratioWeightDivisor; outputBuffer[pixelOffset++] = output[line++] * ratioWeightDivisor; outputBuffer[pixelOffset] = output[line++] * ratioWeightDivisor; } outputOffset += 3; } while (outputOffset < this.targetWidthMultipliedByChannels); return outputBuffer; } Resize.prototype.resizeWidthInterpolatedRGB = function (buffer) { var ratioWeight = this.ratioWeightWidthPass; var weight = 0; var finalOffset = 0; var pixelOffset = 0; var firstWeight = 0; var secondWeight = 0; var outputBuffer = this.widthBuffer; //Handle for only one interpolation input being valid for start calculation: for (var targetPosition = 0; weight < 1/3; targetPosition += 3, weight += ratioWeight) { for (finalOffset = targetPosition, pixelOffset = 0; finalOffset < this.widthPassResultSize; pixelOffset += this.originalWidthMultipliedByChannels, finalOffset += this.targetWidthMultipliedByChannels) { outputBuffer[finalOffset] = buffer[pixelOffset]; outputBuffer[finalOffset + 1] = buffer[pixelOffset + 1]; outputBuffer[finalOffset + 2] = buffer[pixelOffset + 2]; } } //Adjust for overshoot of the last pass's counter: weight -= 1/3; for (var interpolationWidthSourceReadStop = this.widthOriginal - 1; weight < interpolationWidthSourceReadStop; targetPosition += 3, weight += ratioWeight) { //Calculate weightings: secondWeight = weight % 1; firstWeight = 1 - secondWeight; //Interpolate: for (finalOffset = targetPosition, pixelOffset = Math.floor(weight) * 3; finalOffset < this.widthPassResultSize; pixelOffset += this.originalWidthMultipliedByChannels, finalOffset += this.targetWidthMultipliedByChannels) { outputBuffer[finalOffset] = (buffer[pixelOffset] * firstWeight) + (buffer[pixelOffset + 3] * secondWeight); outputBuffer[finalOffset + 1] = (buffer[pixelOffset + 1] * firstWeight) + (buffer[pixelOffset + 4] * secondWeight); outputBuffer[finalOffset + 2] = (buffer[pixelOffset + 2] * firstWeight) + (buffer[pixelOffset + 5] * secondWeight); } } //Handle for only one interpolation input being valid for end calculation: for (interpolationWidthSourceReadStop = this.originalWidthMultipliedByChannels - 3; targetPosition < this.targetWidthMultipliedByChannels; targetPosition += 3) { for (finalOffset = targetPosition, pixelOffset = interpolationWidthSourceReadStop; finalOffset < this.widthPassResultSize; pixelOffset += this.originalWidthMultipliedByChannels, finalOffset += this.targetWidthMultipliedByChannels) { outputBuffer[finalOffset] = buffer[pixelOffset]; outputBuffer[finalOffset + 1] = buffer[pixelOffset + 1]; outputBuffer[finalOffset + 2] = buffer[pixelOffset + 2]; } } return outputBuffer; } Resize.prototype.resizeWidthRGBA = function (buffer) { var ratioWeight = this.ratioWeightWidthPass; var ratioWeightDivisor = 1 / ratioWeight; var weight = 0; var amountToNext = 0; var actualPosition = 0; var currentPosition = 0; var line = 0; var pixelOffset = 0; var outputOffset = 0; var nextLineOffsetOriginalWidth = this.originalWidthMultipliedByChannels - 3; var nextLineOffsetTargetWidth = this.targetWidthMultipliedByChannels - 3; var output = this.outputWidthWorkBench; var outputBuffer = this.widthBuffer; do { for (line = 0; line < this.originalHeightMultipliedByChannels;) { output[line++] = 0; output[line++] = 0; output[line++] = 0; output[line++] = 0; } weight = ratioWeight; do { amountToNext = 1 + actualPosition - currentPosition; if (weight >= amountToNext) { for (line = 0, pixelOffset = actualPosition; line < this.originalHeightMultipliedByChannels; pixelOffset += nextLineOffsetOriginalWidth) { output[line++] += buffer[pixelOffset++] * amountToNext; output[line++] += buffer[pixelOffset++] * amountToNext; output[line++] += buffer[pixelOffset++] * amountToNext; output[line++] += buffer[pixelOffset] * amountToNext; } currentPosition = actualPosition = actualPosition + 4; weight -= amountToNext; } else { for (line = 0, pixelOffset = actualPosition; line < this.originalHeightMultipliedByChannels; pixelOffset += nextLineOffsetOriginalWidth) { output[line++] += buffer[pixelOffset++] * weight; output[line++] += buffer[pixelOffset++] * weight; output[line++] += buffer[pixelOffset++] * weight; output[line++] += buffer[pixelOffset] * weight; } currentPosition += weight; break; } } while (weight > 0 && actualPosition < this.originalWidthMultipliedByChannels); for (line = 0, pixelOffset = outputOffset; line < this.originalHeightMultipliedByChannels; pixelOffset += nextLineOffsetTargetWidth) { outputBuffer[pixelOffset++] = output[line++] * ratioWeightDivisor; outputBuffer[pixelOffset++] = output[line++] * ratioWeightDivisor; outputBuffer[pixelOffset++] = output[line++] * ratioWeightDivisor; outputBuffer[pixelOffset] = output[line++] * ratioWeightDivisor; } outputOffset += 4; } while (outputOffset < this.targetWidthMultipliedByChannels); return outputBuffer; } Resize.prototype.resizeWidthInterpolatedRGBA = function (buffer) { var ratioWeight = this.ratioWeightWidthPass; var weight = 0; var finalOffset = 0; var pixelOffset = 0; var firstWeight = 0; var secondWeight = 0; var outputBuffer = this.widthBuffer; //Handle for only one interpolation input being valid for start calculation: for (var targetPosition = 0; weight < 1/3; targetPosition += 4, weight += ratioWeight) { for (finalOffset = targetPosition, pixelOffset = 0; finalOffset < this.widthPassResultSize; pixelOffset += this.originalWidthMultipliedByChannels, finalOffset += this.targetWidthMultipliedByChannels) { outputBuffer[finalOffset] = buffer[pixelOffset]; outputBuffer[finalOffset + 1] = buffer[pixelOffset + 1]; outputBuffer[finalOffset + 2] = buffer[pixelOffset + 2]; outputBuffer[finalOffset + 3] = buffer[pixelOffset + 3]; } } //Adjust for overshoot of the last pass's counter: weight -= 1/3; for (var interpolationWidthSourceReadStop = this.widthOriginal - 1; weight < interpolationWidthSourceReadStop; targetPosition += 4, weight += ratioWeight) { //Calculate weightings: secondWeight = weight % 1; firstWeight = 1 - secondWeight; //Interpolate: for (finalOffset = targetPosition, pixelOffset = Math.floor(weight) * 4; finalOffset < this.widthPassResultSize; pixelOffset += this.originalWidthMultipliedByChannels, finalOffset += this.targetWidthMultipliedByChannels) { outputBuffer[finalOffset] = (buffer[pixelOffset] * firstWeight) + (buffer[pixelOffset + 4] * secondWeight); outputBuffer[finalOffset + 1] = (buffer[pixelOffset + 1] * firstWeight) + (buffer[pixelOffset + 5] * secondWeight); outputBuffer[finalOffset + 2] = (buffer[pixelOffset + 2] * firstWeight) + (buffer[pixelOffset + 6] * secondWeight); outputBuffer[finalOffset + 3] = (buffer[pixelOffset + 3] * firstWeight) + (buffer[pixelOffset + 7] * secondWeight); } } //Handle for only one interpolation input being valid for end calculation: for (interpolationWidthSourceReadStop = this.originalWidthMultipliedByChannels - 4; targetPosition < this.targetWidthMultipliedByChannels; targetPosition += 4) { for (finalOffset = targetPosition, pixelOffset = interpolationWidthSourceReadStop; finalOffset < this.widthPassResultSize; pixelOffset += this.originalWidthMultipliedByChannels, finalOffset += this.targetWidthMultipliedByChannels) { outputBuffer[finalOffset] = buffer[pixelOffset]; outputBuffer[finalOffset + 1] = buffer[pixelOffset + 1]; outputBuffer[finalOffset + 2] = buffer[pixelOffset + 2]; outputBuffer[finalOffset + 3] = buffer[pixelOffset + 3]; } } return outputBuffer; } Resize.prototype.resizeHeightRGB = function (buffer) { var ratioWeight = this.ratioWeightHeightPass; var ratioWeightDivisor = 1 / ratioWeight; var weight = 0; var amountToNext = 0; var actualPosition = 0; var currentPosition = 0; var pixelOffset = 0; var outputOffset = 0; var output = this.outputHeightWorkBench; var outputBuffer = this.heightBuffer; do { for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) { output[pixelOffset++] = 0; output[pixelOffset++] = 0; output[pixelOffset++] = 0; } weight = ratioWeight; do { amountToNext = 1 + actualPosition - currentPosition; if (weight >= amountToNext) { for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) { output[pixelOffset++] += buffer[actualPosition++] * amountToNext; output[pixelOffset++] += buffer[actualPosition++] * amountToNext; output[pixelOffset++] += buffer[actualPosition++] * amountToNext; } currentPosition = actualPosition; weight -= amountToNext; } else { for (pixelOffset = 0, amountToNext = actualPosition; pixelOffset < this.targetWidthMultipliedByChannels;) { output[pixelOffset++] += buffer[amountToNext++] * weight; output[pixelOffset++] += buffer[amountToNext++] * weight; output[pixelOffset++] += buffer[amountToNext++] * weight; } currentPosition += weight; break; } } while (weight > 0 && actualPosition < this.widthPassResultSize); for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) { outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] * ratioWeightDivisor); outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] * ratioWeightDivisor); outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] * ratioWeightDivisor); } } while (outputOffset < this.finalResultSize); return outputBuffer; } Resize.prototype.resizeHeightInterpolated = function (buffer) { var ratioWeight = this.ratioWeightHeightPass; var weight = 0; var finalOffset = 0; var pixelOffset = 0; var pixelOffsetAccumulated = 0; var pixelOffsetAccumulated2 = 0; var firstWeight = 0; var secondWeight = 0; var outputBuffer = this.heightBuffer; //Handle for only one interpolation input being valid for start calculation: for (; weight < 1/3; weight += ratioWeight) { for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) { outputBuffer[finalOffset++] = Math.round(buffer[pixelOffset++]); } } //Adjust for overshoot of the last pass's counter: weight -= 1/3; for (var interpolationHeightSourceReadStop = this.heightOriginal - 1; weight < interpolationHeightSourceReadStop; weight += ratioWeight) { //Calculate weightings: secondWeight = weight % 1; firstWeight = 1 - secondWeight; //Interpolate: pixelOffsetAccumulated = Math.floor(weight) * this.targetWidthMultipliedByChannels; pixelOffsetAccumulated2 = pixelOffsetAccumulated + this.targetWidthMultipliedByChannels; for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels; ++pixelOffset) { outputBuffer[finalOffset++] = Math.round((buffer[pixelOffsetAccumulated++] * firstWeight) + (buffer[pixelOffsetAccumulated2++] * secondWeight)); } } //Handle for only one interpolation input being valid for end calculation: while (finalOffset < this.finalResultSize) { for (pixelOffset = 0, pixelOffsetAccumulated = interpolationHeightSourceReadStop * this.targetWidthMultipliedByChannels; pixelOffset < this.targetWidthMultipliedByChannels; ++pixelOffset) { outputBuffer[finalOffset++] = Math.round(buffer[pixelOffsetAccumulated++]); } } return outputBuffer; } Resize.prototype.resizeHeightRGBA = function (buffer) { var ratioWeight = this.ratioWeightHeightPass; var ratioWeightDivisor = 1 / ratioWeight; var weight = 0; var amountToNext = 0; var actualPosition = 0; var currentPosition = 0; var pixelOffset = 0; var outputOffset = 0; var output = this.outputHeightWorkBench; var outputBuffer = this.heightBuffer; do { for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) { output[pixelOffset++] = 0; output[pixelOffset++] = 0; output[pixelOffset++] = 0; output[pixelOffset++] = 0; } weight = ratioWeight; do { amountToNext = 1 + actualPosition - currentPosition; if (weight >= amountToNext) { for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) { output[pixelOffset++] += buffer[actualPosition++] * amountToNext; output[pixelOffset++] += buffer[actualPosition++] * amountToNext; output[pixelOffset++] += buffer[actualPosition++] * amountToNext; output[pixelOffset++] += buffer[actualPosition++] * amountToNext; } currentPosition = actualPosition; weight -= amountToNext; } else { for (pixelOffset = 0, amountToNext = actualPosition; pixelOffset < this.targetWidthMultipliedByChannels;) { output[pixelOffset++] += buffer[amountToNext++] * weight; output[pixelOffset++] += buffer[amountToNext++] * weight; output[pixelOffset++] += buffer[amountToNext++] * weight; output[pixelOffset++] += buffer[amountToNext++] * weight; } currentPosition += weight; break; } } while (weight > 0 && actualPosition < this.widthPassResultSize); for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) { outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] * ratioWeightDivisor); outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] * ratioWeightDivisor); outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] * ratioWeightDivisor); outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] * ratioWeightDivisor); } } while (outputOffset < this.finalResultSize); return outputBuffer; } Resize.prototype.resize = function (buffer) { if (this.useWebWorker) { this.worker.postMessage(["resize", buffer]); } else { this.resizeCallback(this.resizeHeight(this.resizeWidth(buffer))); } } Resize.prototype.bypassResizer = function (buffer) { //Just return the buffer passsed: return buffer; } Resize.prototype.initializeFirstPassBuffers = function (BILINEARAlgo) { //Initialize the internal width pass buffers: this.widthBuffer = this.generateFloatBuffer(this.widthPassResultSize); if (!BILINEARAlgo) { this.outputWidthWorkBench = this.generateFloatBuffer(this.originalHeightMultipliedByChannels); } } Resize.prototype.initializeSecondPassBuffers = function (BILINEARAlgo) { //Initialize the internal height pass buffers: this.heightBuffer = this.generateUint8Buffer(this.finalResultSize); if (!BILINEARAlgo) { this.outputHeightWorkBench = this.generateFloatBuffer(this.targetWidthMultipliedByChannels); } } Resize.prototype.generateFloatBuffer = function (bufferLength) { //Generate a float32 typed array buffer: try { return new Float32Array(bufferLength); } catch (error) { return []; } } Resize.prototype.generateUint8Buffer = function (bufferLength) { //Generate a uint8 typed array buffer: try { return new Uint8Array(bufferLength); } catch (error) { return []; } }