@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 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: 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.
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
+ ![sprite with four frames](https://github.com/city41/gba-convertpng/blob/main/multipleFramesExample.png?raw=true)
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
@@ -1,2 +1,3 @@
1
- declare function toAsm(data: number[], width: "b" | "w", numbersPerRow: number): string;
1
+ import { Format } from "./types";
2
+ declare function toAsm(data: number[], width: "b" | "w", numbersPerRow: number, format: Format): string;
2
3
  export { toAsm };
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(` .d${width} ${row.join(",")}`);
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(` .d${width} ${row.join(",")}`);
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
@@ -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 };
@@ -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: (0, asm_1.toAsm)(dedupedTiles.flat(1), "b", 4),
33
- paletteAsmSrc: (0, asm_1.toAsm)(palette, "w", 4),
34
- mapAsmSrc: (0, asm_1.toAsm)(map, "w", 8),
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
@@ -1,2 +1,3 @@
1
- declare function toC(data: number[], width: "b" | "w", numbersPerRow: number): string;
1
+ import { Format } from "./types";
2
+ declare function toC(data: number[], width: "b" | "w", numbersPerRow: number, format: Format): string;
2
3
  export { toC };
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 === "z80" ? "asm" : "c.inc";
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 tileSrcFun = format === "z80" ? asm_1.toAsm : c_1.toC;
32
- const paletteSrcFun = format === "z80" ? asm_1.toAsm : c_1.toC;
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: [tileSrcFun(tiles, "b", 4)],
36
- paletteSrc: paletteSrcFun(palette, "w", 4),
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 tileSrcFun = format === "z80" ? asm_1.toAsm : c_1.toC;
59
- const paletteSrcFun = format === "z80" ? asm_1.toAsm : c_1.toC;
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) => tileSrcFun(t, "b", 4)),
65
- paletteSrc: paletteSrcFun(commonPalette, "w", 4),
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 = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@city41/gba-convertpng",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "Converts png images to GBA tile format",
5
5
  "main": "index.js",
6
6
  "repository": "github.com/city41/gba-convertpng",