UNPKG

ase-parser-slices

Version:
373 lines (369 loc) 9.91 kB
const zlib = require('zlib') class Aseprite { constructor (buffer, name) { this._offset = 0 this._buffer = buffer this.frames = [] this.layers = [] this.slices = [] this.fileSize this.numFrames this.width this.height this.colorDepth this.paletteIndex this.numColors this.pixelRatio this.name = name this.tags = [] this.lastObject = null } readNextByte() { const nextByte = this._buffer.readUInt8(this._offset) this._offset += 1 return nextByte } readByte(offset) { return this._buffer.readUInt8(offset) } readNextWord() { const word = this._buffer.readUInt16LE(this._offset) this._offset += 2 return word } readWord(offset) { return this._buffer.readUInt16LE(offset) } readNextShort() { const short = this._buffer.readInt16LE(this._offset) this._offset += 2 return short } readShort(offset) { return this._buffer.readInt16LE(offset) } readNextDWord() { const dWord = this._buffer.readUInt32LE(this._offset) this._offset += 4 return dWord } readDWord(offset) { return this._buffer.readUInt32LE(offset) } readNextLong() { const long = this._buffer.readInt32LE(this._offset) this._offset += 4 return long } readLong(offset) { return this._buffer.readInt32LE(offset) } readNextFixed() { const fixed = this._buffer.readFloatLE(this._offset) this._offset += 4 return fixed } readFixed(offset) { return this._buffer.readFloatLE(offset) } readNextBytes(numBytes) { let strBuff = Buffer.alloc(numBytes) for (let i = 0; i < numBytes; i++) { strBuff.writeUInt8(this.readNextByte(), i) } return strBuff.toString() } readNextRawBytes(numBytes) { let buff = Buffer.alloc(numBytes) for (let i = 0; i < numBytes; i++) { buff.writeUInt8(this.readNextByte(), i) } return buff } //reads numBytes bytes of buffer b offset by offset bytes readRawBytes(numBytes, b, offset) { let buff = Buffer.alloc(numBytes - offset) for (let i = 0; i < numBytes - offset; i++) { buff.writeUInt8(b.readUInt8(offset + i), i) } return buff } readNextString() { const numBytes = this.readNextWord() return this.readNextBytes(numBytes) } skipBytes(numBytes) { this._offset += numBytes } readHeader() { this.fileSize = this.readNextDWord() this.readNextWord() this.numFrames = this.readNextWord() this.width = this.readNextWord() this.height = this.readNextWord() this.colorDepth = this.readNextWord() this.skipBytes(14) this.paletteIndex = this.readNextByte() this.skipBytes(3) this.numColors = this.readNextWord() const pixW = this.readNextByte() const pixH = this.readNextByte() this.pixelRatio = `${pixW}:${pixH}` this.skipBytes(92) return this.numFrames } readFrame() { const bytesInFrame = this.readNextDWord() this.skipBytes(2) const oldChunk = this.readNextWord() const frameDuration = this.readNextWord() this.skipBytes(2) const newChunk = this.readNextDWord() let cels = [] for (let i = 0; i < newChunk; i++) { let chunkData = this.readChunk() switch (chunkData.type) { case 0x0004: case 0x0011: case 0x2016: case 0x2017: this.skipBytes(chunkData.chunkSize - 6) break case 0x2020: this.readUserDataChunk() break case 0x2022: this.readSliceChunk() break case 0x2004: this.readLayerChunk() break case 0x2005: let celData = this.readCelChunk(chunkData.chunkSize) cels.push(celData) break case 0x2007: this.readColorProfileChunk() break case 0x2018: this.readFrameTagsChunk() break case 0x2019: this.palette = this.readPaletteChunk() break } } this.frames.push({ bytesInFrame, frameDuration, numChunks: newChunk, cels }) } readColorProfileChunk() { const types = [ 'None', 'sRGB', 'ICC' ] const typeInd = this.readNextWord() const type = types[typeInd] const flag = this.readNextWord() const fGamma = this.readNextFixed() this.skipBytes(8) //handle ICC profile data this.colorProfile = { type, flag, fGamma } } readFrameTagsChunk() { const loops = [ 'Forward', 'Reverse', 'Ping-pong' ] const numTags = this.readNextWord() this.skipBytes(8) for (let i = 0; i < numTags; i++) { let tag = {} tag.from = this.readNextWord() tag.to = this.readNextWord() const loopsInd = this.readNextByte() tag.animDirection = loops[loopsInd] this.skipBytes(8) tag.color = this.readNextRawBytes(3).toString('hex') this.skipBytes(1) tag.name = this.readNextString() this.tags.push(tag) } } readPaletteChunk() { const paletteSize = this.readNextDWord() const firstColor = this.readNextDWord() const secondColor = this.readNextDWord() this.skipBytes(8) let colors = [] for (let i = 0; i < paletteSize; i++) { let flag = this.readNextWord() let red = this.readNextByte() let green = this.readNextByte() let blue = this.readNextByte() let alpha = this.readNextByte() let name if (flag === 1) { name = this.readNextString() } colors.push({ red, green, blue, alpha, name: name !== undefined ? name : "none" }) } let palette = { paletteSize, firstColor, lastColor: secondColor, colors } this.colorDepth === 8 ? palette.index = this.paletteIndex : '' return palette } readUserDataChunk() { const flags = this.readNextDWord() const text = (flags & 1) !== 0 ? this.readNextString() : null const color = (flags & 2) !== 0 ? this.readNextRawBytes(4).toString('hex') : null if (this.lastObject) { this.lastObject.userData = { text, color } } } readSliceChunk() { const numSliceKeys = this.readNextDWord() const flags = this.readNextDWord() const patch = (flags & 1) !== 0 ? {} : null const pivot = (flags & 2) !== 0 ? {} : null this.skipBytes(4) const name = this.readNextString() const keys = [] for (let i = 0; i < numSliceKeys; i++) { const frameNumber = this.readNextDWord() const x = this.readNextLong() const y = this.readNextLong() const width = this.readNextDWord() const height = this.readNextDWord() if (patch) { patch.x = this.readNextLong() patch.y = this.readNextLong() patch.width = this.readNextDWord() patch.height = this.readNextDWord() } if (pivot) { pivot.x = this.readNextLong() pivot.y = this.readNextLong() } keys.push({ frameNumber, x, y, width, height, patch, pivot }) } const slice = { flags, name, keys } this.lastObject = slice this.slices.push(slice) } readLayerChunk() { const flags = this.readNextWord() const type = this.readNextWord() const layerChildLevel = this.readNextWord() this.skipBytes(4) const blendMode = this.readNextWord() const opacity = this.readNextByte() this.skipBytes(3) const name = this.readNextString() const layer = { flags, type, layerChildLevel, blendMode, opacity, name } this.lastObject = layer this.layers.push(layer) } //size of chunk in bytes for the WHOLE thing readCelChunk(chunkSize) { const layerIndex = this.readNextWord() const x = this.readNextShort() const y = this.readNextShort() const opacity = this.readNextByte() const celType = this.readNextWord() this.skipBytes(7) const w = this.readNextWord() const h = this.readNextWord() const buff = this.readNextRawBytes(chunkSize - 26) //take the first 20 bytes off for the data above and chunk info let rawCel if (celType === 2) { rawCel = zlib.inflateSync(buff) } else if (celType === 0) { rawCel = buff } return { layerIndex, xpos: x, ypos: y, opacity, celType, w, h, rawCelData: rawCel } } readChunk() { const cSize = this.readNextDWord() const type = this.readNextWord() return { chunkSize: cSize, type: type } } parse() { const numFrames = this.readHeader() for (let i = 0; i < numFrames; i++) { this.readFrame() } } formatBytes(bytes, decimals) { if (bytes === 0) { return '0 Byte' } const k = 1024 const dm = decimals + 1 || 3 const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] const i = Math.floor(Math.log(bytes) / Math.log(k)) return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i] }; toJSON() { return { fileSize: this.fileSize, numFrames: this.numFrames, frames: this.frames.map(frame => { return { size: frame.bytesInFrame, duration: frame.frameDuration, chunks: frame.numChunks, cels: frame.cels.map(cel => { return { layerIndex: cel.layerIndex, xpos: cel.xpos, ypos: cel.ypos, opacity: cel.opacity, celType: cel.celType, w: cel.w, h: cel.h, rawCelData: 'buffer' } }) } }), palette: this.palette, width: this.width, height: this.height, colorDepth: this.colorDepth, numColors: this.numColors, pixelRatio: this.pixelRatio, layers: this.layers, slices: this.slices } } } module.exports = Aseprite