@city41/gba-convertpng 0.0.25 → 0.0.26

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.
@@ -2,4 +2,3 @@ import { BackgroundSpec, ProcessBackgroundResult } from "./types";
2
2
  declare function isProcessBackgroundResult(obj: unknown): obj is ProcessBackgroundResult;
3
3
  declare function processBackground(bg: BackgroundSpec): Promise<ProcessBackgroundResult>;
4
4
  export { processBackground, isProcessBackgroundResult };
5
- export type { ProcessBackgroundResult };
@@ -1,14 +1,11 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.processBackground = processBackground;
7
4
  exports.isProcessBackgroundResult = isProcessBackgroundResult;
8
5
  const canvas_1 = require("./canvas");
6
+ const colors_1 = require("./colors");
7
+ const lodash_1 = require("lodash");
9
8
  const palette_1 = require("./palette");
10
- const tile_1 = require("./tile");
11
- const isEqual_1 = __importDefault(require("lodash/isEqual"));
12
9
  function isProcessBackgroundResult(obj) {
13
10
  return (obj !== null &&
14
11
  typeof obj === "object" &&
@@ -17,40 +14,136 @@ function isProcessBackgroundResult(obj) {
17
14
  obj.background !== null &&
18
15
  "file" in obj.background);
19
16
  }
20
- function extractMap(allTilesThatFormImage, dedupedTiles) {
21
- const map = [];
22
- allTilesThatFormImage.forEach((tile, i) => {
23
- const index = dedupedTiles.findIndex((dt) => {
24
- return (0, isEqual_1.default)(dt, tile);
25
- });
26
- if (index < 0) {
27
- throw new Error("extractMap: failed to find a matching tile in the deduped tile set");
17
+ function convertTileTo15Bit(rawTile) {
18
+ const data15 = [];
19
+ for (let p = 0; p < rawTile.length; p += 4) {
20
+ if (rawTile[p + 3] !== 255) {
21
+ data15.push(palette_1.MAGENTA_15);
22
+ }
23
+ else {
24
+ data15.push((0, colors_1.rgbToGBA15)(rawTile[p], rawTile[p + 1], rawTile[p + 2]));
28
25
  }
29
- map.push(index);
26
+ }
27
+ return data15;
28
+ }
29
+ function getTileIndex(tilePalette, tile) {
30
+ const index = tilePalette.findIndex(t => {
31
+ return t.join('-') === tile.join('-');
30
32
  });
33
+ if (index > -1) {
34
+ return index;
35
+ }
36
+ tilePalette.push([...tile]);
37
+ return tilePalette.length - 1;
38
+ }
39
+ // looks through all the palettes and combines multiple palettes into one
40
+ // based on how much room they have.
41
+ // example, palette-a has 4 colors, palette-b has 6, result is a palette with 10 colors
42
+ // possibly the two palettes share colors, only one copy of each color will be preserved
43
+ //
44
+ // by the way this algoritm works, it also uniqs the palettes
45
+ function combinePalettes(palettes) {
46
+ if (palettes.length <= 1) {
47
+ return palettes;
48
+ }
49
+ const sortedPalettes = (0, lodash_1.sortBy)(palettes, p => p.length);
50
+ let firstPalette = sortedPalettes[0];
51
+ const remainingPalettes = [];
52
+ for (let p = 1; p < sortedPalettes.length; ++p) {
53
+ const otherPalette = sortedPalettes[p];
54
+ const otherPaletteUniqueColors = otherPalette.filter(c => !firstPalette.includes(c));
55
+ if (firstPalette.length + otherPaletteUniqueColors.length < 16) {
56
+ firstPalette = firstPalette.concat(otherPaletteUniqueColors);
57
+ }
58
+ else {
59
+ remainingPalettes.push(otherPalette);
60
+ }
61
+ }
62
+ const combinedOtherPalettes = combinePalettes(remainingPalettes);
63
+ return [firstPalette].concat(combinedOtherPalettes);
64
+ }
65
+ function buildMap(tiles, bgWidthPx, bgHeightPx) {
66
+ const map = [];
67
+ const bgWidthT = bgWidthPx / 8;
68
+ const bgHeightT = bgHeightPx / 8;
69
+ for (let y = 0; y < bgHeightT; ++y) {
70
+ for (let x = 0; x < bgWidthT; ++x) {
71
+ const tile = tiles[y * bgWidthT + x];
72
+ map.push(tile.paletteIndex << 12 | tile.tileIndex);
73
+ }
74
+ }
31
75
  return map;
32
76
  }
77
+ function getGBATile(data15, palette) {
78
+ const gbaTile = [];
79
+ for (let p = 0; p < data15.length; p += 2) {
80
+ const highNibble = palette.indexOf(data15[p + 1]);
81
+ const lowNibble = palette.indexOf(data15[p]);
82
+ const byte = (highNibble & 0xf) << 4 | (lowNibble & 0xf);
83
+ gbaTile.push(byte);
84
+ }
85
+ return gbaTile;
86
+ }
87
+ function findMatchingPalette(data15, palettes) {
88
+ const foundPalette = palettes.find(palette => {
89
+ return data15.every(c => palette.includes(c));
90
+ });
91
+ if (!foundPalette) {
92
+ throw new Error('findMatchingPalette: failed to find a palette');
93
+ }
94
+ return foundPalette;
95
+ }
96
+ function padPalette(palette) {
97
+ while (palette.length < 16) {
98
+ palette.push(0);
99
+ }
100
+ return palette;
101
+ }
33
102
  async function processBackground(bg) {
34
- // const canvas = await reduceColors(await createCanvasFromPath(bg.file), 16);
35
103
  let canvas = await (0, canvas_1.createCanvasFromPath)(bg.file);
36
104
  if (typeof bg.reduceColors === "undefined" || bg.reduceColors === true) {
37
105
  canvas = await (0, canvas_1.reduceColors)(canvas, 16);
38
106
  }
39
107
  canvas = (0, canvas_1.roundUpToTileSize)(canvas);
40
- const palette = (0, palette_1.extractPalette)(canvas, !bg.trimPalette);
41
- console.log("palette size", palette.length);
42
- const allTilesThatFormImage = (0, tile_1.extractTiles)(canvas, palette, 1);
43
- const dedupedTiles = (0, tile_1.dedupeTiles)(allTilesThatFormImage);
44
- const map = extractMap(allTilesThatFormImage, dedupedTiles);
45
- if (typeof bg.transparentColor === "number") {
46
- palette[0] = bg.transparentColor;
108
+ const ctx = canvas.getContext('2d');
109
+ const tiles = [];
110
+ const tilePalette = [];
111
+ let palettes = [];
112
+ // first, determine the palettes
113
+ for (let y = 0; y < canvas.height; y += 8) {
114
+ for (let x = 0; x < canvas.width; x += 8) {
115
+ const rawTile = Array.from(ctx.getImageData(x, y, 8, 8).data);
116
+ const data15 = convertTileTo15Bit(rawTile);
117
+ const palette = (0, palette_1.extractPalette15)(data15, false);
118
+ palettes = combinePalettes(palettes.concat([palette]));
119
+ }
120
+ }
121
+ // now with palettes in hand, do the rest
122
+ for (let y = 0; y < canvas.height; y += 8) {
123
+ for (let x = 0; x < canvas.width; x += 8) {
124
+ const rawTile = Array.from(ctx.getImageData(x, y, 8, 8).data);
125
+ const data15 = convertTileTo15Bit(rawTile);
126
+ const palette = findMatchingPalette(data15, palettes);
127
+ const gbaTileData = getGBATile(data15, palette);
128
+ const tileIndex = getTileIndex(tilePalette, gbaTileData);
129
+ const paletteIndex = palettes.indexOf(palette);
130
+ tiles.push({
131
+ tileIndex,
132
+ paletteIndex,
133
+ });
134
+ }
47
135
  }
136
+ const tileData = tilePalette.flat(1);
137
+ const paletteData = palettes.map(padPalette).flat(1);
138
+ const paletteCount = palettes.length;
139
+ const map = buildMap(tiles, canvas.width, canvas.height);
48
140
  return {
49
- canvas,
50
141
  background: bg,
51
- tiles: dedupedTiles.flat(1),
52
- palette,
142
+ canvas,
53
143
  map,
144
+ palette: paletteData,
145
+ paletteCount,
146
+ tiles: tileData
54
147
  };
55
148
  }
56
149
  //# sourceMappingURL=background.js.map
package/dist/main.js CHANGED
@@ -96,6 +96,13 @@ function getBitmapDefines(result) {
96
96
  return `#define ${name.toUpperCase()}_WIDTH ${result.width}
97
97
  #define ${name.toUpperCase()}_HEIGHT ${result.height}`;
98
98
  }
99
+ function getPaletteDefines(result, file) {
100
+ if ((0, sprite_1.isProcessBasicSpriteResult)(result)) {
101
+ return '';
102
+ }
103
+ const name = path.basename(file, ".png");
104
+ return `#define ${name.toUpperCase()}_NUM_PALETTES ${result.paletteCount}`;
105
+ }
99
106
  function getTileDefines(result, file) {
100
107
  const name = path.basename(file, ".png");
101
108
  let tileWidth = result.canvas.width / 8;
@@ -230,7 +237,7 @@ function toSrcFiles(result, format) {
230
237
  extension: "c",
231
238
  },
232
239
  {
233
- src: (0, c_1.toCh)(result.palette, "w", fileRoot + "_palette"),
240
+ src: (0, c_1.toCh)(result.palette, "w", fileRoot + "_palette", getPaletteDefines(result, file)),
234
241
  extension: "h",
235
242
  },
236
243
  ]
package/dist/palette.d.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import { Canvas } from "canvas";
2
+ declare const MAGENTA_15: number;
2
3
  declare function getForcedPalette(c: Canvas): number[];
3
4
  declare function extractPalette(c: Canvas, pad?: boolean): number[];
5
+ declare function extractPalette15(data15: number[], pad?: boolean): number[];
4
6
  declare function reduceCanvases(canvases: Canvas[]): {
5
7
  palette: number[];
6
8
  canvas: Canvas;
7
9
  };
8
- export { extractPalette, getForcedPalette, reduceCanvases };
10
+ export { extractPalette, extractPalette15, getForcedPalette, reduceCanvases, MAGENTA_15 };
package/dist/palette.js CHANGED
@@ -1,12 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAGENTA_15 = void 0;
3
4
  exports.extractPalette = extractPalette;
5
+ exports.extractPalette15 = extractPalette15;
4
6
  exports.getForcedPalette = getForcedPalette;
5
7
  exports.reduceCanvases = reduceCanvases;
6
8
  const canvas_1 = require("canvas");
7
9
  const colors_1 = require("./colors");
8
10
  const MAGENTA_24 = [255, 0, 255, 255];
9
- const MAGENTA = (0, colors_1.rgbToGBA15)(255, 0, 255);
11
+ const MAGENTA_15 = (0, colors_1.rgbToGBA15)(255, 0, 255);
12
+ exports.MAGENTA_15 = MAGENTA_15;
10
13
  function is24BitMagenta(color) {
11
14
  return (color.length === MAGENTA_24.length &&
12
15
  color.every((channel, i) => channel === MAGENTA_24[i]));
@@ -21,9 +24,9 @@ function getForcedPalette(c) {
21
24
  const gbaColor = (0, colors_1.rgbToGBA15)(r, g, b);
22
25
  rawPalette.push(gbaColor);
23
26
  }
24
- const paletteWithoutMangenta = rawPalette.filter((c) => c !== MAGENTA);
27
+ const paletteWithoutMangenta = rawPalette.filter((c) => c !== MAGENTA_15);
25
28
  // then append magenta as the first color, to become transparent
26
- const palette = [MAGENTA].concat(paletteWithoutMangenta);
29
+ const palette = [MAGENTA_15].concat(paletteWithoutMangenta);
27
30
  return palette;
28
31
  }
29
32
  function extractPalette(c, pad = true) {
@@ -42,9 +45,21 @@ function extractPalette(c, pad = true) {
42
45
  }
43
46
  const rawPalette = Array.from(gbaColors);
44
47
  // make sure there is no magenta in the palette
45
- const paletteWithoutMangenta = rawPalette.filter((c) => c !== MAGENTA);
48
+ const paletteWithoutMangenta = rawPalette.filter((c) => c !== MAGENTA_15);
46
49
  // then append magenta as the first color, to become transparent
47
- const palette = [MAGENTA].concat(paletteWithoutMangenta);
50
+ const palette = [MAGENTA_15].concat(paletteWithoutMangenta);
51
+ while (pad && palette.length < 16) {
52
+ palette.push(0);
53
+ }
54
+ return palette;
55
+ }
56
+ function extractPalette15(data15, pad = true) {
57
+ const gbaColors = new Set(data15);
58
+ const rawPalette = Array.from(gbaColors);
59
+ // make sure there is no magenta in the palette
60
+ const paletteWithoutMangenta = rawPalette.filter((c) => c !== MAGENTA_15);
61
+ // then append magenta as the first color, to become transparent
62
+ const palette = [MAGENTA_15].concat(paletteWithoutMangenta);
48
63
  while (pad && palette.length < 16) {
49
64
  palette.push(0);
50
65
  }
package/dist/tile.js CHANGED
@@ -6,7 +6,7 @@ const colors_1 = require("./colors");
6
6
  function extractTile(imageData, palette) {
7
7
  const tileData = [];
8
8
  for (let p = 0; p < imageData.data.length; p += 8) {
9
- // first pixel in tile, high nibble
9
+ // second pixel in tile, high nibble
10
10
  // if this pixel has any transparency, then use palette index 0
11
11
  // which will make it fully transparent on the gba
12
12
  let hindex = 0;
@@ -18,7 +18,7 @@ function extractTile(imageData, palette) {
18
18
  const hgbaColor = (0, colors_1.rgbToGBA15)(hr, hg, hb);
19
19
  hindex = palette.indexOf(hgbaColor);
20
20
  }
21
- // second pixel in tile, low nibble
21
+ // first pixel in tile, low nibble
22
22
  // if this pixel has any transparency, then use palette index 0
23
23
  // which will make it fully transparent on the gba
24
24
  let lindex = 0;
package/dist/types.d.ts CHANGED
@@ -49,6 +49,7 @@ export type ProcessBackgroundResult = {
49
49
  background: BackgroundSpec;
50
50
  tiles: number[];
51
51
  palette: number[];
52
+ paletteCount: number;
52
53
  map: number[];
53
54
  };
54
55
  export type ProcessBitmapResult = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@city41/gba-convertpng",
3
- "version": "0.0.25",
3
+ "version": "0.0.26",
4
4
  "description": "Converts png images to GBA tile format",
5
5
  "main": "index.js",
6
6
  "repository": "github.com/city41/gba-convertpng",
@@ -25,7 +25,7 @@
25
25
  "@types/nearest-color": "^0.4.1",
26
26
  "canvas": "^3.2.0",
27
27
  "imagemagick": "^0.1.3",
28
- "lodash": "^4.17.21",
28
+ "lodash": "^4.17.23",
29
29
  "mkdirp": "^3.0.1",
30
30
  "nearest-color": "^0.4.4"
31
31
  },