@city41/gba-convertpng 0.0.33 → 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.
- package/dist/AsepriteParser.d.ts +119 -0
- package/dist/AsepriteParser.js +443 -0
- package/dist/background.js +27 -4
- package/dist/main.js +1 -1
- package/dist/sprite.js +31 -4
- package/dist/types.d.ts +2 -1
- package/package.json +3 -2
|
@@ -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/background.js
CHANGED
|
@@ -57,10 +57,33 @@ function buildMap(tiles, bgWidthPx, bgHeightPx) {
|
|
|
57
57
|
const map = [];
|
|
58
58
|
const bgWidthT = bgWidthPx / 8;
|
|
59
59
|
const bgHeightT = bgHeightPx / 8;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
if (bgWidthT % 32 !== 0) {
|
|
61
|
+
throw new Error(`buildMap: bg is of unexpected tile width: ${bgWidthT}`);
|
|
62
|
+
}
|
|
63
|
+
if (bgHeightT % 32 !== 0) {
|
|
64
|
+
throw new Error(`buildMap: bg is of unexpected tile height: ${bgHeightT}`);
|
|
65
|
+
}
|
|
66
|
+
// when backgrounds are 64 tiles high/wide
|
|
67
|
+
// then the map gets divided into quadrants,
|
|
68
|
+
// the quads are laid out colunm first
|
|
69
|
+
//
|
|
70
|
+
// [1][2]
|
|
71
|
+
//
|
|
72
|
+
// [1]
|
|
73
|
+
// [2]
|
|
74
|
+
//
|
|
75
|
+
// [1][2]
|
|
76
|
+
// [3][4]
|
|
77
|
+
const quadranteColumns = bgWidthT / 32;
|
|
78
|
+
const quadranteRows = bgHeightT / 32;
|
|
79
|
+
for (let qc = 0; qc < quadranteColumns; ++qc) {
|
|
80
|
+
for (let qy = 0; qy < quadranteRows; ++qy) {
|
|
81
|
+
for (let y = qy * 32; y < (qy + 1) * 32; ++y) {
|
|
82
|
+
for (let x = qc * 32; x < (qc + 1) * 32; ++x) {
|
|
83
|
+
const tile = tiles[y * bgWidthT + x];
|
|
84
|
+
map.push((tile.paletteIndex << 12) | tile.tileIndex);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
64
87
|
}
|
|
65
88
|
}
|
|
66
89
|
return 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.
|
|
18
|
-
|
|
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 /
|
|
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,
|
|
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
|
|
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.
|
|
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",
|