UNPKG

9.47 kBJavaScriptView Raw
1const zlib = require('zlib');
2
3class 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 //reads numBytes bytes of buffer b offset by offset bytes
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 console.log({
120 bytesInFrame,
121 oldChunk,
122 frameDuration,
123 newChunk
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 //handle ICC profile data
176 /*
177 console.log({
178 type,
179 flag,
180 fGamma
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 console.log({
236 paletteSize,
237 firstColor,
238 secondColor,
239 colors
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 console.log({
263 flags,
264 type,
265 layerChildLevel,
266 blendMode,
267 opacity,
268 name
269 });*/
270 }
271 //size of chunk in bytes for the WHOLE thing
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); //take the first 20 bytes off for the data above and chunk info
282 /*
283 console.log({
284 layerIndex,
285 x,
286 y,
287 opacity,
288 celType,
289 w,
290 h
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 console.log({
311 cSize,
312 type
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
365module.exports = Aseprite;