Merge pull request #235 from obskyr/rgbgfx-color

Add color support to rgbgfx (again)!

Signed-off-by: Antonio Niño Díaz <antonio_nd@outlook.com>
This commit is contained in:
Antonio Niño Díaz
2018-02-20 19:56:39 +00:00
9 changed files with 793 additions and 309 deletions

View File

@@ -30,6 +30,8 @@ Other contributors
- The Musl C library <http://www.musl-libc.org>
- obskyr <powpowd@gmail.com>
- The OpenBSD Project <http://www.openbsd.org>
- Sanqui <gsanky@gmail.com>

View File

@@ -43,7 +43,28 @@
</table>
<h1 class="Sh" title="Sh" id="DESCRIPTION"><a class="selflink" href="#DESCRIPTION">DESCRIPTION</a></h1>
The <b class="Nm" title="Nm">rgbgfx</b> program converts PNG images into the
Nintendo Game Boy's planar tile format. The arguments are as follows:
Nintendo Game Boy's planar tile format.
<div style="height: 1.00em;">&#x00A0;</div>
The resulting colors and their palette indices are determined differently
depending on the input PNG file:
<ul class="Bl-dash">
<li class="It-dash">If the file has an embedded palette, that palette's color
and order are used.</li>
<li class="It-dash">If not, and the image only contains shades of gray, rgbgfx
maps them to the indices appropriate for each shade. Any undetermined
indices are set to respective default shades of gray. For example: if the
bit depth is 2 and the image contains light gray and black, they become
the second and fourth colors - and the first and third colors get set to
default white and dark gray. If the image has multiple shades that map to
the same index, the palette is instead determined as if the image had
color.</li>
<li class="It-dash">If the image has color (or the grayscale method failed),
the colors are sorted from lightest to darkest.</li>
</ul>
<div style="height: 1.00em;">&#x00A0;</div>
The input image may not contain more colors than the selected bit depth allows.
Transparent pixels are set to palette index 0.
<h1 class="Sh" title="Sh" id="ARGUMENTS"><a class="selflink" href="#ARGUMENTS">ARGUMENTS</a></h1>
<dl class="Bl-tag">
<dt class="It-tag">&#x00A0;</dt>
<dd class="It-tag">&#x00A0;</dd>
@@ -58,14 +79,14 @@ The <b class="Nm" title="Nm">rgbgfx</b> program converts PNG images into the
<dd class="It-tag">&#x00A0;</dd>
<dt class="It-tag"><a class="selflink" href="#F"><b class="Fl" title="Fl" id="F">-F</b></a></dt>
<dd class="It-tag">Same as <b class="Fl" title="Fl">-f</b>, but additionally,
the input PNG file is fixed to have its parameters match the command
line's parameters.</dd>
the supplied command line parameters are saved within the PNG and will be
loaded and automatically used next time.</dd>
<dt class="It-tag">&#x00A0;</dt>
<dd class="It-tag">&#x00A0;</dd>
<dt class="It-tag"><a class="selflink" href="#d"><b class="Fl" title="Fl" id="d">-d</b></a>
<var class="Ar" title="Ar">depth</var></dt>
<dd class="It-tag">The bitdepth of the output image (either 1 or 2). By
default, the bitdepth is 2 (two bits per pixel).</dd>
<dd class="It-tag">The bit depth of the output image (either 1 or 2). By
default, the bit depth is 2 (two bits per pixel).</dd>
<dt class="It-tag">&#x00A0;</dt>
<dd class="It-tag">&#x00A0;</dd>
<dt class="It-tag"><a class="selflink" href="#h"><b class="Fl" title="Fl" id="h">-h</b></a></dt>
@@ -79,15 +100,16 @@ The <b class="Nm" title="Nm">rgbgfx</b> program converts PNG images into the
<dd class="It-tag">&#x00A0;</dd>
<dt class="It-tag"><a class="selflink" href="#p"><b class="Fl" title="Fl" id="p">-p</b></a>
<var class="Ar" title="Ar">palfile</var></dt>
<dd class="It-tag">Raw bytes (8 bytes for two bits per pixel, 4 bytes for one
bit per pixel) containing the RGB15 values in the little-endian byte order
and then ordered from lightest to darkest.</dd>
<dd class="It-tag">Output the image's palette in standard GBC palette format -
bytes (8 bytes for two bits per pixel, 4 bytes for one bit per pixel)
containing the RGB15 values in little-endian byte order. If the palette
contains too few colors, the remaining entries are set to black.</dd>
<dt class="It-tag">&#x00A0;</dt>
<dd class="It-tag">&#x00A0;</dd>
<dt class="It-tag"><a class="selflink" href="#P"><b class="Fl" title="Fl" id="P">-P</b></a></dt>
<dd class="It-tag">Same as <b class="Fl" title="Fl">-p</b>, but the pallete
file output name is made by taking the input filename, removing the file
extension, and appending <i class="Pa" title="Pa">.pal</i>.</dd>
<dd class="It-tag">Same as <b class="Fl" title="Fl">-p</b>, but the palette
file output name is made by taking the input PNG file's filename, removing
the file extension, and appending <i class="Pa" title="Pa">.pal</i>.</dd>
<dt class="It-tag">&#x00A0;</dt>
<dd class="It-tag">&#x00A0;</dd>
<dt class="It-tag"><a class="selflink" href="#t"><b class="Fl" title="Fl" id="t">-t</b></a>
@@ -120,7 +142,7 @@ The <b class="Nm" title="Nm">rgbgfx</b> program converts PNG images into the
<dd class="It-tag">Trim the end of the output file by this many tiles.</dd>
</dl>
<h1 class="Sh" title="Sh" id="EXAMPLES"><a class="selflink" href="#EXAMPLES">EXAMPLES</a></h1>
The following will take a PNG file with a bitdepth of 1, 2, or 8, and output
The following will take a PNG file with a bit depth of 1, 2, or 8, and output
planar 2bpp data:
<div class="Pp"></div>
<div class="D1">$ rgbgfx -o out.2bpp in.png</div>

View File

