@city41/gba-convertpng 0.0.3 → 0.0.5
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/README.md +164 -1
- package/dist/asm.d.ts +2 -1
- package/dist/asm.js +7 -3
- package/dist/background.d.ts +2 -2
- package/dist/background.js +6 -4
- package/dist/c.d.ts +2 -1
- package/dist/c.js +4 -1
- package/dist/main.js +9 -2
- package/dist/sprite.js +12 -8
- package/dist/types.d.ts +3 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,170 @@
|
|
|
2
2
|
|
|
3
3
|
This is a nodejs based tool for converting png images to the tile data format used in GBA games.
|
|
4
4
|
|
|
5
|
-
HEADS UP
|
|
5
|
+
**HEADS UP #1:** I make games for the e-Reader, and so far have not done mainstream GBA development. So possibly this tool is missing things for GBA dev. If so, please let me know.
|
|
6
|
+
|
|
7
|
+
**HEADS UP #2:** This is a very simple tool that can only do a few simple things. If you want a lot of control over your graphics, you probably want to use [Grit](https://www.coranac.com/man/grit/html/grit.htm). Maybe someday convertpng will expand enough to be a good alternative to Grit.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Make sure you have a recent version of [nodejs](https://nodejs.org/en) installed.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g @city41/gba-convertpng
|
|
15
|
+
or
|
|
16
|
+
yarn global add @city41/gba-convertpng
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
`convertpng` runs off a json spec file.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
gba-convertpng myGBAResources.json
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This json file provides everything that is needed to convert pngs to GBA data. Here is an example
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"outputDir": "./output",
|
|
32
|
+
"sprites": [
|
|
33
|
+
{
|
|
34
|
+
"file": "cursor.png",
|
|
35
|
+
"frames": 2
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"backgrounds": [
|
|
39
|
+
{
|
|
40
|
+
"file": "myBg.png"
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
This is about as simple as the spec file gets. It will load up `cursor.png` and `myBg.png` and convert them into tile data for the asz80 assembler in files `cursor.tiles.asm`, `cursor.palette.asm`, `myBg.tiles.asm`, `myBg.palette.asm` and `myBg.map.asm`.
|
|
47
|
+
|
|
48
|
+
And for example, `cursor.palette.asm` looks like this
|
|
49
|
+
|
|
50
|
+
```asm
|
|
51
|
+
.dw 0x7c1f,0x28c4,0x7fff,0x76d2,0,0,0,0,0,0,0,0,0,0,0,0
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Ideal for dropping into a game that uses the asz80 assembler.
|
|
55
|
+
|
|
56
|
+
## File paths
|
|
57
|
+
|
|
58
|
+
All of the image paths specified in the json spec are relative to the spec file. So in the above example, the pngs would be in the same directory as the spec file.
|
|
59
|
+
|
|
60
|
+
The same is true for `outputDir`, this is a directory path relative to the json spec file. This directory must exist.
|
|
61
|
+
|
|
62
|
+
## Other source code formats
|
|
63
|
+
|
|
64
|
+
At the root of the json spec you can add `format` to specify C or pyz80 output.
|
|
65
|
+
|
|
66
|
+
### C output
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"outputDir": "./output",
|
|
71
|
+
"format": "C",
|
|
72
|
+
"sprites": [
|
|
73
|
+
...
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
C output is a standard C array, either bytes (tile data) or words (palettes). For example with the above json spec, the C output for the cursor palette would be `cursor.palette.c.inc`, and the contents would be
|
|
77
|
+
|
|
78
|
+
```C
|
|
79
|
+
{ 0x7c1f,0x28c4,0x7fff,0x76d2,0,0,0,0,0,0,0,0,0,0,0,0 }
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The idea is to include it into your C source using the preprocessor, something like:
|
|
83
|
+
|
|
84
|
+
```C
|
|
85
|
+
const u8 cursorTiles[] =
|
|
86
|
+
#include "cursor.tiles.c.inc"
|
|
87
|
+
;
|
|
88
|
+
const u16 cursorPalette[] =
|
|
89
|
+
#include "cursor.palette.c.inc"
|
|
90
|
+
;
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
(I do almost no C development, so the C output might not be ideal. Let me know if you have any ideas here.)
|
|
94
|
+
|
|
95
|
+
### pyz80 output
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"outputDir": "./output",
|
|
100
|
+
"format": "pyz80",
|
|
101
|
+
"sprites": [
|
|
102
|
+
...
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
If you specify "pyz80" as the `format`, the output will look like this, for example `cursor.palette.asm`
|
|
106
|
+
|
|
107
|
+
```asm
|
|
108
|
+
dw 0x7c1f,0x28c4,0x7fff,0x76d2,0,0,0,0,0,0,0,0,0,0,0,0
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The only difference from asz80 is the lack of the leading period.
|
|
112
|
+
|
|
113
|
+
## Other options
|
|
114
|
+
|
|
115
|
+
## Sprite frames
|
|
116
|
+
|
|
117
|
+
All sprite entries must specify how many frames of animation are contained within the png. Each frame should be horizontally laid out within the image.
|
|
118
|
+
|
|
119
|
+
For example this png is a sprite that has four frames of animation
|
|
120
|
+
|
|
121
|
+

|
|
122
|
+
|
|
123
|
+
Even if a sprite just has a single frame of animation, then `"frames": 1` still needs to be specified.
|
|
124
|
+
|
|
125
|
+
### shared palette
|
|
126
|
+
|
|
127
|
+
A group of sprites can share a palette like this
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"outputDir": "./output",
|
|
132
|
+
"sprites": [
|
|
133
|
+
{
|
|
134
|
+
"sharedPalette": [
|
|
135
|
+
{
|
|
136
|
+
"file": "cursor.png",
|
|
137
|
+
"frames": 2
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"file": "enemy.png",
|
|
141
|
+
"frames": 1
|
|
142
|
+
}
|
|
143
|
+
],
|
|
144
|
+
"name": "global"
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
In this case convert-png will combine the colors of both sprites into a single 15 color palette, and output it as `global.shared.palette.[asm|c.inc]`, the tile output files for the sprites will be the same as before.
|
|
151
|
+
|
|
152
|
+
## Trim palette
|
|
153
|
+
|
|
154
|
+
You can add `"trimPalette": true` to any sprite or background. With this specified, the palette output data will only be as big as needed. For example the palette above with all the trailing zeros becomes this
|
|
155
|
+
|
|
156
|
+
```asm
|
|
157
|
+
.dw 0x7c1f,0x28c4,0x7fff,0x76d2
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Force palette
|
|
161
|
+
|
|
162
|
+
Normally convertpng will examine the pngs and figure out an optimal palette. If you need your data to conform to an existing palette, specify it with `"forcePalette": "<palette-file>"`
|
|
163
|
+
|
|
164
|
+
This should be a png image that is 15x1, each pixel being a color in the palette.
|
|
165
|
+
|
|
166
|
+
With forced palettes, imagemagick will be used to push the png files to match this palette. If you create the pngs using that palette, this won't cause any changes. But if you don't, you might be surprised how much your sprite has changed.
|
|
167
|
+
|
|
168
|
+
With a forced palette, the color indexes within the forced palette will be honored. So if within your palette you have a red at index 1, then the tile data for the sprites/backgrounds will be 1 whenever that red pixel is needed.
|
|
6
169
|
|
|
7
170
|
## Publishing
|
|
8
171
|
|
package/dist/asm.d.ts
CHANGED
package/dist/asm.js
CHANGED
|
@@ -2,18 +2,22 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.toAsm = toAsm;
|
|
4
4
|
const toHex_1 = require("./toHex");
|
|
5
|
-
function toAsm(data, width, numbersPerRow) {
|
|
5
|
+
function toAsm(data, width, numbersPerRow, format) {
|
|
6
|
+
if (format !== "asz80" && format !== "z80" && format !== "pyz80") {
|
|
7
|
+
throw new Error(`toAsm: given an incompatible format (${format})`);
|
|
8
|
+
}
|
|
6
9
|
const hexFn = width === "b" ? toHex_1.toHexByte : toHex_1.toHexWord;
|
|
10
|
+
const dPrefix = format === "pyz80" ? "" : ".";
|
|
7
11
|
const rows = [];
|
|
8
12
|
let row = [];
|
|
9
13
|
for (let i = 0; i < data.length; ++i) {
|
|
10
14
|
if (row.length === numbersPerRow) {
|
|
11
|
-
rows.push(`
|
|
15
|
+
rows.push(` ${dPrefix}d${width} ${row.join(",")}`);
|
|
12
16
|
row = [];
|
|
13
17
|
}
|
|
14
18
|
row.push(hexFn(data[i]));
|
|
15
19
|
}
|
|
16
|
-
rows.push(`
|
|
20
|
+
rows.push(` ${dPrefix}d${width} ${row.join(",")}`);
|
|
17
21
|
return rows.join("\r\n") + "\r\n";
|
|
18
22
|
}
|
|
19
23
|
//# sourceMappingURL=asm.js.map
|
package/dist/background.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { BackgroundSpec } from "./types";
|
|
1
|
+
import { BackgroundSpec, Format } from "./types";
|
|
2
2
|
type ProcessBackgroundResult = {
|
|
3
3
|
tilesAsmSrc: string;
|
|
4
4
|
paletteAsmSrc: string;
|
|
5
5
|
mapAsmSrc: string;
|
|
6
6
|
};
|
|
7
|
-
declare function processBackground(bg: BackgroundSpec): Promise<ProcessBackgroundResult>;
|
|
7
|
+
declare function processBackground(bg: BackgroundSpec, format: Format): Promise<ProcessBackgroundResult>;
|
|
8
8
|
export { processBackground };
|
package/dist/background.js
CHANGED
|
@@ -9,6 +9,7 @@ const canvas_1 = require("./canvas");
|
|
|
9
9
|
const palette_1 = require("./palette");
|
|
10
10
|
const tile_1 = require("./tile");
|
|
11
11
|
const isEqual_1 = __importDefault(require("lodash/isEqual"));
|
|
12
|
+
const c_1 = require("./c");
|
|
12
13
|
function extractMap(allTilesThatFormImage, dedupedTiles) {
|
|
13
14
|
const map = [];
|
|
14
15
|
allTilesThatFormImage.forEach((tile, i) => {
|
|
@@ -22,16 +23,17 @@ function extractMap(allTilesThatFormImage, dedupedTiles) {
|
|
|
22
23
|
});
|
|
23
24
|
return map;
|
|
24
25
|
}
|
|
25
|
-
async function processBackground(bg) {
|
|
26
|
+
async function processBackground(bg, format) {
|
|
26
27
|
const canvas = await (0, canvas_1.reduceColors)(await (0, canvas_1.createCanvasFromPath)(bg.file), 16);
|
|
27
28
|
const palette = (0, palette_1.extractPalette)(canvas, !bg.trimPalette);
|
|
28
29
|
const allTilesThatFormImage = (0, tile_1.extractTiles)(canvas, palette, 1);
|
|
29
30
|
const dedupedTiles = (0, tile_1.dedupeTiles)(allTilesThatFormImage);
|
|
30
31
|
const map = extractMap(allTilesThatFormImage, dedupedTiles);
|
|
32
|
+
const toSrcFun = format === "C" ? c_1.toC : asm_1.toAsm;
|
|
31
33
|
return {
|
|
32
|
-
tilesAsmSrc: (
|
|
33
|
-
paletteAsmSrc: (
|
|
34
|
-
mapAsmSrc: (
|
|
34
|
+
tilesAsmSrc: toSrcFun(dedupedTiles.flat(1), "b", 4, format),
|
|
35
|
+
paletteAsmSrc: toSrcFun(palette, "w", 4, format),
|
|
36
|
+
mapAsmSrc: toSrcFun(map, "w", 8, format),
|
|
35
37
|
};
|
|
36
38
|
}
|
|
37
39
|
//# sourceMappingURL=background.js.map
|
package/dist/c.d.ts
CHANGED
package/dist/c.js
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.toC = toC;
|
|
4
4
|
const toHex_1 = require("./toHex");
|
|
5
|
-
function toC(data, width, numbersPerRow) {
|
|
5
|
+
function toC(data, width, numbersPerRow, format) {
|
|
6
|
+
if (format !== "C") {
|
|
7
|
+
throw new Error(`toC: given an incompatible format (${format})`);
|
|
8
|
+
}
|
|
6
9
|
const hexFn = width === "b" ? toHex_1.toHexByte : toHex_1.toHexWord;
|
|
7
10
|
const rows = [];
|
|
8
11
|
let row = [];
|
package/dist/main.js
CHANGED
|
@@ -82,11 +82,18 @@ function hydrateJsonSpec(jsonSpecPath) {
|
|
|
82
82
|
}),
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
|
+
const formatToExt = {
|
|
86
|
+
C: "c.inc",
|
|
87
|
+
asz80: "asm",
|
|
88
|
+
z80: "asm",
|
|
89
|
+
pyz80: "asm",
|
|
90
|
+
bin: "bin",
|
|
91
|
+
};
|
|
85
92
|
async function main(jsonSpec) {
|
|
86
93
|
if (jsonSpec.format === "bin") {
|
|
87
94
|
throw new Error("convertpng does not support bin format");
|
|
88
95
|
}
|
|
89
|
-
const ext = jsonSpec.format
|
|
96
|
+
const ext = formatToExt[jsonSpec.format];
|
|
90
97
|
for (const sprite of jsonSpec.sprites) {
|
|
91
98
|
const processResult = await (0, sprite_1.processSprite)(sprite, jsonSpec.format, sprite.forcePalette);
|
|
92
99
|
if ((0, sprite_1.isBasicSpriteSpec)(sprite)) {
|
|
@@ -112,7 +119,7 @@ async function main(jsonSpec) {
|
|
|
112
119
|
}
|
|
113
120
|
}
|
|
114
121
|
for (const bg of jsonSpec.backgrounds) {
|
|
115
|
-
const processResult = await (0, background_1.processBackground)(bg);
|
|
122
|
+
const processResult = await (0, background_1.processBackground)(bg, jsonSpec.format);
|
|
116
123
|
const fileRoot = path.basename(bg.file, path.extname(bg.file));
|
|
117
124
|
const tilesAsmPath = path.resolve(jsonSpec.outputDir, `${fileRoot}.tiles.asm`);
|
|
118
125
|
const paletteAsmPath = path.resolve(jsonSpec.outputDir, `${fileRoot}.palette.asm`);
|
package/dist/sprite.js
CHANGED
|
@@ -28,12 +28,14 @@ async function processBasicSprite(sprite, format, forcedPalette) {
|
|
|
28
28
|
paletteSrc: palette,
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
|
-
const
|
|
32
|
-
|
|
31
|
+
const toSrcFun = format === "C" ? c_1.toC : asm_1.toAsm;
|
|
32
|
+
if (typeof sprite.transparentColor === "number") {
|
|
33
|
+
palette[0] = sprite.transparentColor;
|
|
34
|
+
}
|
|
33
35
|
return {
|
|
34
36
|
canvas,
|
|
35
|
-
tilesSrc: [
|
|
36
|
-
paletteSrc:
|
|
37
|
+
tilesSrc: [toSrcFun(tiles, "b", 4, format)],
|
|
38
|
+
paletteSrc: toSrcFun(palette, "w", 4, format),
|
|
37
39
|
};
|
|
38
40
|
}
|
|
39
41
|
async function processSharedPaletteSprites(sharedPaletteSprite, format, forcedPalette) {
|
|
@@ -55,14 +57,16 @@ async function processSharedPaletteSprites(sharedPaletteSprite, format, forcedPa
|
|
|
55
57
|
const t = (0, tile_1.extractTiles)(canvases[i], commonPalette, sharedPaletteSprite.sharedPalette[i].frames).flat(1);
|
|
56
58
|
tiles.push(t);
|
|
57
59
|
}
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
+
const toSrcFun = format === "C" ? c_1.toC : asm_1.toAsm;
|
|
61
|
+
if (typeof sharedPaletteSprite.transparentColor === "number") {
|
|
62
|
+
commonPalette[0] = sharedPaletteSprite.transparentColor;
|
|
63
|
+
}
|
|
60
64
|
return {
|
|
61
65
|
// this is useless in this scenario, but canvas
|
|
62
66
|
// really only exists for the puzzle generator
|
|
63
67
|
canvas: canvases[0],
|
|
64
|
-
tilesSrc: tiles.map((t) =>
|
|
65
|
-
paletteSrc:
|
|
68
|
+
tilesSrc: tiles.map((t) => toSrcFun(t, "b", 4, format)),
|
|
69
|
+
paletteSrc: toSrcFun(commonPalette, "w", 4, format),
|
|
66
70
|
};
|
|
67
71
|
}
|
|
68
72
|
async function processSprite(sprite, format, forcedPalettePath) {
|
package/dist/types.d.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
export type Format = "C" | "z80" | "bin";
|
|
1
|
+
export type Format = "C" | "z80" | "pyz80" | "asz80" | "bin";
|
|
2
2
|
export type BasicSpriteSpec = {
|
|
3
3
|
file: string;
|
|
4
4
|
frames: number;
|
|
5
5
|
trimPalette?: boolean;
|
|
6
6
|
forcePalette?: string;
|
|
7
|
+
transparentColor?: number;
|
|
7
8
|
};
|
|
8
9
|
export type SharedPaletteSpriteSpec = {
|
|
9
10
|
name: string;
|
|
10
11
|
trimPalette?: boolean;
|
|
11
12
|
sharedPalette: BasicSpriteSpec[];
|
|
12
13
|
forcePalette?: string;
|
|
14
|
+
transparentColor?: number;
|
|
13
15
|
};
|
|
14
16
|
export type SpriteSpec = BasicSpriteSpec | SharedPaletteSpriteSpec;
|
|
15
17
|
export type BackgroundSpec = {
|