1 | const zlib = require('zlib');
|
2 |
|
3 | class Aseprite {
|
4 | constructor(buffer, name) {
|
5 | this._offset = 0;
|
6 | this._buffer = buffer;
|
7 | this.frames = [];
|
8 | this.layers = [];
|
9 | this.fileSize;
|
10 | this.numFrames;
|
11 | this.width;
|
12 | this.height;
|
13 | this.colorDepth;
|
14 | this.numColors;
|
15 | this.pixelRatio;
|
16 | this.name = name;
|
17 | this.tags = [];
|
18 | }
|
19 | readNextByte() {
|
20 | const nextByte = this._buffer.readUInt8(this._offset);
|
21 | this._offset += 1;
|
22 | return nextByte;
|
23 | }
|
24 | readByte(offset) {
|
25 | return this._buffer.readUInt8(offset);
|
26 | }
|
27 | readNextWord() {
|
28 | const word = this._buffer.readUInt16LE(this._offset);
|
29 | this._offset += 2;
|
30 | return word;
|
31 | }
|
32 | readWord(offset) {
|
33 | return this._buffer.readUInt16LE(offset);
|
34 | }
|
35 | readNextShort() {
|
36 | const short = this._buffer.readInt16LE(this._offset);
|
37 | this._offset += 2;
|
38 | return short;
|
39 | }
|
40 | readShort(offset) {
|
41 | return this._buffer.readInt16LE(offset);
|
42 | }
|
43 | readNextDWord() {
|
44 | const dWord = this._buffer.readUInt32LE(this._offset);
|
45 | this._offset += 4;
|
46 | return dWord;
|
47 | }
|
48 | readDWord(offset) {
|
49 | return this._buffer.readUInt32LE(offset);
|
50 | }
|
51 | readNextLong() {
|
52 | const long = this._buffer.readInt32LE(this._offset);
|
53 | this._offset += 4;
|
54 | return long;
|
55 | }
|
56 | readLong(offset) {
|
57 | return this._buffer.readInt32LE(offset);
|
58 | }
|
59 | readNextFixed() {
|
60 | const fixed = this._buffer.readFloatLE(this._offset);
|
61 | this._offset += 4;
|
62 | return fixed;
|
63 | }
|
64 | readFixed(offset) {
|
65 | return this._buffer.readFloatLE(offset);
|
66 | }
|
67 | readNextBytes(numBytes) {
|
68 | let strBuff = Buffer.alloc(numBytes);
|
69 | for (let i = 0; i < numBytes; i++) {
|
70 | strBuff.writeUInt8(this.readNextByte(), i);
|
71 | }
|
72 | return strBuff.toString();
|
73 | }
|
74 | readNextRawBytes(numBytes) {
|
75 | let buff = Buffer.alloc(numBytes);
|
76 | for (let i = 0; i < numBytes; i++) {
|
77 | buff.writeUInt8(this.readNextByte(), i);
|
78 | }
|
79 | return buff;
|
80 | }
|
81 |
|
82 | readRawBytes(numBytes, b, offset) {
|
83 | let buff = Buffer.alloc(numBytes - offset);
|
84 | for (let i = 0; i < numBytes - offset; i++) {
|
85 | buff.writeUInt8(b.readUInt8(offset + i), i);
|
86 | }
|
87 | return buff;
|
88 | }
|
89 | readNextString() {
|
90 | const numBytes = this.readNextWord();
|
91 | return this.readNextBytes(numBytes);
|
92 | }
|
93 | skipBytes(numBytes) {
|
94 | this._offset += numBytes;
|
95 | }
|
96 | readHeader() {
|
97 | this.fileSize = this.readNextDWord();
|
98 | this.readNextWord();
|
99 | this.numFrames = this.readNextWord();
|
100 | this.width = this.readNextWord();
|
101 | this.height = this.readNextWord();
|
102 | this.colorDepth = this.readNextWord();
|
103 | this.skipBytes(18);
|
104 | this.numColors = this.readNextWord();
|
105 | const pixW = this.readNextByte();
|
106 | const pixH = this.readNextByte();
|
107 | this.pixelRatio = `${pixW}:${pixH}`;
|
108 | this.skipBytes(92);
|
109 | return this.numFrames;
|
110 | }
|
111 | readFrame() {
|
112 | const bytesInFrame = this.readNextDWord();
|
113 | this.skipBytes(2);
|
114 | const oldChunk = this.readNextWord();
|
115 | const frameDuration = this.readNextWord();
|
116 | this.skipBytes(2);
|
117 | const newChunk = this.readNextDWord();
|
118 | |
119 |
|
120 |
|
121 |
|
122 |
|
123 |
|
124 |
|
125 | let cels = [];
|
126 | for(let i = 0; i < newChunk; i ++) {
|
127 | let chunkData = this.readChunk();
|
128 | switch(chunkData.type) {
|
129 | case 0x0004:
|
130 | case 0x0011:
|
131 | case 0x2016:
|
132 | case 0x2017:
|
133 | case 0x2020:
|
134 | case 0x2022:
|
135 | this.skipBytes(chunkData.chunkSize - 6);
|
136 | break;
|
137 | case 0x2004:
|
138 | console.log('Layer');
|
139 | this.readLayerChunk();
|
140 | break;
|
141 | case 0x2005:
|
142 | console.log('Cel')
|
143 | let celData = this.readCelChunk(chunkData.chunkSize);
|
144 | cels.push(celData);
|
145 | break;
|
146 | case 0x2007:
|
147 | this.readColorProfileChunk();
|
148 | break;
|
149 | case 0x2018:
|
150 | console.log('Frame Tags');
|
151 | this.readFrameTagsChunk();
|
152 | break;
|
153 | case 0x2019:
|
154 | console.log('Palette');
|
155 | this.palette = this.readPaletteChunk();
|
156 | break;
|
157 | }
|
158 | }
|
159 | this.frames.push({ bytesInFrame,
|
160 | frameDuration,
|
161 | numChunks: newChunk,
|
162 | cels});
|
163 | }
|
164 | readColorProfileChunk() {
|
165 | const types = [
|
166 | 'None',
|
167 | 'sRGB',
|
168 | 'ICC'
|
169 | ]
|
170 | const typeInd = this.readNextWord();
|
171 | const type = types[typeInd];
|
172 | const flag = this.readNextWord();
|
173 | const fGamma = this.readNextFixed();
|
174 | this.skipBytes(8);
|
175 |
|
176 | |
177 |
|
178 |
|
179 |
|
180 |
|
181 |
|
182 | this.colorProfile = {
|
183 | type,
|
184 | flag,
|
185 | fGamma};
|
186 | }
|
187 | readFrameTagsChunk() {
|
188 | const loops = [
|
189 | 'Forward',
|
190 | 'Reverse',
|
191 | 'Ping-pong'
|
192 | ]
|
193 | const numTags = this.readNextWord();
|
194 | this.skipBytes(8);
|
195 | for(let i = 0; i < numTags; i ++) {
|
196 | let tag = {};
|
197 | tag.from = this.readNextWord();
|
198 | tag.to = this.readNextWord();
|
199 | const loopsInd = this.readNextByte();
|
200 | tag.animDirection = loops[loopsInd];
|
201 | this.skipBytes(8);
|
202 | tag.color = this.readNextRawBytes(3).readUIntLE(0,3);
|
203 | this.skipBytes(1);
|
204 | tag.name = this.readNextString();
|
205 | this.tags.push(tag);
|
206 | console.log(tag);
|
207 | }
|
208 | }
|
209 | readPaletteChunk() {
|
210 | const paletteSize = this.readNextDWord();
|
211 | const firstColor = this.readNextDWord();
|
212 | const secondColor = this.readNextDWord();
|
213 | this.skipBytes(8);
|
214 | let colors = [];
|
215 | for (let i = 0; i < paletteSize; i++) {
|
216 | let flag = this.readNextWord();
|
217 | let red = this.readNextByte();
|
218 | let green = this.readNextByte();
|
219 | let blue = this.readNextByte();
|
220 | let alpha = this.readNextByte();
|
221 | let name;
|
222 | if (flag === 1) {
|
223 | name = this.readNextString();
|
224 | }
|
225 | colors.push({
|
226 | flag,
|
227 | red,
|
228 | green,
|
229 | blue,
|
230 | alpha,
|
231 | name: name !== undefined ? name : "none"
|
232 | });
|
233 | }
|
234 | |
235 |
|
236 |
|
237 |
|
238 |
|
239 |
|
240 |
|
241 | return { paletteSize,
|
242 | firstColor,
|
243 | lastColor: secondColor,
|
244 | colors };
|
245 | }
|
246 | readLayerChunk() {
|
247 | const flags = this.readNextWord();
|
248 | const type = this.readNextWord();
|
249 | const layerChildLevel = this.readNextWord();
|
250 | this.skipBytes(4);
|
251 | const blendMode = this.readNextWord();
|
252 | const opacity = this.readNextByte();
|
253 | this.skipBytes(3);
|
254 | const name = this.readNextString();
|
255 | this.layers.push({ flags,
|
256 | type,
|
257 | layerChildLevel,
|
258 | blendMode,
|
259 | opacity,
|
260 | name});
|
261 | |
262 |
|
263 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 |
|
270 | }
|
271 |
|
272 | readCelChunk(chunkSize) {
|
273 | const layerIndex = this.readNextWord();
|
274 | const x = this.readNextShort();
|
275 | const y = this.readNextShort();
|
276 | const opacity = this.readNextByte();
|
277 | const celType = this.readNextWord();
|
278 | this.skipBytes(7);
|
279 | const w = this.readNextWord();
|
280 | const h = this.readNextWord();
|
281 | const buff = this.readNextRawBytes(chunkSize - 26);
|
282 | |
283 |
|
284 |
|
285 |
|
286 |
|
287 |
|
288 |
|
289 |
|
290 |
|
291 | let rawCel;
|
292 | if(celType === 2) {
|
293 | rawCel = zlib.inflateSync(buff);
|
294 | } else if(celType === 0) {
|
295 | rawCel = buff;
|
296 | }
|
297 | return { layerIndex,
|
298 | xpos: x,
|
299 | ypos: y,
|
300 | opacity,
|
301 | celType,
|
302 | w,
|
303 | h,
|
304 | rawCelData: rawCel}
|
305 | }
|
306 | readChunk() {
|
307 | const cSize = this.readNextDWord();
|
308 | const type = this.readNextWord();
|
309 | |
310 |
|
311 |
|
312 |
|
313 |
|
314 | return {chunkSize: cSize, type: type};
|
315 | }
|
316 | parse() {
|
317 | const numFrames = this.readHeader();
|
318 | for(let i = 0; i < numFrames; i ++) {
|
319 | this.readFrame();
|
320 | }
|
321 |
|
322 | }
|
323 | formatBytes(bytes,decimals) {
|
324 | if (bytes === 0) {
|
325 | return '0 Byte';
|
326 | }
|
327 | const k = 1024;
|
328 | const dm = decimals + 1 || 3;
|
329 | const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
330 | const i = Math.floor(Math.log(bytes) / Math.log(k));
|
331 | return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
332 | };
|
333 | toString() {
|
334 | return {
|
335 | fileSize: this.fileSize,
|
336 | numFrames: this.numFrames,
|
337 | frames: this.frames.map(frame => {
|
338 | return {
|
339 | size: frame.bytesInFrame,
|
340 | duration: frame.frameDuration,
|
341 | chunks: frame.numChunks,
|
342 | cels: frame.cels.map(cel => {
|
343 | return {
|
344 | layerIndex: cel.layerIndex,
|
345 | xpos: cel.xpos,
|
346 | ypos: cel.ypos,
|
347 | opacity: cel.opacity,
|
348 | celType: cel.celType,
|
349 | w: cel.w,
|
350 | h: cel.h,
|
351 | rawCelData: 'buffer'
|
352 | }
|
353 | }).toString() }
|
354 | }).toString(),
|
355 | width: this.width,
|
356 | height: this.height,
|
357 | colorDepth: this.colorDepth,
|
358 | numColors: this.numColors,
|
359 | pixelRatio: this.pixelRatio,
|
360 | layers: this.layers.toString()
|
361 | }.toString();
|
362 | }
|
363 | }
|
364 |
|
365 | module.exports = Aseprite;
|