UNPKG

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