UNPKG

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