@@ -12,14 +12,15 @@
#include <stdint.h>
#include "gfx/main.h"
void png_to_gb(const struct PNGImage png, struct GBImage *gb);
void output_file(const struct Options opts, const struct GBImage gb);
void raw_to_gb(const struct RawIndexedImage *raw_image, struct GBImage *gb);
void output_file(const struct Options *opts, const struct GBImage *gb);
int get_tile_index(uint8_t *tile, uint8_t **tiles, int num_tiles,
int tile_size);
void create_tilemap(const struct Options opts, struct GBImage *gb,
void create_tilemap(const struct Options *opts, struct GBImage *gb,
struct Tilemap *tilemap);
void output_tilemap_file(const struct Options opts,
const struct Tilemap tilemap);
void output_palette_file(const struct Options opts, const struct PNGImage png);
void output_tilemap_file(const struct Options *opts,
const struct Tilemap *tilemap);
void output_palette_file(const struct Options *opts,
const struct RawIndexedImage *raw_image);
#endif

View File

@@ -31,6 +31,21 @@ struct Options {
char *infile;
};
struct RGBColor {
uint8_t red;
uint8_t green;
uint8_t blue;
};
struct ImageOptions {
bool horizontal;
int trim;
char *mapfile;
bool mapout;
char *palfile;
bool palout;
};
struct PNGImage {
png_struct *png;
png_info *info;
@@ -39,12 +54,14 @@ struct PNGImage {
int height;
png_byte depth;
png_byte type;
bool horizontal;
int trim;
char *mapfile;
bool mapout;
char *palfile;
bool palout;
};
struct RawIndexedImage {
uint8_t **data;
struct RGBColor *palette;
int num_colors;
int width;
int height;
};
struct GBImage {

View File

@@ -11,10 +11,11 @@
#include "gfx/main.h"
void input_png_file(const struct Options opts, struct PNGImage *img);
void get_text(struct PNGImage *png);
void set_text(const struct PNGImage *png);
void output_png_file(const struct Options opts, const struct PNGImage *png);
void free_png_data(const struct PNGImage *png);
struct RawIndexedImage *input_png_file(const struct Options *opts,
struct ImageOptions *png_options);
void output_png_file(const struct Options *opts,
const struct ImageOptions *png_options,
const struct RawIndexedImage *raw_image);
void destroy_raw_image(struct RawIndexedImage **raw_image_ptr_ptr);
#endif /* RGBDS_GFX_PNG_H */

View File

@@ -6,6 +6,7 @@
* SPDX-License-Identifier: MIT
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
@@ -31,23 +32,18 @@ void transpose_tiles(struct GBImage *gb, int width)
gb->data = newdata;
}
void png_to_gb(const struct PNGImage png, struct GBImage *gb)
void raw_to_gb(const struct RawIndexedImage *raw_image, struct GBImage *gb)
{
int x, y, byte;
png_byte index;
uint8_t index;
for (y = 0; y < png.height; y++) {
for (x = 0; x < png.width; x++) {
index = png.data[y][x];
for (y = 0; y < raw_image->height; y++) {
for (x = 0; x < raw_image->width; x++) {
index = raw_image->data[y][x];
index &= (1 << depth) - 1;
if (!gb->horizontal) {
byte = y * depth
+ x / 8 * png.height / 8 * 8 * depth;
} else {
byte = y * depth
+ x / 8 * png.height / 8 * 8 * depth;
}
+ x / 8 * raw_image->height / 8 * 8 * depth;
gb->data[byte] |= (index & 1) << (7 - x % 8);
if (depth == 2) {
gb->data[byte + 1] |=
@@ -57,18 +53,18 @@ void png_to_gb(const struct PNGImage png, struct GBImage *gb)
}
if (!gb->horizontal)
transpose_tiles(gb, png.width / 8);
transpose_tiles(gb, raw_image->width / 8);
}
void output_file(const struct Options opts, const struct GBImage gb)
void output_file(const struct Options *opts, const struct GBImage *gb)
{
FILE *f;
f = fopen(opts.outfile, "wb");
f = fopen(opts->outfile, "wb");
if (!f)
err(1, "Opening output file '%s' failed", opts.outfile);
err(1, "Opening output file '%s' failed", opts->outfile);
fwrite(gb.data, 1, gb.size - gb.trim * 8 * depth, f);
fwrite(gb->data, 1, gb->size - gb->trim * 8 * depth, f);
fclose(f);
}
@@ -89,7 +85,7 @@ int get_tile_index(uint8_t *tile, uint8_t **tiles, int num_tiles, int tile_size)
return -1;
}
void create_tilemap(const struct Options opts, struct GBImage *gb,
void create_tilemap(const struct Options *opts, struct GBImage *gb,
struct Tilemap *tilemap)
{
int i, j;
@@ -118,7 +114,7 @@ void create_tilemap(const struct Options opts, struct GBImage *gb,
tile[i] = gb->data[gb_i];
gb_i++;
}
if (opts.unique) {
if (opts->unique) {
index = get_tile_index(tile, tiles, num_tiles,
tile_size);
if (index < 0) {
@@ -135,7 +131,7 @@ void create_tilemap(const struct Options opts, struct GBImage *gb,
tilemap->size++;
}
if (opts.unique) {
if (opts->unique) {
free(gb->data);
gb->data = malloc(tile_size * num_tiles);
for (i = 0; i < num_tiles; i++) {
@@ -152,43 +148,44 @@ void create_tilemap(const struct Options opts, struct GBImage *gb,
free(tiles);
}
void output_tilemap_file(const struct Options opts,
const struct Tilemap tilemap)
void output_tilemap_file(const struct Options *opts,
const struct Tilemap *tilemap)
{
FILE *f;
f = fopen(opts.mapfile, "wb");
f = fopen(opts->mapfile, "wb");
if (!f)
err(1, "Opening tilemap file '%s' failed", opts.mapfile);
err(1, "Opening tilemap file '%s' failed", opts->mapfile);
fwrite(tilemap.data, 1, tilemap.size, f);
fwrite(tilemap->data, 1, tilemap->size, f);
fclose(f);
if (opts.mapout)
free(opts.mapfile);
if (opts->mapout)
free(opts->mapfile);
}
void output_palette_file(const struct Options opts, const struct PNGImage png)
void output_palette_file(const struct Options *opts,
const struct RawIndexedImage *raw_image)
{
FILE *f;
int i, colors, color;
png_color *palette;
int i, color;
uint8_t cur_bytes[2];
if (png_get_PLTE(png.png, png.info, &palette, &colors)) {
f = fopen(opts.palfile, "wb");
if (!f) {
err(1, "Opening palette file '%s' failed",
opts.palfile);
}
for (i = 0; i < colors; i++) {
color = palette[i].blue >> 3 << 10
| palette[i].green >> 3 << 5
| palette[i].red >> 3;
fwrite(&color, 2, 1, f);
f = fopen(opts->palfile, "wb");
if (!f)
err(1, "Opening palette file '%s' failed", opts->palfile);
for (i = 0; i < raw_image->num_colors; i++) {
color =
raw_image->palette[i].blue >> 3 << 10 |
raw_image->palette[i].green >> 3 << 5 |
raw_image->palette[i].red >> 3;
cur_bytes[0] = color & 0xFF;
cur_bytes[1] = color >> 8;
fwrite(cur_bytes, 2, 1, f);
}
fclose(f);
}
if (opts.palout)
free(opts.palfile);
if (opts->palout)
free(opts->palfile);
}

View File

@@ -6,6 +6,7 @@
* SPDX-License-Identifier: MIT
*/
#include <png.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
@@ -26,7 +27,8 @@ int main(int argc, char *argv[])
{
int ch, size;
struct Options opts = {0};
struct PNGImage png = {0};
struct ImageOptions png_options = {0};
struct RawIndexedImage *raw_image;
struct GBImage gb = {0};
struct Tilemap tilemap = {0};
char *ext;
@@ -102,80 +104,89 @@ int main(int argc, char *argv[])
colors = 1 << depth;
input_png_file(opts, &png);
raw_image = input_png_file(&opts, &png_options);
png.mapfile = "";
png.palfile = "";
png_options.mapfile = "";
png_options.palfile = "";
get_text(&png);
if (png.horizontal != opts.horizontal) {
if (png_options.horizontal != opts.horizontal) {
if (opts.verbose)
warnx(errmsg, "horizontal");
if (opts.hardfix)
png.horizontal = opts.horizontal;
png_options.horizontal = opts.horizontal;
}
if (png.horizontal)
opts.horizontal = png.horizontal;
if (png_options.horizontal)
opts.horizontal = png_options.horizontal;
if (png.trim != opts.trim) {
if (png_options.trim != opts.trim) {
if (opts.verbose)
warnx(errmsg, "trim");
if (opts.hardfix)
png.trim = opts.trim;
png_options.trim = opts.trim;
}
if (png.trim)
opts.trim = png.trim;
if (png_options.trim)
opts.trim = png_options.trim;
if (opts.trim > png.width / 8 - 1) {
errx(1, "Trim (%i) for input png file '%s' too large (max: %i)",
opts.trim, opts.infile, png.width / 8 - 1);
if (raw_image->width % 8) {
errx(1, "Input PNG file %s not sized correctly. The image's width must be a multiple of 8.",
opts.infile);
}
if (raw_image->width / 8 > 1 && raw_image->height % 8) {
errx(1, "Input PNG file %s not sized correctly. If the image is more than 1 tile wide, its height must be a multiple of 8.",
opts.infile);
}
if (strcmp(png.mapfile, opts.mapfile) != 0) {
if (opts.trim &&
opts.trim > (raw_image->width / 8) * (raw_image->height / 8) - 1) {
errx(1, "Trim (%i) for input raw_image file '%s' too large (max: %i)",
opts.trim, opts.infile,
(raw_image->width / 8) * (raw_image->height / 8) - 1);
}
if (strcmp(png_options.mapfile, opts.mapfile) != 0) {
if (opts.verbose)
warnx(errmsg, "tilemap file");
if (opts.hardfix)
png.mapfile = opts.mapfile;
png_options.mapfile = opts.mapfile;
}
if (!*opts.mapfile)
opts.mapfile = png.mapfile;
opts.mapfile = png_options.mapfile;
if (png.mapout != opts.mapout) {
if (png_options.mapout != opts.mapout) {
if (opts.verbose)
warnx(errmsg, "tilemap file");
if (opts.hardfix)
png.mapout = opts.mapout;
png_options.mapout = opts.mapout;
}
if (png.mapout)
opts.mapout = png.mapout;
if (png_options.mapout)
opts.mapout = png_options.mapout;
if (strcmp(png.palfile, opts.palfile) != 0) {
if (strcmp(png_options.palfile, opts.palfile) != 0) {
if (opts.verbose)
warnx(errmsg, "palette file");
if (opts.hardfix)
png.palfile = opts.palfile;
png_options.palfile = opts.palfile;
}
if (!*opts.palfile)
opts.palfile = png.palfile;
opts.palfile = png_options.palfile;
if (png.palout != opts.palout) {
if (png_options.palout != opts.palout) {
if (opts.verbose)
warnx(errmsg, "palette file");
if (opts.hardfix)
png.palout = opts.palout;
png_options.palout = opts.palout;
}
if (png.palout)
opts.palout = png.palout;
if (png_options.palout)
opts.palout = png_options.palout;
if (!*opts.mapfile && opts.mapout) {
ext = strrchr(opts.infile, '.');
@@ -209,31 +220,29 @@ int main(int argc, char *argv[])
}
}
gb.size = png.width * png.height * depth / 8;
gb.size = raw_image->width * raw_image->height * depth / 8;
gb.data = calloc(gb.size, 1);
gb.trim = opts.trim;
gb.horizontal = opts.horizontal;
if (*opts.outfile || *opts.mapfile) {
png_to_gb(png, &gb);
create_tilemap(opts, &gb, &tilemap);
raw_to_gb(raw_image, &gb);
create_tilemap(&opts, &gb, &tilemap);
}
if (*opts.outfile)
output_file(opts, gb);
output_file(&opts, &gb);
if (*opts.mapfile)
output_tilemap_file(opts, tilemap);
output_tilemap_file(&opts, &tilemap);
if (*opts.palfile)
output_palette_file(opts, png);
output_palette_file(&opts, raw_image);
if (opts.fix || opts.debug) {
set_text(&png);
output_png_file(opts, &png);
}
if (opts.fix || opts.debug)
output_png_file(&opts, &png_options, raw_image);
free_png_data(&png);
destroy_raw_image(&raw_image);
free(gb.data);
return 0;

View File

@@ -6,25 +6,150 @@
* SPDX-License-Identifier: MIT
*/
#include <png.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gfx/main.h"
void input_png_file(const struct Options opts, struct PNGImage *img)
static void initialize_png(struct PNGImage *img, FILE *f);
static struct RawIndexedImage *indexed_png_to_raw(struct PNGImage *img);
static struct RawIndexedImage *grayscale_png_to_raw(struct PNGImage *img);
static struct RawIndexedImage *truecolor_png_to_raw(struct PNGImage *img);
static void get_text(const struct PNGImage *img,
struct ImageOptions *png_options);
static void set_text(const struct PNGImage *img,
const struct ImageOptions *png_options);
static void free_png_data(const struct PNGImage *png);
struct RawIndexedImage *input_png_file(const struct Options *opts,
struct ImageOptions *png_options)
{
struct PNGImage img;
struct RawIndexedImage *raw_image;
FILE *f;
f = fopen(opts->infile, "rb");
if (!f)
err(1, "Opening input png file '%s' failed", opts->infile);
initialize_png(&img, f);
if (img.depth != depth) {
if (opts->verbose) {
warnx("Image bit depth is not %i (is %i).",
depth, img.depth);
}
}
switch (img.type) {
case PNG_COLOR_TYPE_PALETTE:
raw_image = indexed_png_to_raw(&img); break;
case PNG_COLOR_TYPE_GRAY:
case PNG_COLOR_TYPE_GRAY_ALPHA:
raw_image = grayscale_png_to_raw(&img); break;
case PNG_COLOR_TYPE_RGB:
case PNG_COLOR_TYPE_RGB_ALPHA:
raw_image = truecolor_png_to_raw(&img); break;
default:
/* Shouldn't happen, but might as well handle just in case. */
errx(1, "Input PNG file is of invalid color type.");
}
get_text(&img, png_options);
png_destroy_read_struct(&img.png, &img.info, NULL);
fclose(f);
free_png_data(&img);
return raw_image;
}
void output_png_file(const struct Options *opts,
const struct ImageOptions *png_options,
const struct RawIndexedImage *raw_image)
{
FILE *f;
int i, y, num_trans;
bool has_palette = false;
png_byte *trans_alpha;
png_color_16 *trans_values;
bool *full_alpha;
png_color *palette;
char *outfile;
struct PNGImage img;
png_color *png_palette;
int i;
f = fopen(opts.infile, "rb");
/*
* TODO: Variable outfile is for debugging purposes. Eventually,
* opts.infile will be used directly.
*/
if (opts->debug) {
outfile = malloc(strlen(opts->infile) + 5);
strcpy(outfile, opts->infile);
strcat(outfile, ".out");
} else {
outfile = opts->infile;
}
f = fopen(outfile, "wb");
if (!f)
err(1, "Opening input png file '%s' failed", opts.infile);
err(1, "Opening output png file '%s' failed", outfile);
img.png = png_create_write_struct(PNG_LIBPNG_VER_STRING,
NULL, NULL, NULL);
if (!img.png)
errx(1, "Creating png structure failed");
img.info = png_create_info_struct(img.png);
if (!img.info)
errx(1, "Creating png info structure failed");
if (setjmp(png_jmpbuf(img.png)))
exit(1);
png_init_io(img.png, f);
png_set_IHDR(img.png, img.info, raw_image->width, raw_image->height,
8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_palette = malloc(sizeof(png_color *) * raw_image->num_colors);
for (i = 0; i < raw_image->num_colors; i++) {
png_palette[i].red = raw_image->palette[i].red;
png_palette[i].green = raw_image->palette[i].green;
png_palette[i].blue = raw_image->palette[i].blue;
}
png_set_PLTE(img.png, img.info, png_palette, raw_image->num_colors);
free(png_palette);
if (opts->fix)
set_text(&img, png_options);
png_write_info(img.png, img.info);
png_write_image(img.png, (png_byte **) raw_image->data);
png_write_end(img.png, NULL);
png_destroy_write_struct(&img.png, &img.info);
fclose(f);
if (opts->debug)
free(outfile);
}
void destroy_raw_image(struct RawIndexedImage **raw_image_ptr_ptr)
{
int y;
struct RawIndexedImage *raw_image = *raw_image_ptr_ptr;
for (y = 0; y < raw_image->height; y++)
free(raw_image->data[y]);
free(raw_image->data);
free(raw_image->palette);
free(raw_image);
*raw_image_ptr_ptr = NULL;
}
static void initialize_png(struct PNGImage *img, FILE *f)
{
img->png = png_create_read_struct(PNG_LIBPNG_VER_STRING,
NULL, NULL, NULL);
if (!img->png)
@@ -34,7 +159,6 @@ void input_png_file(const struct Options opts, struct PNGImage *img)
if (!img->info)
errx(1, "Creating png info structure failed");
/* TODO: Better error handling here? */
if (setjmp(png_jmpbuf(img->png)))
exit(1);
@@ -46,125 +170,413 @@ void input_png_file(const struct Options opts, struct PNGImage *img)
img->height = png_get_image_height(img->png, img->info);
img->depth = png_get_bit_depth(img->png, img->info);
img->type = png_get_color_type(img->png, img->info);
}
if (img->type & PNG_COLOR_MASK_ALPHA)
png_set_strip_alpha(img->png);
static void read_png(struct PNGImage *img);
static struct RawIndexedImage *create_raw_image(int width, int height,
int num_colors);
static void set_raw_image_palette(struct RawIndexedImage *raw_image,
const png_color *palette, int num_colors);
if (img->depth != depth) {
if (opts.verbose) {
warnx("Image bit depth is not %i (is %i).", depth,
img->depth);
static struct RawIndexedImage *indexed_png_to_raw(struct PNGImage *img)
{
struct RawIndexedImage *raw_image;
png_color *palette;
int colors_in_PLTE;
int colors_in_new_palette;
png_byte *trans_alpha;
int num_trans;
png_color_16 *trans_color;
png_color *original_palette;
uint8_t *old_to_new_palette;
int i, x, y;
if (img->depth < 8)
png_set_packing(img->png);
png_get_PLTE(img->png, img->info, &palette, &colors_in_PLTE);
raw_image = create_raw_image(img->width, img->height, colors);
/*
* Transparent palette entries are removed, and the palette is collapsed.
* Transparent pixels are then replaced with palette index 0.
* This way, an indexed PNG can contain transparent pixels in *addition*
* to 4 normal colors.
*/
if (png_get_tRNS(img->png, img->info, &trans_alpha, &num_trans,
&trans_color)) {
original_palette = palette;
palette = malloc(sizeof(png_color) * colors_in_PLTE);
colors_in_new_palette = 0;
old_to_new_palette = malloc(sizeof(uint8_t) * colors_in_PLTE);
for (i = 0; i < num_trans; i++) {
if (trans_alpha[i] == 0) {
old_to_new_palette[i] = 0;
} else {
old_to_new_palette[i] = colors_in_new_palette;
palette[colors_in_new_palette++] =
original_palette[i];
}
}
for (i = num_trans; i < colors_in_PLTE; i++) {
old_to_new_palette[i] = colors_in_new_palette;
palette[colors_in_new_palette++] = original_palette[i];
}
if (colors_in_new_palette != colors_in_PLTE) {
palette = realloc(palette,
sizeof(png_color) *
colors_in_new_palette);
}
/*
* Setting and validating palette before reading
* allows us to error out *before* doing the data
* transformation if the palette is too long.
*/
set_raw_image_palette(raw_image, palette,
colors_in_new_palette);
read_png(img);
for (y = 0; y < img->height; y++) {
for (x = 0; x < img->width; x++) {
raw_image->data[y][x] =
old_to_new_palette[img->data[y][x]];
}
}
if (img->type == PNG_COLOR_TYPE_GRAY) {
free(old_to_new_palette);
} else {
set_raw_image_palette(raw_image, palette, colors_in_PLTE);
read_png(img);
for (y = 0; y < img->height; y++) {
for (x = 0; x < img->width; x++)
raw_image->data[y][x] = img->data[y][x];
}
}
return raw_image;
}
static struct RawIndexedImage *grayscale_png_to_raw(struct PNGImage *img)
{
if (img->depth < 8)
png_set_expand_gray_1_2_4_to_8(img->png);
png_set_gray_to_rgb(img->png);
} else {
if (img->depth < 8)
png_set_expand_gray_1_2_4_to_8(img->png);
return truecolor_png_to_raw(img);
}
has_palette = png_get_PLTE(img->png, img->info, &palette,
&colors);
}
static void rgba_png_palette(struct PNGImage *img,
png_color **palette_ptr_ptr, int *num_colors);
static struct RawIndexedImage
*processed_rgba_png_to_raw(const struct PNGImage *img,
const png_color *palette,
int colors_in_palette);
if (png_get_tRNS(img->png, img->info, &trans_alpha, &num_trans,
&trans_values)) {
if (img->type == PNG_COLOR_TYPE_PALETTE) {
full_alpha = malloc(sizeof(bool) * num_trans);
static struct RawIndexedImage *truecolor_png_to_raw(struct PNGImage *img)
{
struct RawIndexedImage *raw_image;
png_color *palette;
int colors_in_palette;
for (i = 0; i < num_trans; i++) {
if (trans_alpha[i] > 0)
full_alpha[i] = false;
else
full_alpha[i] = true;
}
for (i = 0; i < num_trans; i++) {
if (full_alpha[i]) {
palette[i].red = 0xFF;
palette[i].green = 0x00;
palette[i].blue = 0xFF;
/*
* Set to the lightest color in the
* palette.
*/
}
}
free(full_alpha);
} else {
/* Set to the lightest color in the image. */
}
png_free_data(img->png, img->info, PNG_FREE_TRNS, -1);
}
if (has_palette) {
/* Make sure palette only has the amount of colors you want. */
} else {
/*
* Eventually when this copies colors from the image itself,
* make sure order is lightest to darkest.
*/
palette = malloc(sizeof(png_color) * colors);
if (strcmp(opts.infile, "rgb.png") == 0) {
palette[0].red = 0xFF;
palette[0].green = 0xEF;
palette[0].blue = 0xFF;
palette[1].red = 0xF7;
palette[1].green = 0xF7;
palette[1].blue = 0x8C;
palette[2].red = 0x94;
palette[2].green = 0x94;
palette[2].blue = 0xC6;
palette[3].red = 0x39;
palette[3].green = 0x39;
palette[3].blue = 0x84;
} else {
palette[0].red = 0xFF;
palette[0].green = 0xFF;
palette[0].blue = 0xFF;
palette[1].red = 0xA9;
palette[1].green = 0xA9;
palette[1].blue = 0xA9;
palette[2].red = 0x55;
palette[2].green = 0x55;
palette[2].blue = 0x55;
palette[3].red = 0x00;
palette[3].green = 0x00;
palette[3].blue = 0x00;
}
}
/*
* Also unfortunately, this sets it at 8 bit, and I can't find any
* option to reduce to 2 or 1 bit.
*/
#if PNG_LIBPNG_VER < 10402
png_set_dither(img->png, palette, colors, colors, NULL, 1);
if (img->depth == 16) {
#if PNG_LIBPNG_VER >= 10504
png_set_scale_16(img->png);
#else
png_set_quantize(img->png, palette, colors, colors, NULL, 1);
png_set_strip_16(img->png);
#endif
if (!has_palette) {
png_set_PLTE(img->png, img->info, palette, colors);
free(palette);
}
if (!(img->type & PNG_COLOR_MASK_ALPHA)) {
if (png_get_valid(img->png, img->info, PNG_INFO_tRNS))
png_set_tRNS_to_alpha(img->png);
else
png_set_add_alpha(img->png, 0xFF, PNG_FILLER_AFTER);
}
read_png(img);
rgba_png_palette(img, &palette, &colors_in_palette);
raw_image = processed_rgba_png_to_raw(img, palette, colors_in_palette);
free(palette);
return raw_image;
}
static void rgba_PLTE_palette(struct PNGImage *img,
png_color **palette_ptr_ptr, int *num_colors);
static void rgba_build_palette(struct PNGImage *img,
png_color **palette_ptr_ptr, int *num_colors);
static void rgba_png_palette(struct PNGImage *img,
png_color **palette_ptr_ptr, int *num_colors)
{
if (png_get_valid(img->png, img->info, PNG_INFO_PLTE))
return rgba_PLTE_palette(img, palette_ptr_ptr, num_colors);
else
return rgba_build_palette(img, palette_ptr_ptr, num_colors);
}
static void rgba_PLTE_palette(struct PNGImage *img,
png_color **palette_ptr_ptr, int *num_colors)
{
png_get_PLTE(img->png, img->info, palette_ptr_ptr, num_colors);
/*
* If other useless chunks exist (sRGB, bKGD, pHYs, gAMA, cHRM, iCCP,
* etc.) offer to remove?
* Lets us free the palette manually instead of leaving it to libpng,
* which lets us handle a PLTE and a built palette the same way.
*/
png_data_freer(img->png, img->info,
PNG_USER_WILL_FREE_DATA, PNG_FREE_PLTE);
}
static void update_built_palette(png_color *palette,
const png_color *pixel_color, png_byte alpha,
int *num_colors, bool *only_grayscale);
static int fit_grayscale_palette(png_color *palette, int *num_colors);
static void order_color_palette(png_color *palette, int num_colors);
static void rgba_build_palette(struct PNGImage *img,
png_color **palette_ptr_ptr, int *num_colors)
{
png_color *palette;
int y, value_index;
png_color cur_pixel_color;
png_byte cur_alpha;
bool only_grayscale = true;
/*
* By filling the palette up with black by default, if the image
* doesn't have enough colors, the palette gets padded with black.
*/
*palette_ptr_ptr = calloc(colors, sizeof(png_color));
palette = *palette_ptr_ptr;
*num_colors = 0;
for (y = 0; y < img->height; y++) {
value_index = 0;
while (value_index < img->width * 4) {
cur_pixel_color.red = img->data[y][value_index++];
cur_pixel_color.green = img->data[y][value_index++];
cur_pixel_color.blue = img->data[y][value_index++];
cur_alpha = img->data[y][value_index++];
update_built_palette(palette, &cur_pixel_color,
cur_alpha,
num_colors, &only_grayscale);
}
}
/* In order not to count 100% transparent images as grayscale. */
only_grayscale = *num_colors ? only_grayscale : false;
if (!only_grayscale || !fit_grayscale_palette(palette, num_colors))
order_color_palette(palette, *num_colors);
}
static void update_built_palette(png_color *palette,
const png_color *pixel_color, png_byte alpha,
int *num_colors, bool *only_grayscale)
{
bool color_exists;
png_color cur_palette_color;
int i;
/*
* Transparent pixels don't count toward the palette,
* as they'll be replaced with color #0 later.
*/
if (alpha == 0)
return;
if (*only_grayscale && !(pixel_color->red == pixel_color->green &&
pixel_color->red == pixel_color->blue)) {
*only_grayscale = false;
}
color_exists = false;
for (i = 0; i < *num_colors; i++) {
cur_palette_color = palette[i];
if (pixel_color->red == cur_palette_color.red &&
pixel_color->green == cur_palette_color.green &&
pixel_color->blue == cur_palette_color.blue) {
color_exists = true;
break;
}
}
if (!color_exists) {
if (*num_colors == colors) {
err(1, "Too many colors in input PNG file to fit into a %d-bit palette (max %d).",
depth, colors);
}
palette[*num_colors] = *pixel_color;
(*num_colors)++;
}
}
static int fit_grayscale_palette(png_color *palette, int *num_colors)
{
int interval = 256 / colors;
png_color *fitted_palette = malloc(sizeof(png_color) * colors);
bool *set_indices = calloc(colors, sizeof(bool));
int i, shade_index;
fitted_palette[0].red = 0xFF;
fitted_palette[0].green = 0xFF;
fitted_palette[0].blue = 0xFF;
fitted_palette[colors - 1].red = 0;
fitted_palette[colors - 1].green = 0;
fitted_palette[colors - 1].blue = 0;
if (colors == 4) {
fitted_palette[1].red = 0xA9;
fitted_palette[1].green = 0xA9;
fitted_palette[1].blue = 0xA9;
fitted_palette[2].red = 0x55;
fitted_palette[2].green = 0x55;
fitted_palette[2].blue = 0x55;
}
for (i = 0; i < *num_colors; i++) {
shade_index = colors - 1 - palette[i].red / interval;
if (set_indices[shade_index]) {
free(fitted_palette);
free(set_indices);
return false;
}
fitted_palette[shade_index] = palette[i];
set_indices[shade_index] = true;
}
for (i = 0; i < colors; i++)
palette[i] = fitted_palette[i];
*num_colors = colors;
free(fitted_palette);
free(set_indices);
return true;
}
/* A combined struct is needed to sort csolors in order of luminance. */
struct ColorWithLuminance {
png_color color;
int luminance;
};
static int compare_luminance(const void *a, const void *b)
{
struct ColorWithLuminance *x = (struct ColorWithLuminance *)a;
struct ColorWithLuminance *y = (struct ColorWithLuminance *)b;
return y->luminance - x->luminance;
}
static void order_color_palette(png_color *palette, int num_colors)
{
int i;
struct ColorWithLuminance *palette_with_luminance =
malloc(sizeof(struct ColorWithLuminance) * num_colors);
for (i = 0; i < num_colors; i++) {
/*
* Normally this would be done with floats, but since it's only
* used for comparison, we might as well use integer math.
*/
palette_with_luminance[i].color = palette[i];
palette_with_luminance[i].luminance = 2126 * palette[i].red +
7152 * palette[i].green +
722 * palette[i].blue;
}
qsort(palette_with_luminance, num_colors,
sizeof(struct ColorWithLuminance), compare_luminance);
for (i = 0; i < num_colors; i++)
palette[i] = palette_with_luminance[i].color;
free(palette_with_luminance);
}
static void put_raw_image_pixel(struct RawIndexedImage *raw_image,
const struct PNGImage *img,
int *value_index, int x, int y,
const png_color *palette,
int colors_in_palette);
static struct RawIndexedImage
*processed_rgba_png_to_raw(const struct PNGImage *img,
const png_color *palette,
int colors_in_palette)
{
struct RawIndexedImage *raw_image;
int x, y, value_index;
raw_image = create_raw_image(img->width, img->height, colors);
set_raw_image_palette(raw_image, palette, colors_in_palette);
for (y = 0; y < img->height; y++) {
x = raw_image->width - 1;
value_index = img->width * 4 - 1;
while (x >= 0) {
put_raw_image_pixel(raw_image, img,
&value_index, x, y,
palette, colors_in_palette);
x--;
}
}
return raw_image;
}
static uint8_t palette_index_of(const png_color *palette,
int num_colors, const png_color *color);
static void put_raw_image_pixel(struct RawIndexedImage *raw_image,
const struct PNGImage *img,
int *value_index, int x, int y,
const png_color *palette,
int colors_in_palette)
{
png_color pixel_color;
png_byte alpha;
alpha = img->data[y][*value_index];
if (alpha == 0) {
raw_image->data[y][x] = 0;
*value_index -= 4;
} else {
(*value_index)--;
pixel_color.blue = img->data[y][(*value_index)--];
pixel_color.green = img->data[y][(*value_index)--];
pixel_color.red = img->data[y][(*value_index)--];
raw_image->data[y][x] = palette_index_of(palette,
colors_in_palette,
&pixel_color);
}
}
static uint8_t palette_index_of(const png_color *palette,
int num_colors, const png_color *color)
{
uint8_t i;
for (i = 0; i < num_colors; i++) {
if (palette[i].red == color->red &&
palette[i].green == color->green &&
palette[i].blue == color->blue) {
return i;
}
}
errx(1, "The input PNG file contains colors that don't appear in its embedded palette.");
}
static void read_png(struct PNGImage *img)
{
int y;
png_read_update_info(img->png, img->info);
@@ -174,35 +586,78 @@ void input_png_file(const struct Options opts, struct PNGImage *img)
png_read_image(img->png, img->data);
png_read_end(img->png, img->info);
fclose(f);
}
void get_text(struct PNGImage *png)
static struct RawIndexedImage *create_raw_image(int width, int height,
int num_colors)
{
struct RawIndexedImage *raw_image;
int y;
raw_image = malloc(sizeof(struct RawIndexedImage));
raw_image->width = width;
raw_image->height = height;
raw_image->num_colors = num_colors;
raw_image->palette = malloc(sizeof(struct RGBColor) * num_colors);
raw_image->data = malloc(sizeof(uint8_t *) * height);
for (y = 0; y < height; y++)
raw_image->data[y] = malloc(sizeof(uint8_t) * width);
return raw_image;
}
static void set_raw_image_palette(struct RawIndexedImage *raw_image,
const png_color *palette, int num_colors)
{
int i;
if (num_colors > raw_image->num_colors) {
errx(1, "Too many colors in input PNG file's palette to fit into a %d-bit palette (%d in input palette, max %d).",
raw_image->num_colors >> 1,
num_colors, raw_image->num_colors);
}
for (i = 0; i < num_colors; i++) {
raw_image->palette[i].red = palette[i].red;
raw_image->palette[i].green = palette[i].green;
raw_image->palette[i].blue = palette[i].blue;
}
for (i = num_colors; i < raw_image->num_colors; i++) {
raw_image->palette[i].red = 0;
raw_image->palette[i].green = 0;
raw_image->palette[i].blue = 0;
}
}
static void get_text(const struct PNGImage *img,
struct ImageOptions *png_options)
{
png_text *text;
int i, numtxts, numremoved;
png_get_text(png->png, png->info, &text, &numtxts);
png_get_text(img->png, img->info, &text, &numtxts);
for (i = 0; i < numtxts; i++) {
if (strcmp(text[i].key, "h") == 0 && !*text[i].text) {
png->horizontal = true;
png_free_data(png->png, png->info, PNG_FREE_TEXT, i);
png_options->horizontal = true;
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
} else if (strcmp(text[i].key, "x") == 0) {
png->trim = strtoul(text[i].text, NULL, 0);
png_free_data(png->png, png->info, PNG_FREE_TEXT, i);
png_options->trim = strtoul(text[i].text, NULL, 0);
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
} else if (strcmp(text[i].key, "t") == 0) {
png->mapfile = text[i].text;
png_free_data(png->png, png->info, PNG_FREE_TEXT, i);
png_options->mapfile = text[i].text;
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
} else if (strcmp(text[i].key, "T") == 0 && !*text[i].text) {
png->mapout = true;
png_free_data(png->png, png->info, PNG_FREE_TEXT, i);
png_options->mapout = true;
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
} else if (strcmp(text[i].key, "p") == 0) {
png->palfile = text[i].text;
png_free_data(png->png, png->info, PNG_FREE_TEXT, i);
png_options->palfile = text[i].text;
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
} else if (strcmp(text[i].key, "P") == 0 && !*text[i].text) {
png->palout = true;
png_free_data(png->png, png->info, PNG_FREE_TEXT, i);
png_options->palout = true;
png_free_data(img->png, img->info, PNG_FREE_TEXT, i);
}
}
@@ -218,106 +673,64 @@ void get_text(struct PNGImage *png)
text[i].text = text[i + numremoved].text;
text[i].compression = text[i + numremoved].compression;
}
png_set_text(png->png, png->info, text, numtxts - numremoved);
png_set_text(img->png, img->info, text, numtxts - numremoved);
}
void set_text(const struct PNGImage *png)
static void set_text(const struct PNGImage *img,
const struct ImageOptions *png_options)
{
png_text *text;
char buffer[3];
text = malloc(sizeof(png_text));
if (png->horizontal) {
if (png_options->horizontal) {
text[0].key = "h";
text[0].text = "";
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
png_set_text(png->png, png->info, text, 1);
png_set_text(img->png, img->info, text, 1);
}
if (png->trim) {
if (png_options->trim) {
text[0].key = "x";
snprintf(buffer, 3, "%d", png->trim);
snprintf(buffer, 3, "%d", png_options->trim);
text[0].text = buffer;
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
png_set_text(png->png, png->info, text, 1);
png_set_text(img->png, img->info, text, 1);
}
if (*png->mapfile) {
if (*png_options->mapfile) {
text[0].key = "t";
text[0].text = "";
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
png_set_text(png->png, png->info, text, 1);
png_set_text(img->png, img->info, text, 1);
}
if (png->mapout) {
if (png_options->mapout) {
text[0].key = "T";
text[0].text = "";
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
png_set_text(png->png, png->info, text, 1);
png_set_text(img->png, img->info, text, 1);
}
if (*png->palfile) {
if (*png_options->palfile) {
text[0].key = "p";
text[0].text = "";
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
png_set_text(png->png, png->info, text, 1);
png_set_text(img->png, img->info, text, 1);
}
if (png->palout) {
if (png_options->palout) {
text[0].key = "P";
text[0].text = "";
text[0].compression = PNG_TEXT_COMPRESSION_NONE;
png_set_text(png->png, png->info, text, 1);
png_set_text(img->png, img->info, text, 1);
}
free(text);
}
void output_png_file(const struct Options opts, const struct PNGImage *png)
{
FILE *f;
char *outfile;
png_struct *img;
/*
* TODO: Variable outfile is for debugging purposes. Eventually,
* opts.infile will be used directly.
*/
if (opts.debug) {
outfile = malloc(strlen(opts.infile) + 5);
strcpy(outfile, opts.infile);
strcat(outfile, ".out");
} else {
outfile = opts.infile;
}
f = fopen(outfile, "wb");
if (!f)
err(1, "Opening output png file '%s' failed", outfile);
img = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!img)
errx(1, "Creating png structure failed");
/* TODO: Better error handling here? */
if (setjmp(png_jmpbuf(img)))
exit(1);
png_init_io(img, f);
png_write_info(img, png->info);
png_write_image(img, png->data);
png_write_end(img, NULL);
fclose(f);
if (opts.debug)
free(outfile);
}
void free_png_data(const struct PNGImage *png)
static void free_png_data(const struct PNGImage *img)
{
int y;
for (y = 0; y < png->height; y++)
free(png->data[y]);
for (y = 0; y < img->height; y++)
free(img->data[y]);
free(png->data);
free(img->data);
}

View File

@@ -24,7 +24,28 @@
The
.Nm
program converts PNG images into the Nintendo Game Boy's planar tile format.
The arguments are as follows:
The resulting colors and their palette indices are determined differently
depending on the input PNG file:
.Bl -dash -width Ds
.It
If the file has an embedded palette, that palette's color and order are used.
.It
If not, and the image only contains shades of gray, rgbgfx maps them to the
indices appropriate for each shade. Any undetermined indices are set to
respective default shades of gray. For example: if the bit depth is 2 and the
image contains light gray and black, they become the second and fourth colors -
and the first and third colors get set to default white and dark gray. If the
image has multiple shades that map to the same index, the palette is instead
determined as if the image had color.
.It
If the image has color (or the grayscale method failed), the colors are sorted
from lightest to darkest.
.El
The input image may not contain more colors than the selected bit depth
allows. Transparent pixels are set to palette index 0.
.Sh ARGUMENTS
.Bl -tag -width Ds
.It Fl D
Debug features are enabled.
@@ -33,24 +54,25 @@ Fix the input PNG file to be a correctly indexed image.
.It Fl F
Same as
.Fl f ,
but additionally, the input PNG file is fixed to have its parameters match the
command line's parameters.
but additionally, the supplied command line parameters are saved within the PNG
and will be loaded and automatically used next time.
.It Fl d Ar depth
The bitdepth of the output image (either 1 or 2).
By default, the bitdepth is 2 (two bits per pixel).
The bit depth of the output image (either 1 or 2).
By default, the bit depth is 2 (two bits per pixel).
.It Fl h
Lay out tiles horizontally rather than vertically.
.It Fl o Ar outfile
The name of the output file.
.It Fl p Ar palfile
Raw bytes (8 bytes for two bits per pixel, 4 bytes for one bit per pixel)
containing the RGB15 values in the little-endian byte order and then ordered
from lightest to darkest.
Output the image's palette in standard GBC palette format - bytes (8 bytes for
two bits per pixel, 4 bytes for one bit per pixel) containing the RGB15 values
in little-endian byte order. If the palette contains too few colors, the
remaining entries are set to black.
.It Fl P
Same as
.Fl p ,
but the pallete file output name is made by taking the input filename,
removing the file extension, and appending
but the palette file output name is made by taking the input PNG file's
filename, removing the file extension, and appending
.Pa .pal .
.It Fl t Ar mapfile
If any tiles are the same, don't place the repeat tiles in the output file, and
@@ -73,7 +95,7 @@ the PNG file don't match.
Trim the end of the output file by this many tiles.
.El
.Sh EXAMPLES
The following will take a PNG file with a bitdepth of 1, 2, or 8, and output
The following will take a PNG file with a bit depth of 1, 2, or 8, and output
planar 2bpp data:
.Pp
.D1 $ rgbgfx -o out.2bpp in.png