UNPKG

8.66 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 let cels = [];
119 for(let i = 0; i < newChunk; i ++) {
120 let chunkData = this.readChunk();
121 switch(chunkData.type) {
122 case 0x0004:
123 case 0x0011:
124 case 0x2016:
125 case 0x2017:
126 case 0x2020:
127 case 0x2022:
128 this.skipBytes(chunkData.chunkSize - 6);
129 break;
130 case 0x2004:
131 this.readLayerChunk();
132 break;
133 case 0x2005:
134 let celData = this.readCelChunk(chunkData.chunkSize);
135 cels.push(celData);
136 break;
137 case 0x2007:
138 this.readColorProfileChunk();
139 break;
140 case 0x2018:
141 this.readFrameTagsChunk();
142 break;
143 case 0x2019:
144 this.palette = this.readPaletteChunk();
145 break;
146 }
147 }
148 this.frames.push({ bytesInFrame,
149 frameDuration,
150 numChunks: newChunk,
151 cels});
152 }
153 readColorProfileChunk() {
154 const types = [
155 'None',
156 'sRGB',
157 'ICC'
158 ]
159 const typeInd = this.readNextWord();
160 const type = types[typeInd];
161 const flag = this.readNextWord();
162 const fGamma = this.readNextFixed();
163 this.skipBytes(8);
164 //handle ICC profile data
165 this.colorProfile = {
166 type,
167 flag,
168 fGamma};
169 }
170 readFrameTagsChunk() {
171 const loops = [
172 'Forward',
173 'Reverse',
174 'Ping-pong'
175 ]
176 const numTags = this.readNextWord();
177 this.skipBytes(8);
178 for(let i = 0; i < numTags; i ++) {
179 let tag = {};
180 tag.from = this.readNextWord();
181 tag.to = this.readNextWord();
182 const loopsInd = this.readNextByte();
183 tag.animDirection = loops[loopsInd];
184 this.skipBytes(8);
185 tag.color = this.readNextRawBytes(3).toString('hex');
186 this.skipBytes(1);
187 tag.name = this.readNextString();
188 this.tags.push(tag);
189 }
190 }
191 readPaletteChunk() {
192 const paletteSize = this.readNextDWord();
193 const firstColor = this.readNextDWord();
194 const secondColor = this.readNextDWord();
195 this.skipBytes(8);
196 let colors = [];
197 for (let i = 0; i < paletteSize; i++) {
198 let flag = this.readNextWord();
199 let red = this.readNextByte();
200 let green = this.readNextByte();
201 let blue = this.readNextByte();
202 let alpha = this.readNextByte();
203 let name;
204 if (flag === 1) {
205 name = this.readNextString();
206 }
207 colors.push({
208 red,
209 green,
210 blue,
211 alpha,
212 name: name !== undefined ? name : "none"
213 });
214 }
215 return { paletteSize,
216 firstColor,
217 lastColor: secondColor,
218 colors };
219 }
220 readLayerChunk() {
221 const flags = this.readNextWord();
222 const type = this.readNextWord();
223 const layerChildLevel = this.readNextWord();
224 this.skipBytes(4);
225 const blendMode = this.readNextWord();
226 const opacity = this.readNextByte();
227 this.skipBytes(3);
228 const name = this.readNextString();
229 this.layers.push({ flags,
230 type,
231 layerChildLevel,
232 blendMode,
233 opacity,
234 name});
235 }
236 //size of chunk in bytes for the WHOLE thing
237 readCelChunk(chunkSize) {
238 const layerIndex = this.readNextWord();
239 const x = this.readNextShort();
240 const y = this.readNextShort();
241 const opacity = this.readNextByte();
242 const celType = this.readNextWord();
243 this.skipBytes(7);
244 const w = this.readNextWord();
245 const h = this.readNextWord();
246 const buff = this.readNextRawBytes(chunkSize - 26); //take the first 20 bytes off for the data above and chunk info
247 let rawCel;
248 if(celType === 2) {
249 rawCel = zlib.inflateSync(buff);
250 } else if(celType === 0) {
251 rawCel = buff;
252 }
253 return { layerIndex,
254 xpos: x,
255 ypos: y,
256 opacity,
257 celType,
258 w,
259 h,
260 rawCelData: rawCel}
261 }
262 readChunk() {
263 const cSize = this.readNextDWord();
264 const type = this.readNextWord();
265 return {chunkSize: cSize, type: type};
266 }
267 parse() {
268 const numFrames = this.readHeader();
269 for(let i = 0; i < numFrames; i ++) {
270 this.readFrame();
271 }
272
273 }
274 formatBytes(bytes,decimals) {
275 if (bytes === 0) {
276 return '0 Byte';
277 }
278 const k = 1024;
279 const dm = decimals + 1 || 3;
280 const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
281 const i = Math.floor(Math.log(bytes) / Math.log(k));
282 return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
283 };
284 toJSON() {
285 return {
286 fileSize: this.fileSize,
287 numFrames: this.numFrames,
288 frames: this.frames.map(frame => {
289 return {
290 size: frame.bytesInFrame,
291 duration: frame.frameDuration,
292 chunks: frame.numChunks,
293 cels: frame.cels.map(cel => {
294 return {
295 layerIndex: cel.layerIndex,
296 xpos: cel.xpos,
297 ypos: cel.ypos,
298 opacity: cel.opacity,
299 celType: cel.celType,
300 w: cel.w,
301 h: cel.h,
302 rawCelData: 'buffer'
303 }
304 }) }
305 }),
306 palette: this.palette,
307 width: this.width,
308 height: this.height,
309 colorDepth: this.colorDepth,
310 numColors: this.numColors,
311 pixelRatio: this.pixelRatio,
312 layers: this.layers
313 };
314 }
315}
316
317module.exports = Aseprite;