1 | "use strict";
|
2 |
|
3 | let constants = require("./constants");
|
4 | let CrcCalculator = require("./crc");
|
5 |
|
6 | let Parser = (module.exports = function (options, dependencies) {
|
7 | this._options = options;
|
8 | options.checkCRC = options.checkCRC !== false;
|
9 |
|
10 | this._hasIHDR = false;
|
11 | this._hasIEND = false;
|
12 | this._emittedHeadersFinished = false;
|
13 |
|
14 |
|
15 | this._palette = [];
|
16 | this._colorType = 0;
|
17 |
|
18 | this._chunks = {};
|
19 | this._chunks[constants.TYPE_IHDR] = this._handleIHDR.bind(this);
|
20 | this._chunks[constants.TYPE_IEND] = this._handleIEND.bind(this);
|
21 | this._chunks[constants.TYPE_IDAT] = this._handleIDAT.bind(this);
|
22 | this._chunks[constants.TYPE_PLTE] = this._handlePLTE.bind(this);
|
23 | this._chunks[constants.TYPE_tRNS] = this._handleTRNS.bind(this);
|
24 | this._chunks[constants.TYPE_gAMA] = this._handleGAMA.bind(this);
|
25 |
|
26 | this.read = dependencies.read;
|
27 | this.error = dependencies.error;
|
28 | this.metadata = dependencies.metadata;
|
29 | this.gamma = dependencies.gamma;
|
30 | this.transColor = dependencies.transColor;
|
31 | this.palette = dependencies.palette;
|
32 | this.parsed = dependencies.parsed;
|
33 | this.inflateData = dependencies.inflateData;
|
34 | this.finished = dependencies.finished;
|
35 | this.simpleTransparency = dependencies.simpleTransparency;
|
36 | this.headersFinished = dependencies.headersFinished || function () {};
|
37 | });
|
38 |
|
39 | Parser.prototype.start = function () {
|
40 | this.read(constants.PNG_SIGNATURE.length, this._parseSignature.bind(this));
|
41 | };
|
42 |
|
43 | Parser.prototype._parseSignature = function (data) {
|
44 | let signature = constants.PNG_SIGNATURE;
|
45 |
|
46 | for (let i = 0; i < signature.length; i++) {
|
47 | if (data[i] !== signature[i]) {
|
48 | this.error(new Error("Invalid file signature"));
|
49 | return;
|
50 | }
|
51 | }
|
52 | this.read(8, this._parseChunkBegin.bind(this));
|
53 | };
|
54 |
|
55 | Parser.prototype._parseChunkBegin = function (data) {
|
56 |
|
57 | let length = data.readUInt32BE(0);
|
58 |
|
59 |
|
60 | let type = data.readUInt32BE(4);
|
61 | let name = "";
|
62 | for (let i = 4; i < 8; i++) {
|
63 | name += String.fromCharCode(data[i]);
|
64 | }
|
65 |
|
66 |
|
67 |
|
68 |
|
69 | let ancillary = Boolean(data[4] & 0x20);
|
70 |
|
71 |
|
72 |
|
73 | if (!this._hasIHDR && type !== constants.TYPE_IHDR) {
|
74 | this.error(new Error("Expected IHDR on beggining"));
|
75 | return;
|
76 | }
|
77 |
|
78 | this._crc = new CrcCalculator();
|
79 | this._crc.write(Buffer.from(name));
|
80 |
|
81 | if (this._chunks[type]) {
|
82 | return this._chunks[type](length);
|
83 | }
|
84 |
|
85 | if (!ancillary) {
|
86 | this.error(new Error("Unsupported critical chunk type " + name));
|
87 | return;
|
88 | }
|
89 |
|
90 | this.read(length + 4, this._skipChunk.bind(this));
|
91 | };
|
92 |
|
93 | Parser.prototype._skipChunk = function (/*data*/) {
|
94 | this.read(8, this._parseChunkBegin.bind(this));
|
95 | };
|
96 |
|
97 | Parser.prototype._handleChunkEnd = function () {
|
98 | this.read(4, this._parseChunkEnd.bind(this));
|
99 | };
|
100 |
|
101 | Parser.prototype._parseChunkEnd = function (data) {
|
102 | let fileCrc = data.readInt32BE(0);
|
103 | let calcCrc = this._crc.crc32();
|
104 |
|
105 |
|
106 | if (this._options.checkCRC && calcCrc !== fileCrc) {
|
107 | this.error(new Error("Crc error - " + fileCrc + " - " + calcCrc));
|
108 | return;
|
109 | }
|
110 |
|
111 | if (!this._hasIEND) {
|
112 | this.read(8, this._parseChunkBegin.bind(this));
|
113 | }
|
114 | };
|
115 |
|
116 | Parser.prototype._handleIHDR = function (length) {
|
117 | this.read(length, this._parseIHDR.bind(this));
|
118 | };
|
119 | Parser.prototype._parseIHDR = function (data) {
|
120 | this._crc.write(data);
|
121 |
|
122 | let width = data.readUInt32BE(0);
|
123 | let height = data.readUInt32BE(4);
|
124 | let depth = data[8];
|
125 | let colorType = data[9];
|
126 | let compr = data[10];
|
127 | let filter = data[11];
|
128 | let interlace = data[12];
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 | if (
|
136 | depth !== 8 &&
|
137 | depth !== 4 &&
|
138 | depth !== 2 &&
|
139 | depth !== 1 &&
|
140 | depth !== 16
|
141 | ) {
|
142 | this.error(new Error("Unsupported bit depth " + depth));
|
143 | return;
|
144 | }
|
145 | if (!(colorType in constants.COLORTYPE_TO_BPP_MAP)) {
|
146 | this.error(new Error("Unsupported color type"));
|
147 | return;
|
148 | }
|
149 | if (compr !== 0) {
|
150 | this.error(new Error("Unsupported compression method"));
|
151 | return;
|
152 | }
|
153 | if (filter !== 0) {
|
154 | this.error(new Error("Unsupported filter method"));
|
155 | return;
|
156 | }
|
157 | if (interlace !== 0 && interlace !== 1) {
|
158 | this.error(new Error("Unsupported interlace method"));
|
159 | return;
|
160 | }
|
161 |
|
162 | this._colorType = colorType;
|
163 |
|
164 | let bpp = constants.COLORTYPE_TO_BPP_MAP[this._colorType];
|
165 |
|
166 | this._hasIHDR = true;
|
167 |
|
168 | this.metadata({
|
169 | width: width,
|
170 | height: height,
|
171 | depth: depth,
|
172 | interlace: Boolean(interlace),
|
173 | palette: Boolean(colorType & constants.COLORTYPE_PALETTE),
|
174 | color: Boolean(colorType & constants.COLORTYPE_COLOR),
|
175 | alpha: Boolean(colorType & constants.COLORTYPE_ALPHA),
|
176 | bpp: bpp,
|
177 | colorType: colorType,
|
178 | });
|
179 |
|
180 | this._handleChunkEnd();
|
181 | };
|
182 |
|
183 | Parser.prototype._handlePLTE = function (length) {
|
184 | this.read(length, this._parsePLTE.bind(this));
|
185 | };
|
186 | Parser.prototype._parsePLTE = function (data) {
|
187 | this._crc.write(data);
|
188 |
|
189 | let entries = Math.floor(data.length / 3);
|
190 |
|
191 |
|
192 | for (let i = 0; i < entries; i++) {
|
193 | this._palette.push([data[i * 3], data[i * 3 + 1], data[i * 3 + 2], 0xff]);
|
194 | }
|
195 |
|
196 | this.palette(this._palette);
|
197 |
|
198 | this._handleChunkEnd();
|
199 | };
|
200 |
|
201 | Parser.prototype._handleTRNS = function (length) {
|
202 | this.simpleTransparency();
|
203 | this.read(length, this._parseTRNS.bind(this));
|
204 | };
|
205 | Parser.prototype._parseTRNS = function (data) {
|
206 | this._crc.write(data);
|
207 |
|
208 |
|
209 | if (this._colorType === constants.COLORTYPE_PALETTE_COLOR) {
|
210 | if (this._palette.length === 0) {
|
211 | this.error(new Error("Transparency chunk must be after palette"));
|
212 | return;
|
213 | }
|
214 | if (data.length > this._palette.length) {
|
215 | this.error(new Error("More transparent colors than palette size"));
|
216 | return;
|
217 | }
|
218 | for (let i = 0; i < data.length; i++) {
|
219 | this._palette[i][3] = data[i];
|
220 | }
|
221 | this.palette(this._palette);
|
222 | }
|
223 |
|
224 |
|
225 |
|
226 | if (this._colorType === constants.COLORTYPE_GRAYSCALE) {
|
227 |
|
228 | this.transColor([data.readUInt16BE(0)]);
|
229 | }
|
230 | if (this._colorType === constants.COLORTYPE_COLOR) {
|
231 | this.transColor([
|
232 | data.readUInt16BE(0),
|
233 | data.readUInt16BE(2),
|
234 | data.readUInt16BE(4),
|
235 | ]);
|
236 | }
|
237 |
|
238 | this._handleChunkEnd();
|
239 | };
|
240 |
|
241 | Parser.prototype._handleGAMA = function (length) {
|
242 | this.read(length, this._parseGAMA.bind(this));
|
243 | };
|
244 | Parser.prototype._parseGAMA = function (data) {
|
245 | this._crc.write(data);
|
246 | this.gamma(data.readUInt32BE(0) / constants.GAMMA_DIVISION);
|
247 |
|
248 | this._handleChunkEnd();
|
249 | };
|
250 |
|
251 | Parser.prototype._handleIDAT = function (length) {
|
252 | if (!this._emittedHeadersFinished) {
|
253 | this._emittedHeadersFinished = true;
|
254 | this.headersFinished();
|
255 | }
|
256 | this.read(-length, this._parseIDAT.bind(this, length));
|
257 | };
|
258 | Parser.prototype._parseIDAT = function (length, data) {
|
259 | this._crc.write(data);
|
260 |
|
261 | if (
|
262 | this._colorType === constants.COLORTYPE_PALETTE_COLOR &&
|
263 | this._palette.length === 0
|
264 | ) {
|
265 | throw new Error("Expected palette not found");
|
266 | }
|
267 |
|
268 | this.inflateData(data);
|
269 | let leftOverLength = length - data.length;
|
270 |
|
271 | if (leftOverLength > 0) {
|
272 | this._handleIDAT(leftOverLength);
|
273 | } else {
|
274 | this._handleChunkEnd();
|
275 | }
|
276 | };
|
277 |
|
278 | Parser.prototype._handleIEND = function (length) {
|
279 | this.read(length, this._parseIEND.bind(this));
|
280 | };
|
281 | Parser.prototype._parseIEND = function (data) {
|
282 | this._crc.write(data);
|
283 |
|
284 | this._hasIEND = true;
|
285 | this._handleChunkEnd();
|
286 |
|
287 | if (this.finished) {
|
288 | this.finished();
|
289 | }
|
290 | };
|