Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 

403 строки
12 KiB

  1. /*
  2. * MIT LICENSE
  3. * Copyright (c) 2011 Devon Govett
  4. *
  5. * Permission is hereby granted, free of charge, to any person obtaining a copy of this
  6. * software and associated documentation files (the "Software"), to deal in the Software
  7. * without restriction, including without limitation the rights to use, copy, modify, merge,
  8. * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
  9. * to whom the Software is furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in all copies or
  12. * substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
  15. * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  16. * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
  17. * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  19. */
  20. const fs = require('fs');
  21. const zlib = require('zlib');
  22. module.exports = class PNG {
  23. static decode(path, fn) {
  24. return fs.readFile(path, function(err, file) {
  25. const png = new PNG(file);
  26. return png.decode(pixels => fn(pixels));
  27. });
  28. }
  29. static load(path) {
  30. const file = fs.readFileSync(path);
  31. return new PNG(file);
  32. }
  33. constructor(data) {
  34. let i;
  35. this.data = data;
  36. this.pos = 8; // Skip the default header
  37. this.palette = [];
  38. this.imgData = [];
  39. this.transparency = {};
  40. this.text = {};
  41. while (true) {
  42. const chunkSize = this.readUInt32();
  43. let section = '';
  44. for (i = 0; i < 4; i++) {
  45. section += String.fromCharCode(this.data[this.pos++]);
  46. }
  47. switch (section) {
  48. case 'IHDR':
  49. // we can grab interesting values from here (like width, height, etc)
  50. this.width = this.readUInt32();
  51. this.height = this.readUInt32();
  52. this.bits = this.data[this.pos++];
  53. this.colorType = this.data[this.pos++];
  54. this.compressionMethod = this.data[this.pos++];
  55. this.filterMethod = this.data[this.pos++];
  56. this.interlaceMethod = this.data[this.pos++];
  57. break;
  58. case 'PLTE':
  59. this.palette = this.read(chunkSize);
  60. break;
  61. case 'IDAT':
  62. for (i = 0; i < chunkSize; i++) {
  63. this.imgData.push(this.data[this.pos++]);
  64. }
  65. break;
  66. case 'tRNS':
  67. // This chunk can only occur once and it must occur after the
  68. // PLTE chunk and before the IDAT chunk.
  69. this.transparency = {};
  70. switch (this.colorType) {
  71. case 3:
  72. // Indexed color, RGB. Each byte in this chunk is an alpha for
  73. // the palette index in the PLTE ("palette") chunk up until the
  74. // last non-opaque entry. Set up an array, stretching over all
  75. // palette entries which will be 0 (opaque) or 1 (transparent).
  76. this.transparency.indexed = this.read(chunkSize);
  77. var short = 255 - this.transparency.indexed.length;
  78. if (short > 0) {
  79. for (i = 0; i < short; i++) {
  80. this.transparency.indexed.push(255);
  81. }
  82. }
  83. break;
  84. case 0:
  85. // Greyscale. Corresponding to entries in the PLTE chunk.
  86. // Grey is two bytes, range 0 .. (2 ^ bit-depth) - 1
  87. this.transparency.grayscale = this.read(chunkSize)[0];
  88. break;
  89. case 2:
  90. // True color with proper alpha channel.
  91. this.transparency.rgb = this.read(chunkSize);
  92. break;
  93. }
  94. break;
  95. case 'tEXt':
  96. var text = this.read(chunkSize);
  97. var index = text.indexOf(0);
  98. var key = String.fromCharCode.apply(String, text.slice(0, index));
  99. this.text[key] = String.fromCharCode.apply(
  100. String,
  101. text.slice(index + 1)
  102. );
  103. break;
  104. case 'IEND':
  105. // we've got everything we need!
  106. switch (this.colorType) {
  107. case 0:
  108. case 3:
  109. case 4:
  110. this.colors = 1;
  111. break;
  112. case 2:
  113. case 6:
  114. this.colors = 3;
  115. break;
  116. }
  117. this.hasAlphaChannel = [4, 6].includes(this.colorType);
  118. var colors = this.colors + (this.hasAlphaChannel ? 1 : 0);
  119. this.pixelBitlength = this.bits * colors;
  120. switch (this.colors) {
  121. case 1:
  122. this.colorSpace = 'DeviceGray';
  123. break;
  124. case 3:
  125. this.colorSpace = 'DeviceRGB';
  126. break;
  127. }
  128. this.imgData = new Buffer(this.imgData);
  129. return;
  130. break;
  131. default:
  132. // unknown (or unimportant) section, skip it
  133. this.pos += chunkSize;
  134. }
  135. this.pos += 4; // Skip the CRC
  136. if (this.pos > this.data.length) {
  137. throw new Error('Incomplete or corrupt PNG file');
  138. }
  139. }
  140. }
  141. read(bytes) {
  142. const result = new Array(bytes);
  143. for (let i = 0; i < bytes; i++) {
  144. result[i] = this.data[this.pos++];
  145. }
  146. return result;
  147. }
  148. readUInt32() {
  149. const b1 = this.data[this.pos++] << 24;
  150. const b2 = this.data[this.pos++] << 16;
  151. const b3 = this.data[this.pos++] << 8;
  152. const b4 = this.data[this.pos++];
  153. return b1 | b2 | b3 | b4;
  154. }
  155. readUInt16() {
  156. const b1 = this.data[this.pos++] << 8;
  157. const b2 = this.data[this.pos++];
  158. return b1 | b2;
  159. }
  160. decodePixels(fn) {
  161. return zlib.inflate(this.imgData, (err, data) => {
  162. if (err) {
  163. throw err;
  164. }
  165. const { width, height } = this;
  166. const pixelBytes = this.pixelBitlength / 8;
  167. const pixels = new Buffer(width * height * pixelBytes);
  168. const { length } = data;
  169. let pos = 0;
  170. function pass(x0, y0, dx, dy, singlePass = false) {
  171. const w = Math.ceil((width - x0) / dx);
  172. const h = Math.ceil((height - y0) / dy);
  173. const scanlineLength = pixelBytes * w;
  174. const buffer = singlePass ? pixels : new Buffer(scanlineLength * h);
  175. let row = 0;
  176. let c = 0;
  177. while (row < h && pos < length) {
  178. var byte, col, i, left, upper;
  179. switch (data[pos++]) {
  180. case 0: // None
  181. for (i = 0; i < scanlineLength; i++) {
  182. buffer[c++] = data[pos++];
  183. }
  184. break;
  185. case 1: // Sub
  186. for (i = 0; i < scanlineLength; i++) {
  187. byte = data[pos++];
  188. left = i < pixelBytes ? 0 : buffer[c - pixelBytes];
  189. buffer[c++] = (byte + left) % 256;
  190. }
  191. break;
  192. case 2: // Up
  193. for (i = 0; i < scanlineLength; i++) {
  194. byte = data[pos++];
  195. col = (i - (i % pixelBytes)) / pixelBytes;
  196. upper =
  197. row &&
  198. buffer[
  199. (row - 1) * scanlineLength +
  200. col * pixelBytes +
  201. (i % pixelBytes)
  202. ];
  203. buffer[c++] = (upper + byte) % 256;
  204. }
  205. break;
  206. case 3: // Average
  207. for (i = 0; i < scanlineLength; i++) {
  208. byte = data[pos++];
  209. col = (i - (i % pixelBytes)) / pixelBytes;
  210. left = i < pixelBytes ? 0 : buffer[c - pixelBytes];
  211. upper =
  212. row &&
  213. buffer[
  214. (row - 1) * scanlineLength +
  215. col * pixelBytes +
  216. (i % pixelBytes)
  217. ];
  218. buffer[c++] = (byte + Math.floor((left + upper) / 2)) % 256;
  219. }
  220. break;
  221. case 4: // Paeth
  222. for (i = 0; i < scanlineLength; i++) {
  223. var paeth, upperLeft;
  224. byte = data[pos++];
  225. col = (i - (i % pixelBytes)) / pixelBytes;
  226. left = i < pixelBytes ? 0 : buffer[c - pixelBytes];
  227. if (row === 0) {
  228. upper = upperLeft = 0;
  229. } else {
  230. upper =
  231. buffer[
  232. (row - 1) * scanlineLength +
  233. col * pixelBytes +
  234. (i % pixelBytes)
  235. ];
  236. upperLeft =
  237. col &&
  238. buffer[
  239. (row - 1) * scanlineLength +
  240. (col - 1) * pixelBytes +
  241. (i % pixelBytes)
  242. ];
  243. }
  244. const p = left + upper - upperLeft;
  245. const pa = Math.abs(p - left);
  246. const pb = Math.abs(p - upper);
  247. const pc = Math.abs(p - upperLeft);
  248. if (pa <= pb && pa <= pc) {
  249. paeth = left;
  250. } else if (pb <= pc) {
  251. paeth = upper;
  252. } else {
  253. paeth = upperLeft;
  254. }
  255. buffer[c++] = (byte + paeth) % 256;
  256. }
  257. break;
  258. default:
  259. throw new Error(`Invalid filter algorithm: ${data[pos - 1]}`);
  260. }
  261. if (!singlePass) {
  262. let pixelsPos = ((y0 + row * dy) * width + x0) * pixelBytes;
  263. let bufferPos = row * scanlineLength;
  264. for (i = 0; i < w; i++) {
  265. for (let j = 0; j < pixelBytes; j++)
  266. pixels[pixelsPos++] = buffer[bufferPos++];
  267. pixelsPos += (dx - 1) * pixelBytes;
  268. }
  269. }
  270. row++;
  271. }
  272. }
  273. if (this.interlaceMethod === 1) {
  274. /*
  275. 1 6 4 6 2 6 4 6
  276. 7 7 7 7 7 7 7 7
  277. 5 6 5 6 5 6 5 6
  278. 7 7 7 7 7 7 7 7
  279. 3 6 4 6 3 6 4 6
  280. 7 7 7 7 7 7 7 7
  281. 5 6 5 6 5 6 5 6
  282. 7 7 7 7 7 7 7 7
  283. */
  284. pass(0, 0, 8, 8); // 1
  285. pass(4, 0, 8, 8); // 2
  286. pass(0, 4, 4, 8); // 3
  287. pass(2, 0, 4, 4); // 4
  288. pass(0, 2, 2, 4); // 5
  289. pass(1, 0, 2, 2); // 6
  290. pass(0, 1, 1, 2); // 7
  291. } else {
  292. pass(0, 0, 1, 1, true);
  293. }
  294. return fn(pixels);
  295. });
  296. }
  297. decodePalette() {
  298. const { palette } = this;
  299. const { length } = palette;
  300. const transparency = this.transparency.indexed || [];
  301. const ret = new Buffer(transparency.length + length);
  302. let pos = 0;
  303. let c = 0;
  304. for (let i = 0; i < length; i += 3) {
  305. var left;
  306. ret[pos++] = palette[i];
  307. ret[pos++] = palette[i + 1];
  308. ret[pos++] = palette[i + 2];
  309. ret[pos++] = (left = transparency[c++]) != null ? left : 255;
  310. }
  311. return ret;
  312. }
  313. copyToImageData(imageData, pixels) {
  314. let j, k;
  315. let { colors } = this;
  316. let palette = null;
  317. let alpha = this.hasAlphaChannel;
  318. if (this.palette.length) {
  319. palette =
  320. this._decodedPalette || (this._decodedPalette = this.decodePalette());
  321. colors = 4;
  322. alpha = true;
  323. }
  324. const data = imageData.data || imageData;
  325. const { length } = data;
  326. const input = palette || pixels;
  327. let i = (j = 0);
  328. if (colors === 1) {
  329. while (i < length) {
  330. k = palette ? pixels[i / 4] * 4 : j;
  331. const v = input[k++];
  332. data[i++] = v;
  333. data[i++] = v;
  334. data[i++] = v;
  335. data[i++] = alpha ? input[k++] : 255;
  336. j = k;
  337. }
  338. } else {
  339. while (i < length) {
  340. k = palette ? pixels[i / 4] * 4 : j;
  341. data[i++] = input[k++];
  342. data[i++] = input[k++];
  343. data[i++] = input[k++];
  344. data[i++] = alpha ? input[k++] : 255;
  345. j = k;
  346. }
  347. }
  348. }
  349. decode(fn) {
  350. const ret = new Buffer(this.width * this.height * 4);
  351. return this.decodePixels(pixels => {
  352. this.copyToImageData(ret, pixels);
  353. return fn(ret);
  354. });
  355. }
  356. };