Skip to content

Commit 603b7d9

Browse files
committed
Implement NCER-guided PNG to NCGR with palette; enable support for other PNG types
Matching is not yet guaranteed, this is just a capability POC.
1 parent 2a1ae2f commit 603b7d9

File tree

3 files changed

+88
-13
lines changed

3 files changed

+88
-13
lines changed

tools/nitrogfx/convert_png.c

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,21 @@ void ReadPng(char *path, struct Image *image) {
9494

9595
int color_type = png_get_color_type(png_ptr, info_ptr);
9696

97-
if (color_type != PNG_COLOR_TYPE_GRAY && color_type != PNG_COLOR_TYPE_PALETTE) {
97+
switch (color_type) {
98+
case PNG_COLOR_TYPE_GRAY:
99+
case PNG_COLOR_TYPE_GA:
100+
case PNG_COLOR_TYPE_PALETTE:
101+
case PNG_COLOR_TYPE_RGB:
102+
case PNG_COLOR_TYPE_RGBA:
103+
break;
104+
default:
98105
FATAL_ERROR("\"%s\" has an unsupported color type.\n", path);
99106
}
100107

101108
// Check if the image has a palette so that we can tell if the colors need to be inverted later.
102109
// Don't read the palette because it's not needed for now.
103-
image->hasPalette = (color_type == PNG_COLOR_TYPE_PALETTE);
110+
image->hasPalette = (color_type & PNG_COLOR_MASK_COLOR) != 0;
111+
image->hasTransparency = color_type == PNG_COLOR_TYPE_PALETTE || ((color_type & PNG_COLOR_MASK_ALPHA) != 0);
104112

105113
image->width = png_get_image_width(png_ptr, info_ptr);
106114
image->height = png_get_image_height(png_ptr, info_ptr);
@@ -134,7 +142,7 @@ void ReadPng(char *path, struct Image *image) {
134142
free(row_pointers);
135143
fclose(fp);
136144

137-
if (bit_depth != image->bitDepth) {
145+
if ((color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE) && bit_depth != image->bitDepth) {
138146
unsigned char *src = image->pixels;
139147

140148
if (bit_depth != 1 && bit_depth != 2 && bit_depth != 4 && bit_depth != 8) {

tools/nitrogfx/gfx.c

Lines changed: 73 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,14 @@ static void ConvertFromTiles4BppCell(unsigned char *src, unsigned char *dest, in
151151
dest[idxComponentY * pitch + idxComponentX] = (leftPixel << 4) | rightPixel;
152152
}
153153
} else {
154-
srcPixelPair = src[idxComponentY * pitch + idxComponentX];
155-
leftPixel = srcPixelPair & 0xF;
156-
rightPixel = srcPixelPair >> 4;
157-
154+
if (image->hasPalette) {
155+
leftPixel = src[idxComponentY * pitch + idxComponentX * 2 + 0] & 0xF;
156+
rightPixel = src[idxComponentY * pitch + idxComponentX * 2 + 1] & 0xF;
157+
} else {
158+
srcPixelPair = src[idxComponentY * pitch + idxComponentX];
159+
leftPixel = srcPixelPair & 0xF;
160+
rightPixel = srcPixelPair >> 4;
161+
}
158162
*dest++ = (leftPixel << 4) | rightPixel;
159163
}
160164
} else {
@@ -169,7 +173,13 @@ static void ConvertFromTiles4BppCell(unsigned char *src, unsigned char *dest, in
169173
dest[idxComponentY * pitch + idxComponentX] = *src++;
170174
}
171175
} else {
172-
*dest++ = src[idxComponentY * pitch + idxComponentX];
176+
if (image->hasPalette) {
177+
rightPixel = src[idxComponentY * pitch + idxComponentX * 2 + 0] & 0xF;
178+
leftPixel = src[idxComponentY * pitch + idxComponentX * 2 + 1] & 0xF;
179+
*dest++ = leftPixel | (rightPixel << 4);
180+
} else {
181+
*dest++ = src[idxComponentY * pitch + idxComponentX];
182+
}
173183
}
174184
}
175185
}
@@ -245,6 +255,8 @@ static void ConvertFromTiles8Bpp(unsigned char *src, unsigned char *dest, int nu
245255
}
246256

247257
static void ConvertFromTiles8BppCell(unsigned char *src, unsigned char *dest, int oamWidth, int oamHeight, int imageWidth, int startX, int startY, bool hFlip, bool vFlip, bool hvFlip, bool toPNG, int plttNum, int mappingType, struct Image *image) {
258+
static bool setTransparencyColor = false;
259+
248260
int tilesSoFar = 0;
249261
int rowsSoFar = 0;
250262
int chunkStartX = 0;
@@ -271,20 +283,71 @@ static void ConvertFromTiles8BppCell(unsigned char *src, unsigned char *dest, in
271283
}
272284

273285
if (toPNG) {
274-
if (mappingType == 2) {
286+
if (image->hasPalette && mappingType == 2) {
287+
// Color mode 256x16 extpltt is handled specially
288+
// The underlying PNG image is 24-bit RGB or 32-bit RGBA
275289
int colorIdx = plttNum * 256 + (*src++);
276290
struct Color *color = &image->palette.colors[colorIdx];
277291
dest[idxComponentY * pitch + idxComponentX * pitchFactor + 0] = color->red;
278292
dest[idxComponentY * pitch + idxComponentX * pitchFactor + 1] = color->green;
279293
dest[idxComponentY * pitch + idxComponentX * pitchFactor + 2] = color->blue;
280294
if (image->hasTransparency) {
295+
// Alpha on DS is binary. Alpha in PNG is 8-bit. Scale accordingly.
281296
dest[idxComponentY * pitch + idxComponentX * pitchFactor + 3] = colorIdx == 0 ? 0 : 255;
282297
}
283298
} else {
284299
dest[idxComponentY * pitch + idxComponentX] = *src++;
285300
}
286301
} else {
287-
*dest++ = src[idxComponentY * pitch + idxComponentX];
302+
if (image->hasPalette && mappingType == 2) {
303+
// Color mode 256x16 extpltt is handled specially
304+
// The underlying PNG image is 24-bit RGB or 32-bit RGBA
305+
int colorIdx;
306+
struct Color color = {
307+
.red = src[idxComponentY * pitch + idxComponentX * pitchFactor + 0],
308+
.green = src[idxComponentY * pitch + idxComponentX * pitchFactor + 1],
309+
.blue = src[idxComponentY * pitch + idxComponentX * pitchFactor + 2],
310+
};
311+
if (image->hasTransparency && src[idxComponentY * pitch + idxComponentX * pitchFactor + 3] == 0) {
312+
// First color is hardcoded to be transparency
313+
colorIdx = 0;
314+
if (!setTransparencyColor) {
315+
memcpy(&image->palette.colors[0], &color, sizeof(struct Color));
316+
setTransparencyColor = true;
317+
if (image->palette.numColors == 0) {
318+
image->palette.numColors = 1;
319+
}
320+
} else {
321+
// No other color is permitted to be transparency
322+
if (memcmp(&image->palette.colors[0], &color, sizeof(struct Color)) != 0) {
323+
FATAL_ERROR("Transparency color is not uniform\n");
324+
}
325+
}
326+
} else if (image->palette.numColors == 0) {
327+
// Haven't registered a color yet, and the very first pixel is not transparency
328+
memcpy(&image->palette.colors[1], &color, sizeof(struct Color));
329+
image->palette.numColors = 2;
330+
colorIdx = 1;
331+
} else {
332+
// Assume that palette is arranged in order of use, excluding transparency
333+
// It is not known whether this holds for retail ROMs
334+
for (colorIdx = 1; colorIdx < 4096 && colorIdx < image->palette.numColors; colorIdx++) {
335+
if (memcmp(&image->palette.colors[colorIdx], &color, sizeof(struct Color)) == 0) {
336+
break;
337+
}
338+
}
339+
if (colorIdx == image->palette.numColors) {
340+
if (colorIdx == 4096) {
341+
FATAL_ERROR("Too many unique colors for DS object extpltt\n");
342+
}
343+
memcpy(&image->palette.colors[colorIdx], &color, sizeof(struct Color));
344+
image->palette.numColors++;
345+
}
346+
}
347+
*dest++ = colorIdx & 0xFF;
348+
} else {
349+
*dest++ = src[idxComponentY * pitch + idxComponentX];
350+
}
288351
}
289352
}
290353
}
@@ -826,7 +889,7 @@ void ReadNtrScrn(char *path, struct NSCRFile **ppScrnHeader) {
826889
free(data);
827890
}
828891

829-
static inline uint32_t tileToPixelOffset(uint32_t tileIdx, uint32_t width, int bitDepth) {
892+
static inline uint32_t NSCR_tileToPixelOffset(uint32_t tileIdx, uint32_t width, int bitDepth) {
830893
div_t tile_yx = div(tileIdx, width / 8);
831894
return tile_yx.quot * bitDepth * width + tile_yx.rem * bitDepth;
832895
}
@@ -893,7 +956,7 @@ void ApplyScrnToImage(char *scrnFilePath, struct Image *image) {
893956
int i, x, y, xOffset, yOffset;
894957

895958
for (i = 0; i < numTiles; ++i) {
896-
dstOffset = tileToPixelOffset(i, pScrnHeader->scrnWidth, 8 * outSizeMul / outSizeDiv);
959+
dstOffset = NSCR_tileToPixelOffset(i, pScrnHeader->scrnWidth, 8 * outSizeMul / outSizeDiv);
897960
if (pScrnHeader->scrnMode == 1) {
898961
tileIdx = pScrnHeader->data[i];
899962
} else {
@@ -903,7 +966,7 @@ void ApplyScrnToImage(char *scrnFilePath, struct Image *image) {
903966
vFlip = (tileData >> 11) & 1;
904967
plttIndex = (tileData >> 12) & 0xF;
905968
}
906-
srcOffset = tileToPixelOffset(tileIdx, image->width, image->bitDepth);
969+
srcOffset = NSCR_tileToPixelOffset(tileIdx, image->width, image->bitDepth);
907970
for (y = 0; y < 8; ++y) {
908971
yOffset = vFlip ? 7 - y : y;
909972
for (x = 0; x < 8; ++x) {

tools/nitrogfx/main.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,10 @@ void ConvertPngToNtr(char *inputPath, char *outputPath, struct PngToNtrOptions *
166166

167167
if (options->cellFilePath != NULL) {
168168
ApplyCellsToImage(options->cellFilePath, &image, false);
169+
} else {
170+
if (image.pixelsAreRGB || (image.bitDepth != 4 && image.bitDepth != 8)) {
171+
FATAL_ERROR("PNG image %s has unsupported mapping type\n", inputPath);
172+
}
169173
}
170174

171175
WriteNtrImage(outputPath, options->numTiles, options->bitDepth, options->colsPerChunk, options->rowsPerChunk, &image, !image.hasPalette, options->clobberSize, options->byteOrder, options->version101, options->sopc, options->vramTransfer, options->scanMode, options->mappingType, key, options->wrongSize);

0 commit comments

Comments
 (0)