resize.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. "use strict";
  2. // JavaScript Image Resizer (c) 2012 - Grant Galitz
  3. // Released to public domain 29 July 2013: https://github.com/grantgalitz/JS-Image-Resizer/issues/4
  4. function Resize(widthOriginal, heightOriginal, targetWidth, targetHeight, blendAlpha, interpolationPass, resizeCallback) {
  5. this.widthOriginal = Math.abs(Math.floor(widthOriginal) || 0);
  6. this.heightOriginal = Math.abs(Math.floor(heightOriginal) || 0);
  7. this.targetWidth = Math.abs(Math.floor(targetWidth) || 0);
  8. this.targetHeight = Math.abs(Math.floor(targetHeight) || 0);
  9. this.colorChannels = blendAlpha ? 4 : 3;
  10. this.interpolationPass = Boolean(interpolationPass);
  11. this.resizeCallback = typeof resizeCallback === 'function' ? resizeCallback : function () {};
  12. this.targetWidthMultipliedByChannels = this.targetWidth * this.colorChannels;
  13. this.originalWidthMultipliedByChannels = this.widthOriginal * this.colorChannels;
  14. this.originalHeightMultipliedByChannels = this.heightOriginal * this.colorChannels;
  15. this.widthPassResultSize = this.targetWidthMultipliedByChannels * this.heightOriginal;
  16. this.finalResultSize = this.targetWidthMultipliedByChannels * this.targetHeight;
  17. this.initialize();
  18. }
  19. Resize.prototype.initialize = function () {
  20. // Perform some checks:
  21. if (this.widthOriginal > 0 && this.heightOriginal > 0 && this.targetWidth > 0 && this.targetHeight > 0) {
  22. this.configurePasses();
  23. } else {
  24. throw new Error('Invalid settings specified for the resizer.');
  25. }
  26. };
  27. Resize.prototype.configurePasses = function () {
  28. if (this.widthOriginal === this.targetWidth) {
  29. // Bypass the width resizer pass:
  30. this.resizeWidth = this.bypassResizer;
  31. } else {
  32. // Setup the width resizer pass:
  33. this.ratioWeightWidthPass = this.widthOriginal / this.targetWidth;
  34. if (this.ratioWeightWidthPass < 1 && this.interpolationPass) {
  35. this.initializeFirstPassBuffers(true);
  36. this.resizeWidth = this.colorChannels === 4 ? this.resizeWidthInterpolatedRGBA : this.resizeWidthInterpolatedRGB;
  37. } else {
  38. this.initializeFirstPassBuffers(false);
  39. this.resizeWidth = this.colorChannels === 4 ? this.resizeWidthRGBA : this.resizeWidthRGB;
  40. }
  41. }
  42. if (this.heightOriginal === this.targetHeight) {
  43. // Bypass the height resizer pass:
  44. this.resizeHeight = this.bypassResizer;
  45. } else {
  46. // Setup the height resizer pass:
  47. this.ratioWeightHeightPass = this.heightOriginal / this.targetHeight;
  48. if (this.ratioWeightHeightPass < 1 && this.interpolationPass) {
  49. this.initializeSecondPassBuffers(true);
  50. this.resizeHeight = this.resizeHeightInterpolated;
  51. } else {
  52. this.initializeSecondPassBuffers(false);
  53. this.resizeHeight = this.colorChannels === 4 ? this.resizeHeightRGBA : this.resizeHeightRGB;
  54. }
  55. }
  56. };
  57. Resize.prototype._resizeWidthInterpolatedRGBChannels = function (buffer, fourthChannel) {
  58. var channelsNum = fourthChannel ? 4 : 3;
  59. var ratioWeight = this.ratioWeightWidthPass;
  60. var outputBuffer = this.widthBuffer;
  61. var weight = 0;
  62. var finalOffset = 0;
  63. var pixelOffset = 0;
  64. var firstWeight = 0;
  65. var secondWeight = 0;
  66. var targetPosition; // Handle for only one interpolation input being valid for start calculation:
  67. for (targetPosition = 0; weight < 1 / 3; targetPosition += channelsNum, weight += ratioWeight) {
  68. for (finalOffset = targetPosition, pixelOffset = 0; finalOffset < this.widthPassResultSize; pixelOffset += this.originalWidthMultipliedByChannels, finalOffset += this.targetWidthMultipliedByChannels) {
  69. outputBuffer[finalOffset] = buffer[pixelOffset];
  70. outputBuffer[finalOffset + 1] = buffer[pixelOffset + 1];
  71. outputBuffer[finalOffset + 2] = buffer[pixelOffset + 2];
  72. if (fourthChannel) outputBuffer[finalOffset + 3] = buffer[pixelOffset + 3];
  73. }
  74. } // Adjust for overshoot of the last pass's counter:
  75. weight -= 1 / 3;
  76. var interpolationWidthSourceReadStop;
  77. for (interpolationWidthSourceReadStop = this.widthOriginal - 1; weight < interpolationWidthSourceReadStop; targetPosition += channelsNum, weight += ratioWeight) {
  78. // Calculate weightings:
  79. secondWeight = weight % 1;
  80. firstWeight = 1 - secondWeight; // Interpolate:
  81. for (finalOffset = targetPosition, pixelOffset = Math.floor(weight) * channelsNum; finalOffset < this.widthPassResultSize; pixelOffset += this.originalWidthMultipliedByChannels, finalOffset += this.targetWidthMultipliedByChannels) {
  82. outputBuffer[finalOffset + 0] = buffer[pixelOffset + 0] * firstWeight + buffer[pixelOffset + channelsNum + 0] * secondWeight;
  83. outputBuffer[finalOffset + 1] = buffer[pixelOffset + 1] * firstWeight + buffer[pixelOffset + channelsNum + 1] * secondWeight;
  84. outputBuffer[finalOffset + 2] = buffer[pixelOffset + 2] * firstWeight + buffer[pixelOffset + channelsNum + 2] * secondWeight;
  85. if (fourthChannel) outputBuffer[finalOffset + 3] = buffer[pixelOffset + 3] * firstWeight + buffer[pixelOffset + channelsNum + 3] * secondWeight;
  86. }
  87. } // Handle for only one interpolation input being valid for end calculation:
  88. for (interpolationWidthSourceReadStop = this.originalWidthMultipliedByChannels - channelsNum; targetPosition < this.targetWidthMultipliedByChannels; targetPosition += channelsNum) {
  89. for (finalOffset = targetPosition, pixelOffset = interpolationWidthSourceReadStop; finalOffset < this.widthPassResultSize; pixelOffset += this.originalWidthMultipliedByChannels, finalOffset += this.targetWidthMultipliedByChannels) {
  90. outputBuffer[finalOffset] = buffer[pixelOffset];
  91. outputBuffer[finalOffset + 1] = buffer[pixelOffset + 1];
  92. outputBuffer[finalOffset + 2] = buffer[pixelOffset + 2];
  93. if (fourthChannel) outputBuffer[finalOffset + 3] = buffer[pixelOffset + 3];
  94. }
  95. }
  96. return outputBuffer;
  97. };
  98. Resize.prototype._resizeWidthRGBChannels = function (buffer, fourthChannel) {
  99. var channelsNum = fourthChannel ? 4 : 3;
  100. var ratioWeight = this.ratioWeightWidthPass;
  101. var ratioWeightDivisor = 1 / ratioWeight;
  102. var nextLineOffsetOriginalWidth = this.originalWidthMultipliedByChannels - channelsNum + 1;
  103. var nextLineOffsetTargetWidth = this.targetWidthMultipliedByChannels - channelsNum + 1;
  104. var output = this.outputWidthWorkBench;
  105. var outputBuffer = this.widthBuffer;
  106. var trustworthyColorsCount = this.outputWidthWorkBenchOpaquePixelsCount;
  107. var weight = 0;
  108. var amountToNext = 0;
  109. var actualPosition = 0;
  110. var currentPosition = 0;
  111. var line = 0;
  112. var pixelOffset = 0;
  113. var outputOffset = 0;
  114. var multiplier = 1;
  115. var r = 0;
  116. var g = 0;
  117. var b = 0;
  118. var a = 0;
  119. do {
  120. for (line = 0; line < this.originalHeightMultipliedByChannels;) {
  121. output[line++] = 0;
  122. output[line++] = 0;
  123. output[line++] = 0;
  124. if (fourthChannel) {
  125. output[line++] = 0;
  126. trustworthyColorsCount[line / channelsNum - 1] = 0;
  127. }
  128. }
  129. weight = ratioWeight;
  130. do {
  131. amountToNext = 1 + actualPosition - currentPosition;
  132. multiplier = Math.min(weight, amountToNext);
  133. for (line = 0, pixelOffset = actualPosition; line < this.originalHeightMultipliedByChannels; pixelOffset += nextLineOffsetOriginalWidth) {
  134. r = buffer[pixelOffset];
  135. g = buffer[++pixelOffset];
  136. b = buffer[++pixelOffset];
  137. a = fourthChannel ? buffer[++pixelOffset] : 255; // Ignore RGB values if pixel is completely transparent
  138. output[line++] += (a ? r : 0) * multiplier;
  139. output[line++] += (a ? g : 0) * multiplier;
  140. output[line++] += (a ? b : 0) * multiplier;
  141. if (fourthChannel) {
  142. output[line++] += a * multiplier;
  143. trustworthyColorsCount[line / channelsNum - 1] += a ? multiplier : 0;
  144. }
  145. }
  146. if (weight >= amountToNext) {
  147. actualPosition += channelsNum;
  148. currentPosition = actualPosition;
  149. weight -= amountToNext;
  150. } else {
  151. currentPosition += weight;
  152. break;
  153. }
  154. } while (weight > 0 && actualPosition < this.originalWidthMultipliedByChannels);
  155. for (line = 0, pixelOffset = outputOffset; line < this.originalHeightMultipliedByChannels; pixelOffset += nextLineOffsetTargetWidth) {
  156. weight = fourthChannel ? trustworthyColorsCount[line / channelsNum] : 1;
  157. multiplier = fourthChannel ? weight ? 1 / weight : 0 : ratioWeightDivisor;
  158. outputBuffer[pixelOffset] = output[line++] * multiplier;
  159. outputBuffer[++pixelOffset] = output[line++] * multiplier;
  160. outputBuffer[++pixelOffset] = output[line++] * multiplier;
  161. if (fourthChannel) outputBuffer[++pixelOffset] = output[line++] * ratioWeightDivisor;
  162. }
  163. outputOffset += channelsNum;
  164. } while (outputOffset < this.targetWidthMultipliedByChannels);
  165. return outputBuffer;
  166. };
  167. Resize.prototype._resizeHeightRGBChannels = function (buffer, fourthChannel) {
  168. var ratioWeight = this.ratioWeightHeightPass;
  169. var ratioWeightDivisor = 1 / ratioWeight;
  170. var output = this.outputHeightWorkBench;
  171. var outputBuffer = this.heightBuffer;
  172. var trustworthyColorsCount = this.outputHeightWorkBenchOpaquePixelsCount;
  173. var weight = 0;
  174. var amountToNext = 0;
  175. var actualPosition = 0;
  176. var currentPosition = 0;
  177. var pixelOffset = 0;
  178. var outputOffset = 0;
  179. var caret = 0;
  180. var multiplier = 1;
  181. var r = 0;
  182. var g = 0;
  183. var b = 0;
  184. var a = 0;
  185. do {
  186. for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) {
  187. output[pixelOffset++] = 0;
  188. output[pixelOffset++] = 0;
  189. output[pixelOffset++] = 0;
  190. if (fourthChannel) {
  191. output[pixelOffset++] = 0;
  192. trustworthyColorsCount[pixelOffset / 4 - 1] = 0;
  193. }
  194. }
  195. weight = ratioWeight;
  196. do {
  197. amountToNext = 1 + actualPosition - currentPosition;
  198. multiplier = Math.min(weight, amountToNext);
  199. caret = actualPosition;
  200. for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) {
  201. r = buffer[caret++];
  202. g = buffer[caret++];
  203. b = buffer[caret++];
  204. a = fourthChannel ? buffer[caret++] : 255; // Ignore RGB values if pixel is completely transparent
  205. output[pixelOffset++] += (a ? r : 0) * multiplier;
  206. output[pixelOffset++] += (a ? g : 0) * multiplier;
  207. output[pixelOffset++] += (a ? b : 0) * multiplier;
  208. if (fourthChannel) {
  209. output[pixelOffset++] += a * multiplier;
  210. trustworthyColorsCount[pixelOffset / 4 - 1] += a ? multiplier : 0;
  211. }
  212. }
  213. if (weight >= amountToNext) {
  214. actualPosition = caret;
  215. currentPosition = actualPosition;
  216. weight -= amountToNext;
  217. } else {
  218. currentPosition += weight;
  219. break;
  220. }
  221. } while (weight > 0 && actualPosition < this.widthPassResultSize);
  222. for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) {
  223. weight = fourthChannel ? trustworthyColorsCount[pixelOffset / 4] : 1;
  224. multiplier = fourthChannel ? weight ? 1 / weight : 0 : ratioWeightDivisor;
  225. outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] * multiplier);
  226. outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] * multiplier);
  227. outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] * multiplier);
  228. if (fourthChannel) {
  229. outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] * ratioWeightDivisor);
  230. }
  231. }
  232. } while (outputOffset < this.finalResultSize);
  233. return outputBuffer;
  234. };
  235. Resize.prototype.resizeWidthInterpolatedRGB = function (buffer) {
  236. return this._resizeWidthInterpolatedRGBChannels(buffer, false);
  237. };
  238. Resize.prototype.resizeWidthInterpolatedRGBA = function (buffer) {
  239. return this._resizeWidthInterpolatedRGBChannels(buffer, true);
  240. };
  241. Resize.prototype.resizeWidthRGB = function (buffer) {
  242. return this._resizeWidthRGBChannels(buffer, false);
  243. };
  244. Resize.prototype.resizeWidthRGBA = function (buffer) {
  245. return this._resizeWidthRGBChannels(buffer, true);
  246. };
  247. Resize.prototype.resizeHeightInterpolated = function (buffer) {
  248. var ratioWeight = this.ratioWeightHeightPass;
  249. var outputBuffer = this.heightBuffer;
  250. var weight = 0;
  251. var finalOffset = 0;
  252. var pixelOffset = 0;
  253. var pixelOffsetAccumulated = 0;
  254. var pixelOffsetAccumulated2 = 0;
  255. var firstWeight = 0;
  256. var secondWeight = 0;
  257. var interpolationHeightSourceReadStop; // Handle for only one interpolation input being valid for start calculation:
  258. for (; weight < 1 / 3; weight += ratioWeight) {
  259. for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) {
  260. outputBuffer[finalOffset++] = Math.round(buffer[pixelOffset++]);
  261. }
  262. } // Adjust for overshoot of the last pass's counter:
  263. weight -= 1 / 3;
  264. for (interpolationHeightSourceReadStop = this.heightOriginal - 1; weight < interpolationHeightSourceReadStop; weight += ratioWeight) {
  265. // Calculate weightings:
  266. secondWeight = weight % 1;
  267. firstWeight = 1 - secondWeight; // Interpolate:
  268. pixelOffsetAccumulated = Math.floor(weight) * this.targetWidthMultipliedByChannels;
  269. pixelOffsetAccumulated2 = pixelOffsetAccumulated + this.targetWidthMultipliedByChannels;
  270. for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels; ++pixelOffset) {
  271. outputBuffer[finalOffset++] = Math.round(buffer[pixelOffsetAccumulated++] * firstWeight + buffer[pixelOffsetAccumulated2++] * secondWeight);
  272. }
  273. } // Handle for only one interpolation input being valid for end calculation:
  274. while (finalOffset < this.finalResultSize) {
  275. for (pixelOffset = 0, pixelOffsetAccumulated = interpolationHeightSourceReadStop * this.targetWidthMultipliedByChannels; pixelOffset < this.targetWidthMultipliedByChannels; ++pixelOffset) {
  276. outputBuffer[finalOffset++] = Math.round(buffer[pixelOffsetAccumulated++]);
  277. }
  278. }
  279. return outputBuffer;
  280. };
  281. Resize.prototype.resizeHeightRGB = function (buffer) {
  282. return this._resizeHeightRGBChannels(buffer, false);
  283. };
  284. Resize.prototype.resizeHeightRGBA = function (buffer) {
  285. return this._resizeHeightRGBChannels(buffer, true);
  286. };
  287. Resize.prototype.resize = function (buffer) {
  288. this.resizeCallback(this.resizeHeight(this.resizeWidth(buffer)));
  289. };
  290. Resize.prototype.bypassResizer = function (buffer) {
  291. // Just return the buffer passed:
  292. return buffer;
  293. };
  294. Resize.prototype.initializeFirstPassBuffers = function (BILINEARAlgo) {
  295. // Initialize the internal width pass buffers:
  296. this.widthBuffer = this.generateFloatBuffer(this.widthPassResultSize);
  297. if (!BILINEARAlgo) {
  298. this.outputWidthWorkBench = this.generateFloatBuffer(this.originalHeightMultipliedByChannels);
  299. if (this.colorChannels > 3) {
  300. this.outputWidthWorkBenchOpaquePixelsCount = this.generateFloat64Buffer(this.heightOriginal);
  301. }
  302. }
  303. };
  304. Resize.prototype.initializeSecondPassBuffers = function (BILINEARAlgo) {
  305. // Initialize the internal height pass buffers:
  306. this.heightBuffer = this.generateUint8Buffer(this.finalResultSize);
  307. if (!BILINEARAlgo) {
  308. this.outputHeightWorkBench = this.generateFloatBuffer(this.targetWidthMultipliedByChannels);
  309. if (this.colorChannels > 3) {
  310. this.outputHeightWorkBenchOpaquePixelsCount = this.generateFloat64Buffer(this.targetWidth);
  311. }
  312. }
  313. };
  314. Resize.prototype.generateFloatBuffer = function (bufferLength) {
  315. // Generate a float32 typed array buffer:
  316. try {
  317. return new Float32Array(bufferLength);
  318. } catch (error) {
  319. return [];
  320. }
  321. };
  322. Resize.prototype.generateFloat64Buffer = function (bufferLength) {
  323. // Generate a float64 typed array buffer:
  324. try {
  325. return new Float64Array(bufferLength);
  326. } catch (error) {
  327. return [];
  328. }
  329. };
  330. Resize.prototype.generateUint8Buffer = function (bufferLength) {
  331. // Generate a uint8 typed array buffer:
  332. try {
  333. return new Uint8Array(bufferLength);
  334. } catch (error) {
  335. return [];
  336. }
  337. };
  338. module.exports = Resize;
  339. //# sourceMappingURL=resize.js.map