modules/png_support.js

  1. /**
  2. * @license
  3. *
  4. * Copyright (c) 2014 James Robb, https://github.com/jamesbrobb
  5. *
  6. * Permission is hereby granted, free of charge, to any person obtaining
  7. * a copy of this software and associated documentation files (the
  8. * "Software"), to deal in the Software without restriction, including
  9. * without limitation the rights to use, copy, modify, merge, publish,
  10. * distribute, sublicense, and/or sell copies of the Software, and to
  11. * permit persons to whom the Software is furnished to do so, subject to
  12. * the following conditions:
  13. *
  14. * The above copyright notice and this permission notice shall be
  15. * included in all copies or substantial portions of the Software.
  16. *
  17. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  18. * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  19. * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  20. * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  21. * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  22. * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  23. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  24. * ====================================================================
  25. */
  26. import { jsPDF } from "../jspdf.js";
  27. import { zlibSync } from "../libs/fflate.js";
  28. import { PNG } from "../libs/png.js";
  29. /**
  30. * jsPDF PNG PlugIn
  31. * @name png_support
  32. * @module
  33. */
  34. (function(jsPDFAPI) {
  35. "use strict";
  36. /*
  37. * @see http://www.w3.org/TR/PNG-Chunks.html
  38. *
  39. Color Allowed Interpretation
  40. Type Bit Depths
  41. 0 1,2,4,8,16 Each pixel is a grayscale sample.
  42. 2 8,16 Each pixel is an R,G,B triple.
  43. 3 1,2,4,8 Each pixel is a palette index;
  44. a PLTE chunk must appear.
  45. 4 8,16 Each pixel is a grayscale sample,
  46. followed by an alpha sample.
  47. 6 8,16 Each pixel is an R,G,B triple,
  48. followed by an alpha sample.
  49. */
  50. /*
  51. * PNG filter method types
  52. *
  53. * @see http://www.w3.org/TR/PNG-Filters.html
  54. * @see http://www.libpng.org/pub/png/book/chapter09.html
  55. *
  56. * This is what the value 'Predictor' in decode params relates to
  57. *
  58. * 15 is "optimal prediction", which means the prediction algorithm can change from line to line.
  59. * In that case, you actually have to read the first byte off each line for the prediction algorthim (which should be 0-4, corresponding to PDF 10-14) and select the appropriate unprediction algorithm based on that byte.
  60. *
  61. 0 None
  62. 1 Sub
  63. 2 Up
  64. 3 Average
  65. 4 Paeth
  66. */
  67. var canCompress = function(value) {
  68. return value !== jsPDFAPI.image_compression.NONE && hasCompressionJS();
  69. };
  70. var hasCompressionJS = function() {
  71. return typeof zlibSync === "function";
  72. };
  73. var compressBytes = function(bytes, lineLength, colorsPerPixel, compression) {
  74. var level = 4;
  75. var filter_method = filterUp;
  76. switch (compression) {
  77. case jsPDFAPI.image_compression.FAST:
  78. level = 1;
  79. filter_method = filterSub;
  80. break;
  81. case jsPDFAPI.image_compression.MEDIUM:
  82. level = 6;
  83. filter_method = filterAverage;
  84. break;
  85. case jsPDFAPI.image_compression.SLOW:
  86. level = 9;
  87. filter_method = filterPaeth;
  88. break;
  89. }
  90. bytes = applyPngFilterMethod(
  91. bytes,
  92. lineLength,
  93. colorsPerPixel,
  94. filter_method
  95. );
  96. var dat = zlibSync(bytes, { level: level });
  97. return jsPDFAPI.__addimage__.arrayBufferToBinaryString(dat);
  98. };
  99. var applyPngFilterMethod = function(
  100. bytes,
  101. lineLength,
  102. colorsPerPixel,
  103. filter_method
  104. ) {
  105. var lines = bytes.length / lineLength,
  106. result = new Uint8Array(bytes.length + lines),
  107. filter_methods = getFilterMethods(),
  108. line,
  109. prevLine,
  110. offset;
  111. for (var i = 0; i < lines; i += 1) {
  112. offset = i * lineLength;
  113. line = bytes.subarray(offset, offset + lineLength);
  114. if (filter_method) {
  115. result.set(filter_method(line, colorsPerPixel, prevLine), offset + i);
  116. } else {
  117. var len = filter_methods.length,
  118. results = [];
  119. for (var j; j < len; j += 1) {
  120. results[j] = filter_methods[j](line, colorsPerPixel, prevLine);
  121. }
  122. var ind = getIndexOfSmallestSum(results.concat());
  123. result.set(results[ind], offset + i);
  124. }
  125. prevLine = line;
  126. }
  127. return result;
  128. };
  129. var filterNone = function(line) {
  130. /*var result = new Uint8Array(line.length + 1);
  131. result[0] = 0;
  132. result.set(line, 1);*/
  133. var result = Array.apply([], line);
  134. result.unshift(0);
  135. return result;
  136. };
  137. var filterSub = function(line, colorsPerPixel) {
  138. var result = [],
  139. len = line.length,
  140. left;
  141. result[0] = 1;
  142. for (var i = 0; i < len; i += 1) {
  143. left = line[i - colorsPerPixel] || 0;
  144. result[i + 1] = (line[i] - left + 0x0100) & 0xff;
  145. }
  146. return result;
  147. };
  148. var filterUp = function(line, colorsPerPixel, prevLine) {
  149. var result = [],
  150. len = line.length,
  151. up;
  152. result[0] = 2;
  153. for (var i = 0; i < len; i += 1) {
  154. up = (prevLine && prevLine[i]) || 0;
  155. result[i + 1] = (line[i] - up + 0x0100) & 0xff;
  156. }
  157. return result;
  158. };
  159. var filterAverage = function(line, colorsPerPixel, prevLine) {
  160. var result = [],
  161. len = line.length,
  162. left,
  163. up;
  164. result[0] = 3;
  165. for (var i = 0; i < len; i += 1) {
  166. left = line[i - colorsPerPixel] || 0;
  167. up = (prevLine && prevLine[i]) || 0;
  168. result[i + 1] = (line[i] + 0x0100 - ((left + up) >>> 1)) & 0xff;
  169. }
  170. return result;
  171. };
  172. var filterPaeth = function(line, colorsPerPixel, prevLine) {
  173. var result = [],
  174. len = line.length,
  175. left,
  176. up,
  177. upLeft,
  178. paeth;
  179. result[0] = 4;
  180. for (var i = 0; i < len; i += 1) {
  181. left = line[i - colorsPerPixel] || 0;
  182. up = (prevLine && prevLine[i]) || 0;
  183. upLeft = (prevLine && prevLine[i - colorsPerPixel]) || 0;
  184. paeth = paethPredictor(left, up, upLeft);
  185. result[i + 1] = (line[i] - paeth + 0x0100) & 0xff;
  186. }
  187. return result;
  188. };
  189. var paethPredictor = function(left, up, upLeft) {
  190. if (left === up && up === upLeft) {
  191. return left;
  192. }
  193. var pLeft = Math.abs(up - upLeft),
  194. pUp = Math.abs(left - upLeft),
  195. pUpLeft = Math.abs(left + up - upLeft - upLeft);
  196. return pLeft <= pUp && pLeft <= pUpLeft
  197. ? left
  198. : pUp <= pUpLeft
  199. ? up
  200. : upLeft;
  201. };
  202. var getFilterMethods = function() {
  203. return [filterNone, filterSub, filterUp, filterAverage, filterPaeth];
  204. };
  205. var getIndexOfSmallestSum = function(arrays) {
  206. var sum = arrays.map(function(value) {
  207. return value.reduce(function(pv, cv) {
  208. return pv + Math.abs(cv);
  209. }, 0);
  210. });
  211. return sum.indexOf(Math.min.apply(null, sum));
  212. };
  213. var getPredictorFromCompression = function(compression) {
  214. var predictor;
  215. switch (compression) {
  216. case jsPDFAPI.image_compression.FAST:
  217. predictor = 11;
  218. break;
  219. case jsPDFAPI.image_compression.MEDIUM:
  220. predictor = 13;
  221. break;
  222. case jsPDFAPI.image_compression.SLOW:
  223. predictor = 14;
  224. break;
  225. default:
  226. predictor = 12;
  227. break;
  228. }
  229. return predictor;
  230. };
  231. /**
  232. * @name processPNG
  233. * @function
  234. * @ignore
  235. */
  236. jsPDFAPI.processPNG = function(imageData, index, alias, compression) {
  237. "use strict";
  238. var colorSpace,
  239. filter = this.decode.FLATE_DECODE,
  240. bitsPerComponent,
  241. image,
  242. decodeParameters = "",
  243. trns,
  244. colors,
  245. pal,
  246. smask,
  247. pixels,
  248. len,
  249. alphaData,
  250. imgData,
  251. hasColors,
  252. pixel,
  253. i,
  254. n;
  255. if (this.__addimage__.isArrayBuffer(imageData))
  256. imageData = new Uint8Array(imageData);
  257. if (this.__addimage__.isArrayBufferView(imageData)) {
  258. image = new PNG(imageData);
  259. imageData = image.imgData;
  260. bitsPerComponent = image.bits;
  261. colorSpace = image.colorSpace;
  262. colors = image.colors;
  263. /*
  264. * colorType 6 - Each pixel is an R,G,B triple, followed by an alpha sample.
  265. *
  266. * colorType 4 - Each pixel is a grayscale sample, followed by an alpha sample.
  267. *
  268. * Extract alpha to create two separate images, using the alpha as a sMask
  269. */
  270. if ([4, 6].indexOf(image.colorType) !== -1) {
  271. /*
  272. * processes 8 bit RGBA and grayscale + alpha images
  273. */
  274. if (image.bits === 8) {
  275. pixels =
  276. image.pixelBitlength == 32
  277. ? new Uint32Array(image.decodePixels().buffer)
  278. : image.pixelBitlength == 16
  279. ? new Uint16Array(image.decodePixels().buffer)
  280. : new Uint8Array(image.decodePixels().buffer);
  281. len = pixels.length;
  282. imgData = new Uint8Array(len * image.colors);
  283. alphaData = new Uint8Array(len);
  284. var pDiff = image.pixelBitlength - image.bits;
  285. i = 0;
  286. n = 0;
  287. var pbl;
  288. for (; i < len; i++) {
  289. pixel = pixels[i];
  290. pbl = 0;
  291. while (pbl < pDiff) {
  292. imgData[n++] = (pixel >>> pbl) & 0xff;
  293. pbl = pbl + image.bits;
  294. }
  295. alphaData[i] = (pixel >>> pbl) & 0xff;
  296. }
  297. }
  298. /*
  299. * processes 16 bit RGBA and grayscale + alpha images
  300. */
  301. if (image.bits === 16) {
  302. pixels = new Uint32Array(image.decodePixels().buffer);
  303. len = pixels.length;
  304. imgData = new Uint8Array(
  305. len * (32 / image.pixelBitlength) * image.colors
  306. );
  307. alphaData = new Uint8Array(len * (32 / image.pixelBitlength));
  308. hasColors = image.colors > 1;
  309. i = 0;
  310. n = 0;
  311. var a = 0;
  312. while (i < len) {
  313. pixel = pixels[i++];
  314. imgData[n++] = (pixel >>> 0) & 0xff;
  315. if (hasColors) {
  316. imgData[n++] = (pixel >>> 16) & 0xff;
  317. pixel = pixels[i++];
  318. imgData[n++] = (pixel >>> 0) & 0xff;
  319. }
  320. alphaData[a++] = (pixel >>> 16) & 0xff;
  321. }
  322. bitsPerComponent = 8;
  323. }
  324. if (canCompress(compression)) {
  325. imageData = compressBytes(
  326. imgData,
  327. image.width * image.colors,
  328. image.colors,
  329. compression
  330. );
  331. smask = compressBytes(alphaData, image.width, 1, compression);
  332. } else {
  333. imageData = imgData;
  334. smask = alphaData;
  335. filter = undefined;
  336. }
  337. }
  338. /*
  339. * Indexed png. Each pixel is a palette index.
  340. */
  341. if (image.colorType === 3) {
  342. colorSpace = this.color_spaces.INDEXED;
  343. pal = image.palette;
  344. if (image.transparency.indexed) {
  345. var trans = image.transparency.indexed;
  346. var total = 0;
  347. i = 0;
  348. len = trans.length;
  349. for (; i < len; ++i) {
  350. total += trans[i];
  351. }
  352. total = total / 255;
  353. /*
  354. * a single color is specified as 100% transparent (0),
  355. * so we set trns to use a /Mask with that index
  356. */
  357. if (total === len - 1 && trans.indexOf(0) !== -1) {
  358. trns = [trans.indexOf(0)];
  359. /*
  360. * there's more than one colour within the palette that specifies
  361. * a transparency value less than 255, so we unroll the pixels to create an image sMask
  362. */
  363. } else if (total !== len) {
  364. pixels = image.decodePixels();
  365. alphaData = new Uint8Array(pixels.length);
  366. i = 0;
  367. len = pixels.length;
  368. for (; i < len; i++) {
  369. alphaData[i] = trans[pixels[i]];
  370. }
  371. smask = compressBytes(alphaData, image.width, 1);
  372. }
  373. }
  374. }
  375. var predictor = getPredictorFromCompression(compression);
  376. if (filter === this.decode.FLATE_DECODE) {
  377. decodeParameters = "/Predictor " + predictor + " ";
  378. }
  379. decodeParameters +=
  380. "/Colors " +
  381. colors +
  382. " /BitsPerComponent " +
  383. bitsPerComponent +
  384. " /Columns " +
  385. image.width;
  386. if (
  387. this.__addimage__.isArrayBuffer(imageData) ||
  388. this.__addimage__.isArrayBufferView(imageData)
  389. ) {
  390. imageData = this.__addimage__.arrayBufferToBinaryString(imageData);
  391. }
  392. if (
  393. (smask && this.__addimage__.isArrayBuffer(smask)) ||
  394. this.__addimage__.isArrayBufferView(smask)
  395. ) {
  396. smask = this.__addimage__.arrayBufferToBinaryString(smask);
  397. }
  398. return {
  399. alias: alias,
  400. data: imageData,
  401. index: index,
  402. filter: filter,
  403. decodeParameters: decodeParameters,
  404. transparency: trns,
  405. palette: pal,
  406. sMask: smask,
  407. predictor: predictor,
  408. width: image.width,
  409. height: image.height,
  410. bitsPerComponent: bitsPerComponent,
  411. colorSpace: colorSpace
  412. };
  413. }
  414. };
  415. })(jsPDF.API);