Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 
 

561 рядки
17 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. window.PNG = (function() {
  21. let APNG_DISPOSE_OP_NONE = 0;
  22. let APNG_DISPOSE_OP_BACKGROUND = 1;
  23. let APNG_DISPOSE_OP_PREVIOUS = 2;
  24. let APNG_BLEND_OP_SOURCE = 0;
  25. let APNG_BLEND_OP_OVER = 1;
  26. let scratchCanvas = document.createElement('canvas');
  27. let scratchCtx = scratchCanvas.getContext('2d');
  28. let makeImage = function(imageData) {
  29. scratchCtx.width = imageData.width;
  30. scratchCtx.height = imageData.height;
  31. scratchCtx.clearRect(0, 0, imageData.width, imageData.height);
  32. scratchCtx.putImageData(imageData, 0, 0);
  33. const img = new Image();
  34. img.src = scratchCanvas.toDataURL();
  35. return img;
  36. };
  37. class PNG {
  38. static load(url, canvas, callback) {
  39. if (typeof canvas === 'function') {
  40. callback = canvas;
  41. }
  42. const xhr = new XMLHttpRequest();
  43. xhr.open('GET', url, true);
  44. xhr.responseType = 'arraybuffer';
  45. xhr.onload = () => {
  46. const data = new Uint8Array(xhr.response || xhr.mozResponseArrayBuffer);
  47. const png = new PNG(data);
  48. if (typeof (canvas && canvas.getContext) === 'function') {
  49. png.render(canvas);
  50. }
  51. return typeof callback === 'function' ? callback(png) : undefined;
  52. };
  53. return xhr.send(null);
  54. }
  55. constructor(data1) {
  56. let i;
  57. this.data = data1;
  58. this.pos = 8; // Skip the default header
  59. this.palette = [];
  60. this.imgData = [];
  61. this.transparency = {};
  62. this.animation = null;
  63. this.text = {};
  64. let frame = null;
  65. while (true) {
  66. var data;
  67. let chunkSize = this.readUInt32();
  68. let section = '';
  69. for (i = 0; i < 4; i++) {
  70. section += String.fromCharCode(this.data[this.pos++]);
  71. }
  72. switch (section) {
  73. case 'IHDR':
  74. // we can grab interesting values from here (like width, height, etc)
  75. this.width = this.readUInt32();
  76. this.height = this.readUInt32();
  77. this.bits = this.data[this.pos++];
  78. this.colorType = this.data[this.pos++];
  79. this.compressionMethod = this.data[this.pos++];
  80. this.filterMethod = this.data[this.pos++];
  81. this.interlaceMethod = this.data[this.pos++];
  82. break;
  83. case 'acTL':
  84. // we have an animated PNG
  85. this.animation = {
  86. numFrames: this.readUInt32(),
  87. numPlays: this.readUInt32() || Infinity,
  88. frames: []
  89. };
  90. break;
  91. case 'PLTE':
  92. this.palette = this.read(chunkSize);
  93. break;
  94. case 'fcTL':
  95. if (frame) {
  96. this.animation.frames.push(frame);
  97. }
  98. this.pos += 4; // skip sequence number
  99. frame = {
  100. width: this.readUInt32(),
  101. height: this.readUInt32(),
  102. xOffset: this.readUInt32(),
  103. yOffset: this.readUInt32()
  104. };
  105. var delayNum = this.readUInt16();
  106. var delayDen = this.readUInt16() || 100;
  107. frame.delay = (1000 * delayNum) / delayDen;
  108. frame.disposeOp = this.data[this.pos++];
  109. frame.blendOp = this.data[this.pos++];
  110. frame.data = [];
  111. break;
  112. case 'IDAT':
  113. case 'fdAT':
  114. if (section === 'fdAT') {
  115. this.pos += 4; // skip sequence number
  116. chunkSize -= 4;
  117. }
  118. data = (frame && frame.data) || this.imgData;
  119. for (i = 0; i < chunkSize; i++) {
  120. data.push(this.data[this.pos++]);
  121. }
  122. break;
  123. case 'tRNS':
  124. // This chunk can only occur once and it must occur after the
  125. // PLTE chunk and before the IDAT chunk.
  126. this.transparency = {};
  127. switch (this.colorType) {
  128. case 3:
  129. // Indexed color, RGB. Each byte in this chunk is an alpha for
  130. // the palette index in the PLTE ("palette") chunk up until the
  131. // last non-opaque entry. Set up an array, stretching over all
  132. // palette entries which will be 0 (opaque) or 1 (transparent).
  133. this.transparency.indexed = this.read(chunkSize);
  134. var short = 255 - this.transparency.indexed.length;
  135. if (short > 0) {
  136. for (i = 0; i < short; i++) {
  137. this.transparency.indexed.push(255);
  138. }
  139. }
  140. break;
  141. case 0:
  142. // Greyscale. Corresponding to entries in the PLTE chunk.
  143. // Grey is two bytes, range 0 .. (2 ^ bit-depth) - 1
  144. this.transparency.grayscale = this.read(chunkSize)[0];
  145. break;
  146. case 2:
  147. // True color with proper alpha channel.
  148. this.transparency.rgb = this.read(chunkSize);
  149. break;
  150. }
  151. break;
  152. case 'tEXt':
  153. var text = this.read(chunkSize);
  154. var index = text.indexOf(0);
  155. var key = String.fromCharCode.apply(String, text.slice(0, index));
  156. this.text[key] = String.fromCharCode.apply(
  157. String,
  158. text.slice(index + 1)
  159. );
  160. break;
  161. case 'IEND':
  162. if (frame) {
  163. this.animation.frames.push(frame);
  164. }
  165. // we've got everything we need!
  166. switch (this.colorType) {
  167. case 0:
  168. case 3:
  169. case 4:
  170. this.colors = 1;
  171. break;
  172. case 2:
  173. case 6:
  174. this.colors = 3;
  175. break;
  176. }
  177. this.hasAlphaChannel = [4, 6].includes(this.colorType);
  178. var colors = this.colors + (this.hasAlphaChannel ? 1 : 0);
  179. this.pixelBitlength = this.bits * colors;
  180. switch (this.colors) {
  181. case 1:
  182. this.colorSpace = 'DeviceGray';
  183. break;
  184. case 3:
  185. this.colorSpace = 'DeviceRGB';
  186. break;
  187. }
  188. this.imgData = new Uint8Array(this.imgData);
  189. return;
  190. break;
  191. default:
  192. // unknown (or unimportant) section, skip it
  193. this.pos += chunkSize;
  194. }
  195. this.pos += 4; // Skip the CRC
  196. if (this.pos > this.data.length) {
  197. throw new Error('Incomplete or corrupt PNG file');
  198. }
  199. }
  200. }
  201. read(bytes) {
  202. const result = new Array(bytes);
  203. for (let i = 0; i < bytes; i++) {
  204. result[i] = this.data[this.pos++];
  205. }
  206. return result;
  207. }
  208. readUInt32() {
  209. const b1 = this.data[this.pos++] << 24;
  210. const b2 = this.data[this.pos++] << 16;
  211. const b3 = this.data[this.pos++] << 8;
  212. const b4 = this.data[this.pos++];
  213. return b1 | b2 | b3 | b4;
  214. }
  215. readUInt16() {
  216. const b1 = this.data[this.pos++] << 8;
  217. const b2 = this.data[this.pos++];
  218. return b1 | b2;
  219. }
  220. decodePixels(data) {
  221. if (data == null) {
  222. data = this.imgData;
  223. }
  224. if (data.length === 0) {
  225. return new Uint8Array(0);
  226. }
  227. data = new FlateStream(data);
  228. data = data.getBytes();
  229. const { width, height } = this;
  230. const pixelBytes = this.pixelBitlength / 8;
  231. const pixels = new Uint8Array(width * height * pixelBytes);
  232. const { length } = data;
  233. let pos = 0;
  234. function pass(x0, y0, dx, dy, singlePass = false) {
  235. const w = Math.ceil((width - x0) / dx);
  236. const h = Math.ceil((height - y0) / dy);
  237. const scanlineLength = pixelBytes * w;
  238. const buffer = singlePass ? pixels : new Uint8Array(scanlineLength * h);
  239. let row = 0;
  240. let c = 0;
  241. while (row < h && pos < length) {
  242. var byte, col, i, left, upper;
  243. switch (data[pos++]) {
  244. case 0: // None
  245. for (i = 0; i < scanlineLength; i++) {
  246. buffer[c++] = data[pos++];
  247. }
  248. break;
  249. case 1: // Sub
  250. for (i = 0; i < scanlineLength; i++) {
  251. byte = data[pos++];
  252. left = i < pixelBytes ? 0 : buffer[c - pixelBytes];
  253. buffer[c++] = (byte + left) % 256;
  254. }
  255. break;
  256. case 2: // Up
  257. for (i = 0; i < scanlineLength; i++) {
  258. byte = data[pos++];
  259. col = (i - (i % pixelBytes)) / pixelBytes;
  260. upper =
  261. row &&
  262. buffer[
  263. (row - 1) * scanlineLength +
  264. col * pixelBytes +
  265. (i % pixelBytes)
  266. ];
  267. buffer[c++] = (upper + byte) % 256;
  268. }
  269. break;
  270. case 3: // Average
  271. for (i = 0; i < scanlineLength; i++) {
  272. byte = data[pos++];
  273. col = (i - (i % pixelBytes)) / pixelBytes;
  274. left = i < pixelBytes ? 0 : buffer[c - pixelBytes];
  275. upper =
  276. row &&
  277. buffer[
  278. (row - 1) * scanlineLength +
  279. col * pixelBytes +
  280. (i % pixelBytes)
  281. ];
  282. buffer[c++] = (byte + Math.floor((left + upper) / 2)) % 256;
  283. }
  284. break;
  285. case 4: // Paeth
  286. for (i = 0; i < scanlineLength; i++) {
  287. var paeth, upperLeft;
  288. byte = data[pos++];
  289. col = (i - (i % pixelBytes)) / pixelBytes;
  290. left = i < pixelBytes ? 0 : buffer[c - pixelBytes];
  291. if (row === 0) {
  292. upper = upperLeft = 0;
  293. } else {
  294. upper =
  295. buffer[
  296. (row - 1) * scanlineLength +
  297. col * pixelBytes +
  298. (i % pixelBytes)
  299. ];
  300. upperLeft =
  301. col &&
  302. buffer[
  303. (row - 1) * scanlineLength +
  304. (col - 1) * pixelBytes +
  305. (i % pixelBytes)
  306. ];
  307. }
  308. const p = left + upper - upperLeft;
  309. const pa = Math.abs(p - left);
  310. const pb = Math.abs(p - upper);
  311. const pc = Math.abs(p - upperLeft);
  312. if (pa <= pb && pa <= pc) {
  313. paeth = left;
  314. } else if (pb <= pc) {
  315. paeth = upper;
  316. } else {
  317. paeth = upperLeft;
  318. }
  319. buffer[c++] = (byte + paeth) % 256;
  320. }
  321. break;
  322. default:
  323. throw new Error(`Invalid filter algorithm: ${data[pos - 1]}`);
  324. }
  325. if (!singlePass) {
  326. let pixelsPos = ((y0 + row * dy) * width + x0) * pixelBytes;
  327. let bufferPos = row * scanlineLength;
  328. for (i = 0; i < w; i++) {
  329. for (let j = 0; j < pixelBytes; j++)
  330. pixels[pixelsPos++] = buffer[bufferPos++];
  331. pixelsPos += (dx - 1) * pixelBytes;
  332. }
  333. }
  334. row++;
  335. }
  336. }
  337. if (this.interlaceMethod === 1) {
  338. /*
  339. 1 6 4 6 2 6 4 6
  340. 7 7 7 7 7 7 7 7
  341. 5 6 5 6 5 6 5 6
  342. 7 7 7 7 7 7 7 7
  343. 3 6 4 6 3 6 4 6
  344. 7 7 7 7 7 7 7 7
  345. 5 6 5 6 5 6 5 6
  346. 7 7 7 7 7 7 7 7
  347. */
  348. pass(0, 0, 8, 8); // 1
  349. pass(4, 0, 8, 8); // 2
  350. pass(0, 4, 4, 8); // 3
  351. pass(2, 0, 4, 4); // 4
  352. pass(0, 2, 2, 4); // 5
  353. pass(1, 0, 2, 2); // 6
  354. pass(0, 1, 1, 2); // 7
  355. } else {
  356. pass(0, 0, 1, 1, true);
  357. }
  358. return pixels;
  359. }
  360. decodePalette() {
  361. const { palette } = this;
  362. const { length } = palette;
  363. const transparency = this.transparency.indexed || [];
  364. const ret = new Uint8Array((transparency.length || 0) + length);
  365. let pos = 0;
  366. let c = 0;
  367. for (let i = 0; i < length; i += 3) {
  368. var left;
  369. ret[pos++] = palette[i];
  370. ret[pos++] = palette[i + 1];
  371. ret[pos++] = palette[i + 2];
  372. ret[pos++] = (left = transparency[c++]) != null ? left : 255;
  373. }
  374. return ret;
  375. }
  376. copyToImageData(imageData, pixels) {
  377. let j, k;
  378. let { colors } = this;
  379. let palette = null;
  380. let alpha = this.hasAlphaChannel;
  381. if (this.palette.length) {
  382. palette =
  383. this._decodedPalette || (this._decodedPalette = this.decodePalette());
  384. colors = 4;
  385. alpha = true;
  386. }
  387. const data = imageData.data || imageData;
  388. const { length } = data;
  389. const input = palette || pixels;
  390. let i = (j = 0);
  391. if (colors === 1) {
  392. while (i < length) {
  393. k = palette ? pixels[i / 4] * 4 : j;
  394. const v = input[k++];
  395. data[i++] = v;
  396. data[i++] = v;
  397. data[i++] = v;
  398. data[i++] = alpha ? input[k++] : 255;
  399. j = k;
  400. }
  401. } else {
  402. while (i < length) {
  403. k = palette ? pixels[i / 4] * 4 : j;
  404. data[i++] = input[k++];
  405. data[i++] = input[k++];
  406. data[i++] = input[k++];
  407. data[i++] = alpha ? input[k++] : 255;
  408. j = k;
  409. }
  410. }
  411. }
  412. decode() {
  413. const ret = new Uint8Array(this.width * this.height * 4);
  414. this.copyToImageData(ret, this.decodePixels());
  415. return ret;
  416. }
  417. decodeFrames(ctx) {
  418. if (!this.animation) {
  419. return;
  420. }
  421. for (let i = 0; i < this.animation.frames.length; i++) {
  422. const frame = this.animation.frames[i];
  423. const imageData = ctx.createImageData(frame.width, frame.height);
  424. const pixels = this.decodePixels(new Uint8Array(frame.data));
  425. this.copyToImageData(imageData, pixels);
  426. frame.imageData = imageData;
  427. frame.image = makeImage(imageData);
  428. }
  429. }
  430. renderFrame(ctx, number) {
  431. const { frames } = this.animation;
  432. const frame = frames[number];
  433. const prev = frames[number - 1];
  434. // if we're on the first frame, clear the canvas
  435. if (number === 0) {
  436. ctx.clearRect(0, 0, this.width, this.height);
  437. }
  438. // check the previous frame's dispose operation
  439. if ((prev && prev.disposeOp) === APNG_DISPOSE_OP_BACKGROUND) {
  440. ctx.clearRect(prev.xOffset, prev.yOffset, prev.width, prev.height);
  441. } else if ((prev && prev.disposeOp) === APNG_DISPOSE_OP_PREVIOUS) {
  442. ctx.putImageData(prev.imageData, prev.xOffset, prev.yOffset);
  443. }
  444. // APNG_BLEND_OP_SOURCE overwrites the previous data
  445. if (frame.blendOp === APNG_BLEND_OP_SOURCE) {
  446. ctx.clearRect(frame.xOffset, frame.yOffset, frame.width, frame.height);
  447. }
  448. // draw the current frame
  449. return ctx.drawImage(frame.image, frame.xOffset, frame.yOffset);
  450. }
  451. animate(ctx) {
  452. let frameNumber = 0;
  453. const { numFrames, frames, numPlays } = this.animation;
  454. const doFrame = () => {
  455. const f = frameNumber++ % numFrames;
  456. const frame = frames[f];
  457. this.renderFrame(ctx, f);
  458. if (numFrames > 1 && frameNumber / numFrames < numPlays) {
  459. this.animation._timeout = setTimeout(doFrame, frame.delay);
  460. }
  461. };
  462. doFrame();
  463. }
  464. stopAnimation() {
  465. return clearTimeout(this.animation && this.animation._timeout);
  466. }
  467. render(canvas) {
  468. // if this canvas was displaying another image before,
  469. // stop the animation on it
  470. if (canvas._png) {
  471. canvas._png.stopAnimation();
  472. }
  473. canvas._png = this;
  474. canvas.width = this.width;
  475. canvas.height = this.height;
  476. const ctx = canvas.getContext('2d');
  477. if (this.animation) {
  478. this.decodeFrames(ctx);
  479. return this.animate(ctx);
  480. } else {
  481. const data = ctx.createImageData(this.width, this.height);
  482. this.copyToImageData(data, this.decodePixels());
  483. return ctx.putImageData(data, 0, 0);
  484. }
  485. }
  486. }
  487. return PNG;
  488. })();