Path: blob/master/node_modules/@jimp/plugin-resize/src/modules/resize.js
1126 views
// JavaScript Image Resizer (c) 2012 - Grant Galitz1// Released to public domain 29 July 2013: https://github.com/grantgalitz/JS-Image-Resizer/issues/423function Resize(4widthOriginal,5heightOriginal,6targetWidth,7targetHeight,8blendAlpha,9interpolationPass,10resizeCallback11) {12this.widthOriginal = Math.abs(Math.floor(widthOriginal) || 0);13this.heightOriginal = Math.abs(Math.floor(heightOriginal) || 0);14this.targetWidth = Math.abs(Math.floor(targetWidth) || 0);15this.targetHeight = Math.abs(Math.floor(targetHeight) || 0);16this.colorChannels = blendAlpha ? 4 : 3;17this.interpolationPass = Boolean(interpolationPass);18this.resizeCallback =19typeof resizeCallback === 'function' ? resizeCallback : function() {};2021this.targetWidthMultipliedByChannels = this.targetWidth * this.colorChannels;22this.originalWidthMultipliedByChannels =23this.widthOriginal * this.colorChannels;24this.originalHeightMultipliedByChannels =25this.heightOriginal * this.colorChannels;26this.widthPassResultSize =27this.targetWidthMultipliedByChannels * this.heightOriginal;28this.finalResultSize =29this.targetWidthMultipliedByChannels * this.targetHeight;30this.initialize();31}3233Resize.prototype.initialize = function() {34// Perform some checks:35if (36this.widthOriginal > 0 &&37this.heightOriginal > 0 &&38this.targetWidth > 0 &&39this.targetHeight > 040) {41this.configurePasses();42} else {43throw new Error('Invalid settings specified for the resizer.');44}45};4647Resize.prototype.configurePasses = function() {48if (this.widthOriginal === this.targetWidth) {49// Bypass the width resizer pass:50this.resizeWidth = this.bypassResizer;51} else {52// Setup the width resizer pass:53this.ratioWeightWidthPass = this.widthOriginal / this.targetWidth;54if (this.ratioWeightWidthPass < 1 && this.interpolationPass) {55this.initializeFirstPassBuffers(true);56this.resizeWidth =57this.colorChannels === 458? this.resizeWidthInterpolatedRGBA59: this.resizeWidthInterpolatedRGB;60} else {61this.initializeFirstPassBuffers(false);62this.resizeWidth =63this.colorChannels === 4 ? this.resizeWidthRGBA : this.resizeWidthRGB;64}65}6667if (this.heightOriginal === this.targetHeight) {68// Bypass the height resizer pass:69this.resizeHeight = this.bypassResizer;70} else {71// Setup the height resizer pass:72this.ratioWeightHeightPass = this.heightOriginal / this.targetHeight;73if (this.ratioWeightHeightPass < 1 && this.interpolationPass) {74this.initializeSecondPassBuffers(true);75this.resizeHeight = this.resizeHeightInterpolated;76} else {77this.initializeSecondPassBuffers(false);78this.resizeHeight =79this.colorChannels === 4 ? this.resizeHeightRGBA : this.resizeHeightRGB;80}81}82};8384Resize.prototype._resizeWidthInterpolatedRGBChannels = function(85buffer,86fourthChannel87) {88const channelsNum = fourthChannel ? 4 : 3;89const ratioWeight = this.ratioWeightWidthPass;90const outputBuffer = this.widthBuffer;9192let weight = 0;93let finalOffset = 0;94let pixelOffset = 0;95let firstWeight = 0;96let secondWeight = 0;97let targetPosition;9899// Handle for only one interpolation input being valid for start calculation:100for (101targetPosition = 0;102weight < 1 / 3;103targetPosition += channelsNum, weight += ratioWeight104) {105for (106finalOffset = targetPosition, pixelOffset = 0;107finalOffset < this.widthPassResultSize;108pixelOffset += this.originalWidthMultipliedByChannels,109finalOffset += this.targetWidthMultipliedByChannels110) {111outputBuffer[finalOffset] = buffer[pixelOffset];112outputBuffer[finalOffset + 1] = buffer[pixelOffset + 1];113outputBuffer[finalOffset + 2] = buffer[pixelOffset + 2];114if (fourthChannel)115outputBuffer[finalOffset + 3] = buffer[pixelOffset + 3];116}117}118119// Adjust for overshoot of the last pass's counter:120weight -= 1 / 3;121let interpolationWidthSourceReadStop;122123for (124interpolationWidthSourceReadStop = this.widthOriginal - 1;125weight < interpolationWidthSourceReadStop;126targetPosition += channelsNum, weight += ratioWeight127) {128// Calculate weightings:129secondWeight = weight % 1;130firstWeight = 1 - secondWeight;131// Interpolate:132for (133finalOffset = targetPosition,134pixelOffset = Math.floor(weight) * channelsNum;135finalOffset < this.widthPassResultSize;136pixelOffset += this.originalWidthMultipliedByChannels,137finalOffset += this.targetWidthMultipliedByChannels138) {139outputBuffer[finalOffset + 0] =140buffer[pixelOffset + 0] * firstWeight +141buffer[pixelOffset + channelsNum + 0] * secondWeight;142outputBuffer[finalOffset + 1] =143buffer[pixelOffset + 1] * firstWeight +144buffer[pixelOffset + channelsNum + 1] * secondWeight;145outputBuffer[finalOffset + 2] =146buffer[pixelOffset + 2] * firstWeight +147buffer[pixelOffset + channelsNum + 2] * secondWeight;148if (fourthChannel)149outputBuffer[finalOffset + 3] =150buffer[pixelOffset + 3] * firstWeight +151buffer[pixelOffset + channelsNum + 3] * secondWeight;152}153}154155// Handle for only one interpolation input being valid for end calculation:156for (157interpolationWidthSourceReadStop =158this.originalWidthMultipliedByChannels - channelsNum;159targetPosition < this.targetWidthMultipliedByChannels;160targetPosition += channelsNum161) {162for (163finalOffset = targetPosition,164pixelOffset = interpolationWidthSourceReadStop;165finalOffset < this.widthPassResultSize;166pixelOffset += this.originalWidthMultipliedByChannels,167finalOffset += this.targetWidthMultipliedByChannels168) {169outputBuffer[finalOffset] = buffer[pixelOffset];170outputBuffer[finalOffset + 1] = buffer[pixelOffset + 1];171outputBuffer[finalOffset + 2] = buffer[pixelOffset + 2];172if (fourthChannel)173outputBuffer[finalOffset + 3] = buffer[pixelOffset + 3];174}175}176177return outputBuffer;178};179180Resize.prototype._resizeWidthRGBChannels = function(buffer, fourthChannel) {181const channelsNum = fourthChannel ? 4 : 3;182const ratioWeight = this.ratioWeightWidthPass;183const ratioWeightDivisor = 1 / ratioWeight;184const nextLineOffsetOriginalWidth =185this.originalWidthMultipliedByChannels - channelsNum + 1;186const nextLineOffsetTargetWidth =187this.targetWidthMultipliedByChannels - channelsNum + 1;188const output = this.outputWidthWorkBench;189const outputBuffer = this.widthBuffer;190const trustworthyColorsCount = this.outputWidthWorkBenchOpaquePixelsCount;191192let weight = 0;193let amountToNext = 0;194let actualPosition = 0;195let currentPosition = 0;196let line = 0;197let pixelOffset = 0;198let outputOffset = 0;199let multiplier = 1;200let r = 0;201let g = 0;202let b = 0;203let a = 0;204205do {206for (line = 0; line < this.originalHeightMultipliedByChannels; ) {207output[line++] = 0;208output[line++] = 0;209output[line++] = 0;210if (fourthChannel) {211output[line++] = 0;212trustworthyColorsCount[line / channelsNum - 1] = 0;213}214}215216weight = ratioWeight;217218do {219amountToNext = 1 + actualPosition - currentPosition;220multiplier = Math.min(weight, amountToNext);221for (222line = 0, pixelOffset = actualPosition;223line < this.originalHeightMultipliedByChannels;224pixelOffset += nextLineOffsetOriginalWidth225) {226r = buffer[pixelOffset];227g = buffer[++pixelOffset];228b = buffer[++pixelOffset];229a = fourthChannel ? buffer[++pixelOffset] : 255;230// Ignore RGB values if pixel is completely transparent231output[line++] += (a ? r : 0) * multiplier;232output[line++] += (a ? g : 0) * multiplier;233output[line++] += (a ? b : 0) * multiplier;234if (fourthChannel) {235output[line++] += a * multiplier;236trustworthyColorsCount[line / channelsNum - 1] += a ? multiplier : 0;237}238}239240if (weight >= amountToNext) {241actualPosition += channelsNum;242currentPosition = actualPosition;243weight -= amountToNext;244} else {245currentPosition += weight;246break;247}248} while (249weight > 0 &&250actualPosition < this.originalWidthMultipliedByChannels251);252253for (254line = 0, pixelOffset = outputOffset;255line < this.originalHeightMultipliedByChannels;256pixelOffset += nextLineOffsetTargetWidth257) {258weight = fourthChannel ? trustworthyColorsCount[line / channelsNum] : 1;259multiplier = fourthChannel260? weight261? 1 / weight262: 0263: ratioWeightDivisor;264outputBuffer[pixelOffset] = output[line++] * multiplier;265outputBuffer[++pixelOffset] = output[line++] * multiplier;266outputBuffer[++pixelOffset] = output[line++] * multiplier;267if (fourthChannel)268outputBuffer[++pixelOffset] = output[line++] * ratioWeightDivisor;269}270271outputOffset += channelsNum;272} while (outputOffset < this.targetWidthMultipliedByChannels);273274return outputBuffer;275};276277Resize.prototype._resizeHeightRGBChannels = function(buffer, fourthChannel) {278const ratioWeight = this.ratioWeightHeightPass;279const ratioWeightDivisor = 1 / ratioWeight;280const output = this.outputHeightWorkBench;281const outputBuffer = this.heightBuffer;282const trustworthyColorsCount = this.outputHeightWorkBenchOpaquePixelsCount;283284let weight = 0;285let amountToNext = 0;286let actualPosition = 0;287let currentPosition = 0;288let pixelOffset = 0;289let outputOffset = 0;290let caret = 0;291let multiplier = 1;292let r = 0;293let g = 0;294let b = 0;295let a = 0;296297do {298for (299pixelOffset = 0;300pixelOffset < this.targetWidthMultipliedByChannels;301302) {303output[pixelOffset++] = 0;304output[pixelOffset++] = 0;305output[pixelOffset++] = 0;306307if (fourthChannel) {308output[pixelOffset++] = 0;309trustworthyColorsCount[pixelOffset / 4 - 1] = 0;310}311}312313weight = ratioWeight;314315do {316amountToNext = 1 + actualPosition - currentPosition;317multiplier = Math.min(weight, amountToNext);318caret = actualPosition;319320for (321pixelOffset = 0;322pixelOffset < this.targetWidthMultipliedByChannels;323324) {325r = buffer[caret++];326g = buffer[caret++];327b = buffer[caret++];328a = fourthChannel ? buffer[caret++] : 255;329// Ignore RGB values if pixel is completely transparent330output[pixelOffset++] += (a ? r : 0) * multiplier;331output[pixelOffset++] += (a ? g : 0) * multiplier;332output[pixelOffset++] += (a ? b : 0) * multiplier;333334if (fourthChannel) {335output[pixelOffset++] += a * multiplier;336trustworthyColorsCount[pixelOffset / 4 - 1] += a ? multiplier : 0;337}338}339340if (weight >= amountToNext) {341actualPosition = caret;342currentPosition = actualPosition;343weight -= amountToNext;344} else {345currentPosition += weight;346break;347}348} while (weight > 0 && actualPosition < this.widthPassResultSize);349350for (351pixelOffset = 0;352pixelOffset < this.targetWidthMultipliedByChannels;353354) {355weight = fourthChannel ? trustworthyColorsCount[pixelOffset / 4] : 1;356multiplier = fourthChannel357? weight358? 1 / weight359: 0360: ratioWeightDivisor;361outputBuffer[outputOffset++] = Math.round(362output[pixelOffset++] * multiplier363);364outputBuffer[outputOffset++] = Math.round(365output[pixelOffset++] * multiplier366);367outputBuffer[outputOffset++] = Math.round(368output[pixelOffset++] * multiplier369);370371if (fourthChannel) {372outputBuffer[outputOffset++] = Math.round(373output[pixelOffset++] * ratioWeightDivisor374);375}376}377} while (outputOffset < this.finalResultSize);378379return outputBuffer;380};381382Resize.prototype.resizeWidthInterpolatedRGB = function(buffer) {383return this._resizeWidthInterpolatedRGBChannels(buffer, false);384};385386Resize.prototype.resizeWidthInterpolatedRGBA = function(buffer) {387return this._resizeWidthInterpolatedRGBChannels(buffer, true);388};389390Resize.prototype.resizeWidthRGB = function(buffer) {391return this._resizeWidthRGBChannels(buffer, false);392};393394Resize.prototype.resizeWidthRGBA = function(buffer) {395return this._resizeWidthRGBChannels(buffer, true);396};397398Resize.prototype.resizeHeightInterpolated = function(buffer) {399const ratioWeight = this.ratioWeightHeightPass;400const outputBuffer = this.heightBuffer;401402let weight = 0;403let finalOffset = 0;404let pixelOffset = 0;405let pixelOffsetAccumulated = 0;406let pixelOffsetAccumulated2 = 0;407let firstWeight = 0;408let secondWeight = 0;409let interpolationHeightSourceReadStop;410411// Handle for only one interpolation input being valid for start calculation:412for (; weight < 1 / 3; weight += ratioWeight) {413for (414pixelOffset = 0;415pixelOffset < this.targetWidthMultipliedByChannels;416417) {418outputBuffer[finalOffset++] = Math.round(buffer[pixelOffset++]);419}420}421422// Adjust for overshoot of the last pass's counter:423weight -= 1 / 3;424425for (426interpolationHeightSourceReadStop = this.heightOriginal - 1;427weight < interpolationHeightSourceReadStop;428weight += ratioWeight429) {430// Calculate weightings:431secondWeight = weight % 1;432firstWeight = 1 - secondWeight;433// Interpolate:434pixelOffsetAccumulated =435Math.floor(weight) * this.targetWidthMultipliedByChannels;436pixelOffsetAccumulated2 =437pixelOffsetAccumulated + this.targetWidthMultipliedByChannels;438for (439pixelOffset = 0;440pixelOffset < this.targetWidthMultipliedByChannels;441++pixelOffset442) {443outputBuffer[finalOffset++] = Math.round(444buffer[pixelOffsetAccumulated++] * firstWeight +445buffer[pixelOffsetAccumulated2++] * secondWeight446);447}448}449450// Handle for only one interpolation input being valid for end calculation:451while (finalOffset < this.finalResultSize) {452for (453pixelOffset = 0,454pixelOffsetAccumulated =455interpolationHeightSourceReadStop *456this.targetWidthMultipliedByChannels;457pixelOffset < this.targetWidthMultipliedByChannels;458++pixelOffset459) {460outputBuffer[finalOffset++] = Math.round(461buffer[pixelOffsetAccumulated++]462);463}464}465466return outputBuffer;467};468469Resize.prototype.resizeHeightRGB = function(buffer) {470return this._resizeHeightRGBChannels(buffer, false);471};472473Resize.prototype.resizeHeightRGBA = function(buffer) {474return this._resizeHeightRGBChannels(buffer, true);475};476477Resize.prototype.resize = function(buffer) {478this.resizeCallback(this.resizeHeight(this.resizeWidth(buffer)));479};480481Resize.prototype.bypassResizer = function(buffer) {482// Just return the buffer passed:483return buffer;484};485486Resize.prototype.initializeFirstPassBuffers = function(BILINEARAlgo) {487// Initialize the internal width pass buffers:488this.widthBuffer = this.generateFloatBuffer(this.widthPassResultSize);489490if (!BILINEARAlgo) {491this.outputWidthWorkBench = this.generateFloatBuffer(492this.originalHeightMultipliedByChannels493);494495if (this.colorChannels > 3) {496this.outputWidthWorkBenchOpaquePixelsCount = this.generateFloat64Buffer(497this.heightOriginal498);499}500}501};502503Resize.prototype.initializeSecondPassBuffers = function(BILINEARAlgo) {504// Initialize the internal height pass buffers:505this.heightBuffer = this.generateUint8Buffer(this.finalResultSize);506507if (!BILINEARAlgo) {508this.outputHeightWorkBench = this.generateFloatBuffer(509this.targetWidthMultipliedByChannels510);511512if (this.colorChannels > 3) {513this.outputHeightWorkBenchOpaquePixelsCount = this.generateFloat64Buffer(514this.targetWidth515);516}517}518};519520Resize.prototype.generateFloatBuffer = function(bufferLength) {521// Generate a float32 typed array buffer:522try {523return new Float32Array(bufferLength);524} catch (error) {525return [];526}527};528529Resize.prototype.generateFloat64Buffer = function(bufferLength) {530// Generate a float64 typed array buffer:531try {532return new Float64Array(bufferLength);533} catch (error) {534return [];535}536};537538Resize.prototype.generateUint8Buffer = function(bufferLength) {539// Generate a uint8 typed array buffer:540try {541return new Uint8Array(bufferLength);542} catch (error) {543return [];544}545};546547module.exports = Resize;548549550