@city41/gba-convertpng 0.0.34 → 0.0.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,119 @@
1
+ import { Canvas } from "canvas";
2
+ export interface AsepriteHeader {
3
+ fileSize: number;
4
+ imageWidth: number;
5
+ imageHeight: number;
6
+ frameCount: number;
7
+ colorDepth: number;
8
+ }
9
+ export interface AsepriteFrame {
10
+ layers: Array<FrameLayer>;
11
+ duration: number;
12
+ image: Canvas;
13
+ }
14
+ export interface AsepriteLayer {
15
+ layerID: number;
16
+ layerName: string;
17
+ }
18
+ export interface AsepriteTag {
19
+ startIndex: number;
20
+ endIndex: number;
21
+ tagName: string;
22
+ }
23
+ interface FrameLayer {
24
+ layerID: number;
25
+ position: {
26
+ x: number;
27
+ y: number;
28
+ };
29
+ size: {
30
+ w: number;
31
+ h: number;
32
+ };
33
+ imageData: Uint8Array;
34
+ }
35
+ export interface SpriteSheetOptions {
36
+ /**
37
+ * Frames: Array<number>
38
+ */
39
+ frames: Array<number> | "all";
40
+ /**
41
+ *rows: number - number of rows in spritesheet
42
+ */
43
+ rows: number;
44
+ /**
45
+ *cols: number - number of columns in spritesheet
46
+ */
47
+ cols?: number;
48
+ }
49
+ export declare class AsepriteParser {
50
+ filepath: string;
51
+ loaded: boolean;
52
+ header: AsepriteHeader | undefined;
53
+ frames: Array<AsepriteFrame>;
54
+ tags: Array<AsepriteTag>;
55
+ layers: Array<AsepriteLayer>;
56
+ palette: Array<string>;
57
+ constructor(filePath: string);
58
+ /**
59
+ * initialize - requiired call prior to making other calls
60
+ * reads in and parses asepreite or ase file
61
+ * Asynchronous function
62
+ * @returns Promise<boolean>
63
+ */
64
+ initialize(): Promise<boolean>;
65
+ /**
66
+ * getTags() returns the parsed animation tags from the Aseprite file
67
+ * @returns Array<AsepriteTag>
68
+ */
69
+ getTags(): Array<AsepriteTag>;
70
+ /**
71
+ * getPalette - returns the array of colors that are in the aseprite file
72
+ * @returns Array<string>
73
+ */
74
+ getPalette(): Array<string>;
75
+ /**
76
+ * getTaggedAnimation - finds the animation tag from aseprite and uses the frame indexes associated
77
+ * to return either the spritesheet or an array of images associated with that tag, throws error
78
+ * if it cannot find that tag
79
+ * Asynchronous function
80
+ * @param {string} tag - the string text that is listed in the aseprite file for a collection of frames
81
+ * @param {boolean} split - the boolean flag to return a spritesheet (false), or an array of images (true)
82
+ * @returns {Canvas|Array<Canvas>}
83
+ */
84
+ getTaggedAnimation(tag: string, split?: boolean): Promise<Array<Canvas> | Canvas>;
85
+ /**
86
+ * getFrames - returns specific frame content as spritesheet or array of images
87
+ * Asynchronous function
88
+ * @param {number} from - starting index for retrieving image frames
89
+ * @param {number} to - ending index for retrieving image frames
90
+ * @param {boolean} split - the boolean flag to return a spritesheet (false), or an array of images (true)
91
+ * @returns {HTMLImageElement|Array<HTMLImageElement>}
92
+ */
93
+ getFrames(from: number, to: number, split?: boolean): Promise<Array<Canvas> | Canvas>;
94
+ /**
95
+ * getSpriteSheet - returns a spritesheet based on options parameters
96
+ * Asynchronous function
97
+ * @param {SpriteSheetOptions} options - frames, rows, cols
98
+ * @returns {HTMLImageElement}
99
+ */
100
+ getSpriteSheet(options: SpriteSheetOptions): Promise<Canvas>;
101
+ /**
102
+ * getImage - pulls the image element for given frame
103
+ * @param {number} frame - number representing the index of the frame to pull image from
104
+ * @returns {HTMLIFrameElement}
105
+ */
106
+ getImage(frame: number): Canvas | undefined;
107
+ private _parseHeader;
108
+ private _parseFrames;
109
+ private _convertRGBAtoHexSTring;
110
+ private _makeSpriteSheet;
111
+ private _readByteString;
112
+ private _getBytes;
113
+ private _readLayersChunk;
114
+ private _readTagsChunk;
115
+ private _readPalletChunk;
116
+ private _readCelChunk;
117
+ private _loadCheck;
118
+ }
119
+ export {};
@@ -0,0 +1,443 @@
1
+ "use strict";
2
+ /* Aseprite-parser.ts - 6/30/2023 - version 1.0.8
3
+ Author: Justin Young
4
+ https://github.com/jyoung4242/aseprite-parser
5
+
6
+ exported types:
7
+ AsepriteHeader, AsepriteFrame, AspriteTag, SpriteSheetOptions
8
+
9
+ exported class AsepriteModule{}
10
+
11
+ constructor
12
+ @params -> either the relative path to an *.Aseprite or *.ase file, or passing the file itself
13
+
14
+ usage:
15
+ const myAsepriteFile = new AsepriteModule(myasefile);
16
+ await myAsepriteFile.initialize()
17
+
18
+ ... then you can call one of several methods
19
+ getTags(), getPalette(), getTaggedAnimation(), getFrames(), getSpriteSheet(), getImage()
20
+
21
+ */
22
+ var __importDefault = (this && this.__importDefault) || function (mod) {
23
+ return (mod && mod.__esModule) ? mod : { "default": mod };
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.AsepriteParser = void 0;
27
+ const pako_1 = require("pako");
28
+ const canvas_1 = require("canvas");
29
+ const promises_1 = __importDefault(require("node:fs/promises"));
30
+ var chunktype;
31
+ (function (chunktype) {
32
+ chunktype[chunktype["OldPalletChunk1"] = 4] = "OldPalletChunk1";
33
+ chunktype[chunktype["OldPalletChunk2"] = 17] = "OldPalletChunk2";
34
+ chunktype[chunktype["LayerChunk"] = 8196] = "LayerChunk";
35
+ chunktype[chunktype["CelChunk"] = 8197] = "CelChunk";
36
+ chunktype[chunktype["CelExtraChunk"] = 8198] = "CelExtraChunk";
37
+ chunktype[chunktype["ColorProfileChunk"] = 8199] = "ColorProfileChunk";
38
+ chunktype[chunktype["ExternalFilesChunk"] = 8200] = "ExternalFilesChunk";
39
+ chunktype[chunktype["MaskChunk"] = 8214] = "MaskChunk";
40
+ chunktype[chunktype["TagsChunk"] = 8216] = "TagsChunk";
41
+ chunktype[chunktype["PalletChunk"] = 8217] = "PalletChunk";
42
+ chunktype[chunktype["UserDataChunk"] = 8224] = "UserDataChunk";
43
+ chunktype[chunktype["SliceChunk"] = 8226] = "SliceChunk";
44
+ chunktype[chunktype["TileSetChunk"] = 8227] = "TileSetChunk";
45
+ })(chunktype || (chunktype = {}));
46
+ class AsepriteParser {
47
+ /*Public Methods */
48
+ constructor(filePath) {
49
+ //Properties
50
+ this.filepath = "";
51
+ this.loaded = false;
52
+ this._parseFrames = async (fileData) => {
53
+ return new Promise(async (resolve, reject) => {
54
+ // let numBytesinFrame = 0;
55
+ // let magicWord = 0;
56
+ let oldChunks, newChunks, numChunks;
57
+ let frameDuration = 0;
58
+ let newPalletChunk = false;
59
+ let framelayers = [];
60
+ //remove Aseprite Header
61
+ const frameBytes = fileData.slice(128);
62
+ let fileCursor = 0;
63
+ const tempFrames = [];
64
+ for (let frameIndex = 0; frameIndex < this.header.frameCount; frameIndex++) {
65
+ // numBytesinFrame = new DataView(frameBytes.buffer).getUint32(
66
+ // fileCursor,
67
+ // true,
68
+ // );
69
+ // magicWord = new DataView(frameBytes.buffer).getUint16(
70
+ // fileCursor + 4,
71
+ // true,
72
+ // );
73
+ oldChunks = new DataView(frameBytes.buffer).getUint16(fileCursor + 6, true);
74
+ frameDuration = new DataView(frameBytes.buffer).getUint16(fileCursor + 8, true);
75
+ newChunks = new DataView(frameBytes.buffer).getUint32(fileCursor + 12, true);
76
+ numChunks = newChunks === 0 ? oldChunks : newChunks;
77
+ fileCursor += 16;
78
+ //iterate over chunks
79
+ framelayers = [];
80
+ for (let chunkIndex = 0; chunkIndex < numChunks; chunkIndex++) {
81
+ //Chunk Parsing
82
+ let chunkSize = new DataView(frameBytes.buffer).getUint32(fileCursor, true);
83
+ const chunkStartIndex = fileCursor;
84
+ let chunkType = new DataView(frameBytes.buffer).getUint16(fileCursor + 4, true);
85
+ switch (chunkType) {
86
+ case chunktype.OldPalletChunk1:
87
+ if (newPalletChunk)
88
+ break;
89
+ break;
90
+ case chunktype.OldPalletChunk2:
91
+ if (newPalletChunk)
92
+ break;
93
+ break;
94
+ case chunktype.LayerChunk:
95
+ this._readLayersChunk(fileCursor, frameBytes.buffer);
96
+ break;
97
+ case chunktype.CelChunk:
98
+ let frameLayer = this._readCelChunk(fileCursor, frameBytes, chunkSize, chunkStartIndex);
99
+ framelayers.push(frameLayer);
100
+ break;
101
+ case chunktype.CelExtraChunk:
102
+ break;
103
+ case chunktype.ColorProfileChunk:
104
+ break;
105
+ case chunktype.ExternalFilesChunk:
106
+ break;
107
+ case chunktype.MaskChunk:
108
+ break;
109
+ case chunktype.TagsChunk:
110
+ this._readTagsChunk(fileCursor, frameBytes);
111
+ break;
112
+ case chunktype.PalletChunk:
113
+ this._readPalletChunk(fileCursor, frameBytes);
114
+ break;
115
+ case chunktype.UserDataChunk:
116
+ break;
117
+ case chunktype.SliceChunk:
118
+ break;
119
+ case chunktype.TileSetChunk:
120
+ break;
121
+ }
122
+ fileCursor += chunkSize;
123
+ //Last Chunk, build out frame data
124
+ if (chunkIndex == numChunks - 1) {
125
+ let myCanvas = (0, canvas_1.createCanvas)(this.header?.imageWidth, this.header?.imageHeight);
126
+ let ctx = myCanvas.getContext("2d");
127
+ ctx?.clearRect(0, 0, this.header?.imageWidth, this.header?.imageHeight);
128
+ //build out frame entry in array
129
+ framelayers.forEach((frame) => {
130
+ let myClampedArray = new Uint8ClampedArray(frame?.imageData);
131
+ const newImageData = new canvas_1.ImageData(myClampedArray, frame?.size.w, frame?.size.h);
132
+ let tempCanvas = (0, canvas_1.createCanvas)(frame.size.w, frame.size.h);
133
+ let tempctx = tempCanvas.getContext("2d");
134
+ tempctx?.putImageData(newImageData, 0, 0);
135
+ ctx?.drawImage(tempCanvas, frame.position.x, frame.position.y, frame.size.w, frame.size.h);
136
+ });
137
+ tempFrames.push({
138
+ layers: framelayers,
139
+ duration: frameDuration,
140
+ image: myCanvas,
141
+ });
142
+ }
143
+ }
144
+ }
145
+ resolve(tempFrames);
146
+ });
147
+ };
148
+ this.filepath = filePath;
149
+ this.tags = [];
150
+ this.palette = [];
151
+ this.layers = [];
152
+ this.frames = [];
153
+ }
154
+ /**
155
+ * initialize - requiired call prior to making other calls
156
+ * reads in and parses asepreite or ase file
157
+ * Asynchronous function
158
+ * @returns Promise<boolean>
159
+ */
160
+ async initialize() {
161
+ const fileData = Uint8Array.from(await promises_1.default.readFile(this.filepath));
162
+ this.header = await this._parseHeader(fileData);
163
+ this.frames = await this._parseFrames(fileData);
164
+ if (!this.header || !this.frames) {
165
+ return false;
166
+ }
167
+ if (this.header.fileSize != 0 &&
168
+ this.frames.length != 0) {
169
+ this.loaded = true;
170
+ }
171
+ return true;
172
+ }
173
+ /**
174
+ * getTags() returns the parsed animation tags from the Aseprite file
175
+ * @returns Array<AsepriteTag>
176
+ */
177
+ getTags() {
178
+ if (!this.loaded)
179
+ throw new Error("Aseprite file not loaded");
180
+ return this.tags;
181
+ }
182
+ /**
183
+ * getPalette - returns the array of colors that are in the aseprite file
184
+ * @returns Array<string>
185
+ */
186
+ getPalette() {
187
+ if (!this.loaded)
188
+ throw new Error("Aseprite file not loaded");
189
+ return this.palette;
190
+ }
191
+ /**
192
+ * getTaggedAnimation - finds the animation tag from aseprite and uses the frame indexes associated
193
+ * to return either the spritesheet or an array of images associated with that tag, throws error
194
+ * if it cannot find that tag
195
+ * Asynchronous function
196
+ * @param {string} tag - the string text that is listed in the aseprite file for a collection of frames
197
+ * @param {boolean} split - the boolean flag to return a spritesheet (false), or an array of images (true)
198
+ * @returns {Canvas|Array<Canvas>}
199
+ */
200
+ async getTaggedAnimation(tag, split = true) {
201
+ if (!this.loaded)
202
+ throw new Error("Aseprite file not loaded");
203
+ //find tag
204
+ const foundTag = this.tags.findIndex((tagstring) => tag == tagstring.tagName);
205
+ if (foundTag == -1)
206
+ throw new Error("tagname not found");
207
+ //tagindex found
208
+ const result = await this.getFrames(this.tags[foundTag].startIndex, this.tags[foundTag].endIndex, split);
209
+ return result;
210
+ }
211
+ /**
212
+ * getFrames - returns specific frame content as spritesheet or array of images
213
+ * Asynchronous function
214
+ * @param {number} from - starting index for retrieving image frames
215
+ * @param {number} to - ending index for retrieving image frames
216
+ * @param {boolean} split - the boolean flag to return a spritesheet (false), or an array of images (true)
217
+ * @returns {HTMLImageElement|Array<HTMLImageElement>}
218
+ */
219
+ async getFrames(from, to, split = true) {
220
+ if (!this.loaded)
221
+ throw new Error("Aseprite file not loaded");
222
+ if (split) {
223
+ let tempArray = [];
224
+ for (let index = from; index <= to; index++) {
225
+ tempArray.push(this.frames[index].image);
226
+ }
227
+ return tempArray;
228
+ }
229
+ else {
230
+ let tempArray = [];
231
+ //console.log(from, to);
232
+ for (let index = from; index <= to; index++) {
233
+ tempArray.push(index);
234
+ }
235
+ //console.log(tempArray);
236
+ const tempImage = await this._makeSpriteSheet(tempArray, 1, tempArray.length);
237
+ return tempImage;
238
+ }
239
+ }
240
+ /**
241
+ * getSpriteSheet - returns a spritesheet based on options parameters
242
+ * Asynchronous function
243
+ * @param {SpriteSheetOptions} options - frames, rows, cols
244
+ * @returns {HTMLImageElement}
245
+ */
246
+ async getSpriteSheet(options) {
247
+ if (!this.loaded)
248
+ throw new Error("Aseprite file not loaded");
249
+ const tempImage = await this._makeSpriteSheet(options.frames, options.rows, options.cols);
250
+ return tempImage;
251
+ }
252
+ /**
253
+ * getImage - pulls the image element for given frame
254
+ * @param {number} frame - number representing the index of the frame to pull image from
255
+ * @returns {HTMLIFrameElement}
256
+ */
257
+ getImage(frame) {
258
+ this._loadCheck();
259
+ if (!this.frames)
260
+ return undefined;
261
+ return this.frames[frame].image;
262
+ }
263
+ /*Private Methods */
264
+ async _parseHeader(fileData) {
265
+ return new Promise((resolve, reject) => {
266
+ //isolate Aseprite Header
267
+ const headerBytes = fileData.slice(0, 128);
268
+ // Parse the header fields
269
+ const fileSize = new DataView(headerBytes.buffer).getUint32(0, true);
270
+ const frameCount = new DataView(headerBytes.buffer).getUint16(6, true);
271
+ const imageWidth = new DataView(headerBytes.buffer).getUint16(8, true);
272
+ const imageHeight = new DataView(headerBytes.buffer).getUint16(10, true);
273
+ const colorDepth = new DataView(headerBytes.buffer).getUint16(12, true);
274
+ resolve({ fileSize, imageWidth, imageHeight, colorDepth, frameCount });
275
+ });
276
+ }
277
+ _convertRGBAtoHexSTring(color) {
278
+ function padTo2(str) {
279
+ return str.padStart(2, "0");
280
+ }
281
+ const hexR = padTo2(color.r.toString(16));
282
+ const hexG = padTo2(color.g.toString(16));
283
+ const hexB = padTo2(color.b.toString(16));
284
+ const hexA = padTo2(color.a.toString(16));
285
+ return `#${hexR}${hexG}${hexB}${hexA}`;
286
+ }
287
+ async _makeSpriteSheet(frames, rows, cols) {
288
+ if (frames === "all") {
289
+ cols = this.header?.frameCount;
290
+ }
291
+ if (!cols) {
292
+ throw new Error("_makeSpriteSheet: cols not specified");
293
+ }
294
+ if (!this.loaded)
295
+ throw new Error("Aseprite file not loaded");
296
+ let tempFrames = [];
297
+ let rowIndex = 0;
298
+ let colIndex = 0;
299
+ let ssWidth = 0;
300
+ let ssHeight = 0;
301
+ if (this.header) {
302
+ ssWidth = this.header.imageWidth * cols;
303
+ ssHeight = this.header.imageHeight * rows;
304
+ }
305
+ let tempCanvas = (0, canvas_1.createCanvas)(ssWidth, ssHeight);
306
+ let tempCtx = tempCanvas.getContext("2d");
307
+ let imageIndex = 0;
308
+ if (frames === "all") {
309
+ //console.log(this.header?.frameCount);
310
+ if (this.header)
311
+ for (let index = 0; index < this.header?.frameCount; index++) {
312
+ //console.log("loop index: ", index);
313
+ tempFrames.push(index);
314
+ }
315
+ }
316
+ else {
317
+ tempFrames = [...frames];
318
+ }
319
+ //console.log(tempFrames);
320
+ tempFrames.forEach((frame) => {
321
+ colIndex = imageIndex % cols;
322
+ rowIndex = Math.floor(imageIndex / cols);
323
+ let drawX, drawY;
324
+ if (this.header)
325
+ drawX = colIndex * this.header?.imageWidth;
326
+ if (this.header)
327
+ drawY = rowIndex * this.header?.imageHeight;
328
+ //console.log(rowIndex, colIndex, drawX, drawY);
329
+ tempCtx?.drawImage(this.frames[frame].image, drawX, drawY);
330
+ imageIndex++;
331
+ });
332
+ return tempCanvas;
333
+ }
334
+ _readByteString(frameBuffer, startingIndex, length) {
335
+ const myStringArray = new Uint8Array(length);
336
+ for (let i = 0; i < myStringArray.length; i++) {
337
+ myStringArray[i] = new DataView(frameBuffer).getUint8(startingIndex + i);
338
+ }
339
+ let decoder = new TextDecoder();
340
+ return decoder.decode(myStringArray);
341
+ }
342
+ _getBytes(n, start, buffer) {
343
+ return new Uint8Array(buffer.slice(start, start + n));
344
+ }
345
+ _readLayersChunk(cursor, buffer) {
346
+ let layerCursor = cursor + 6;
347
+ let layerNameLength = new DataView(buffer).getInt16(layerCursor + 16, true);
348
+ layerCursor += 18;
349
+ let layerName = this._readByteString(buffer, layerCursor, layerNameLength);
350
+ let layerID = this.layers?.length;
351
+ this.layers?.push({ layerID: layerID, layerName: layerName });
352
+ }
353
+ _readTagsChunk(cursor, parentbuffer) {
354
+ let tagsChunkOffsetCursor = cursor + 6;
355
+ let buffer = parentbuffer.buffer;
356
+ let numTags = new DataView(buffer).getUint16(tagsChunkOffsetCursor, true);
357
+ tagsChunkOffsetCursor += 10;
358
+ for (let index = 0; index < numTags; index++) {
359
+ let fromIndex = new DataView(buffer).getUint16(tagsChunkOffsetCursor, true);
360
+ let toIndex = new DataView(buffer).getUint16(tagsChunkOffsetCursor + 2, true);
361
+ tagsChunkOffsetCursor += 17;
362
+ let nameLength = new DataView(buffer).getUint16(tagsChunkOffsetCursor, true);
363
+ tagsChunkOffsetCursor += 2;
364
+ let tagName = this._readByteString(parentbuffer.buffer, tagsChunkOffsetCursor, nameLength);
365
+ tagsChunkOffsetCursor += nameLength;
366
+ this.tags?.push({
367
+ startIndex: fromIndex,
368
+ endIndex: toIndex,
369
+ tagName: tagName,
370
+ });
371
+ }
372
+ }
373
+ _readPalletChunk(cursor, parentbuffer) {
374
+ let palletCursor = cursor + 6;
375
+ let buffer = parentbuffer.buffer;
376
+ let newPalletSize = new DataView(buffer).getUint32(palletCursor, true);
377
+ palletCursor += 20;
378
+ for (let index = 0; index < newPalletSize; index++) {
379
+ let red = new DataView(buffer).getUint8(palletCursor + 2);
380
+ let green = new DataView(buffer).getUint8(palletCursor + 3);
381
+ let blue = new DataView(buffer).getUint8(palletCursor + 4);
382
+ let alpha = new DataView(buffer).getUint8(palletCursor + 5);
383
+ let colorstring = this._convertRGBAtoHexSTring({
384
+ r: red,
385
+ g: green,
386
+ b: blue,
387
+ a: alpha,
388
+ });
389
+ this.palette?.push(colorstring);
390
+ }
391
+ }
392
+ _readCelChunk(cursor, parentbuffer, size, startIndex) {
393
+ let celCursor = cursor + 6;
394
+ let buffer = parentbuffer.buffer;
395
+ // let framelayers = [];
396
+ let layer = new DataView(buffer).getUint16(celCursor, true);
397
+ let xpos = new DataView(buffer).getInt16(celCursor + 2, true);
398
+ let ypos = new DataView(buffer).getInt16(celCursor + 4, true);
399
+ // let opacity = new DataView(buffer).getUint8(celCursor + 6);
400
+ let celType = new DataView(buffer).getUint16(celCursor + 7, true);
401
+ // let zindex = new DataView(buffer).getInt16(celCursor + 9, true);
402
+ let pixelWidth, pixelHeight;
403
+ celCursor += 16;
404
+ switch (celType) {
405
+ case 0:
406
+ //raw image
407
+ break;
408
+ case 1:
409
+ //linked cell
410
+ break;
411
+ case 2:
412
+ //compressed image
413
+ pixelWidth = new DataView(buffer).getUint16(celCursor, true);
414
+ pixelHeight = new DataView(buffer).getUint16(celCursor + 2, true);
415
+ celCursor += 4;
416
+ const bytesToRead = size - (celCursor - startIndex);
417
+ let compressedArray = this._getBytes(bytesToRead, celCursor, parentbuffer);
418
+ let decompressedArray;
419
+ try {
420
+ decompressedArray = (0, pako_1.inflate)(compressedArray);
421
+ }
422
+ catch (error) {
423
+ throw new Error("Error unpacking compressed Image");
424
+ }
425
+ return {
426
+ layerID: layer,
427
+ position: { x: xpos, y: ypos },
428
+ size: { w: pixelWidth, h: pixelHeight },
429
+ imageData: decompressedArray,
430
+ };
431
+ break;
432
+ case 3:
433
+ //compressed tilemap
434
+ break;
435
+ }
436
+ }
437
+ _loadCheck() {
438
+ if (!this.loaded)
439
+ throw new Error("Aseprite file not loaded");
440
+ }
441
+ }
442
+ exports.AsepriteParser = AsepriteParser;
443
+ //# sourceMappingURL=AsepriteParser.js.map
package/dist/main.js CHANGED
@@ -132,7 +132,7 @@ function getTileDefines(result, file) {
132
132
  let frameCountSrc = "";
133
133
  let frameCount = 1;
134
134
  if (result.type == "BasicSprite") {
135
- frameCount = result.spec.frames;
135
+ frameCount = result.spec.frames ?? 1;
136
136
  frameCountSrc = `\n#define ${name.toUpperCase()}_FRAME_COUNT ${frameCount}`;
137
137
  }
138
138
  let singleFrameTileWidth = allFrameTileWidth / frameCount;
package/dist/sprite.js CHANGED
@@ -1,25 +1,51 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.isBasicSpriteSpec = isBasicSpriteSpec;
4
7
  exports.isSharedPaletteSpriteSpec = isSharedPaletteSpriteSpec;
5
8
  exports.processBasicSprite = processBasicSprite;
6
9
  exports.processSharedPaletteSprites = processSharedPaletteSprites;
10
+ const promises_1 = __importDefault(require("node:fs/promises"));
7
11
  const canvas_1 = require("./canvas");
8
12
  const palette_1 = require("./palette");
9
13
  const tile_1 = require("./tile");
14
+ const AsepriteParser_1 = require("./AsepriteParser");
10
15
  function isSharedPaletteSpriteSpec(obj) {
11
16
  return typeof obj === "object" && obj !== null && "name" in obj;
12
17
  }
13
18
  function isBasicSpriteSpec(sprite) {
14
19
  return "file" in sprite;
15
20
  }
21
+ async function processBasicAseprite(sprite, forcedPaletteOverride) {
22
+ const parser = new AsepriteParser_1.AsepriteParser(sprite.file);
23
+ await parser.initialize();
24
+ const spriteSheetCanvas = await parser.getSpriteSheet({
25
+ rows: 1,
26
+ frames: "all",
27
+ });
28
+ const spriteSheetBuffer = spriteSheetCanvas.toBuffer();
29
+ const newFilePath = sprite.file.replace(".aseprite", ".png");
30
+ await promises_1.default.writeFile(newFilePath, spriteSheetBuffer);
31
+ const processBasicResult = await processBasicSprite({
32
+ ...sprite,
33
+ file: newFilePath,
34
+ frames: parser.frames.length,
35
+ }, forcedPaletteOverride);
36
+ return {
37
+ ...processBasicResult,
38
+ frameDurations: parser.frames.map((f) => f.duration),
39
+ };
40
+ }
16
41
  async function processBasicSprite(sprite, forcedPaletteOverride) {
17
- if (sprite.frames === undefined || sprite.frames === 0) {
18
- throw new Error(`sprite, ${sprite.file}, has no frames defined`);
42
+ if (sprite.file.toLowerCase().endsWith(".aseprite")) {
43
+ return processBasicAseprite(sprite, forcedPaletteOverride);
19
44
  }
20
45
  let canvas = await (0, canvas_1.reduceColors)(await (0, canvas_1.createCanvasFromPath)(sprite.file), 16);
46
+ const frames = sprite.frames ?? 1;
21
47
  // before rounding up the canvas, grab its actual pixel size
22
- const framePixelWidth = Math.ceil(canvas.width / sprite.frames);
48
+ const framePixelWidth = Math.ceil(canvas.width / 1);
23
49
  const framePixelHeight = canvas.height;
24
50
  canvas = (0, canvas_1.roundUpToTileSize)(canvas);
25
51
  let palette;
@@ -32,7 +58,7 @@ async function processBasicSprite(sprite, forcedPaletteOverride) {
32
58
  else {
33
59
  palette = (0, palette_1.extractPalette)(canvas, !sprite.trimPalette);
34
60
  }
35
- const tiles = (0, tile_1.extractTiles)(canvas, palette, sprite.frames).flat(1);
61
+ const tiles = (0, tile_1.extractTiles)(canvas, palette, 1).flat(1);
36
62
  if (typeof sprite.transparentColor === "number") {
37
63
  palette[0] = sprite.transparentColor;
38
64
  }
@@ -44,6 +70,7 @@ async function processBasicSprite(sprite, forcedPaletteOverride) {
44
70
  palette,
45
71
  framePixelWidth,
46
72
  framePixelHeight,
73
+ frameDurations: new Array(frames).fill(100),
47
74
  };
48
75
  }
49
76
  async function processSharedPaletteSprites(sharedPaletteSprite) {
package/dist/types.d.ts CHANGED
@@ -3,7 +3,7 @@ export type Format = "C" | "C.inc" | "z80" | "pyz80" | "asz80" | "bin";
3
3
  export type DataWidth = "b" | "w" | "dw";
4
4
  export type BasicSpriteSpec = {
5
5
  file: string;
6
- frames: number;
6
+ frames?: number;
7
7
  trimPalette?: boolean;
8
8
  forcePalette?: string;
9
9
  transparentColor?: number;
@@ -62,6 +62,7 @@ export type ProcessBasicSpriteResult = {
62
62
  canvas: Canvas;
63
63
  framePixelWidth: number;
64
64
  framePixelHeight: number;
65
+ frameDurations: number[];
65
66
  tiles: number[];
66
67
  palette?: number[];
67
68
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@city41/gba-convertpng",
3
- "version": "0.0.34",
3
+ "version": "0.0.35",
4
4
  "description": "Converts png images to GBA tile format",
5
5
  "main": "index.js",
6
6
  "repository": "github.com/city41/gba-convertpng",
@@ -27,7 +27,8 @@
27
27
  "imagemagick": "^0.1.3",
28
28
  "lodash": "^4.17.23",
29
29
  "mkdirp": "^3.0.1",
30
- "nearest-color": "^0.4.4"
30
+ "nearest-color": "^0.4.4",
31
+ "pako": "^3.0.2"
31
32
  },
32
33
  "devDependencies": {
33
34
  "@types/node": "^24.9.1",