Add color and transparency support to rgbgfx

In addition, fix various bugs.
Among them are minor memory issues and edge cases with certain inputs.

Signed-off-by: obskyr <powpowd@gmail.com>
This commit is contained in:
obskyr
2018-02-13 10:22:27 +01:00
parent 305512a2b7
commit 3075945367
6 changed files with 626 additions and 281 deletions

View File

@@ -12,14 +12,14 @@
#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,
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);
int tile_size);
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 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,22 +32,22 @@ 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;
+ x / 8 * raw_image->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) {
@@ -57,18 +58,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 +90,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 +119,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 +136,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 +153,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);
}
fclose(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,86 @@ 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 || raw_image->height % 8) {
errx(1, "Input PNG file %s not sized correctly. "
"The image's width and height must be multiples 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 +217,30 @@ 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);
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,27 +6,151 @@
* 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 it 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_read_struct(PNG_LIBPNG_VER_STRING,
NULL, NULL, NULL);
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");
/* TODO: Better error handling here? */
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)
errx(1, "Creating png structure failed");
@@ -35,8 +159,9 @@ void input_png_file(const struct Options opts, struct PNGImage *img)
errx(1, "Creating png info structure failed");
/* TODO: Better error handling here? */
if (setjmp(png_jmpbuf(img->png)))
if (setjmp(png_jmpbuf(img->png))) {
exit(1);
}
png_init_io(img->png, f);
@@ -46,163 +171,398 @@ 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);
if (img->depth != depth) {
if (opts.verbose) {
warnx("Image bit depth is not %i (is %i).", depth,
img->depth);
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);
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]];
}
}
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];
}
}
}
if (img->type == PNG_COLOR_TYPE_GRAY) {
if (img->depth < 8)
png_set_expand_gray_1_2_4_to_8(img->png);
return raw_image;
}
png_set_gray_to_rgb(img->png);
} else {
if (img->depth < 8)
png_set_expand_gray_1_2_4_to_8(img->png);
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);
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(
struct PNGImage *img, const png_color *palette, int colors_in_palette);
static struct RawIndexedImage *truecolor_png_to_raw(struct PNGImage *img)
{
struct RawIndexedImage *raw_image;
png_color *palette;
int colors_in_palette;
if (img->depth == 16) {
#if PNG_LIBPNG_VER >= 10504
png_set_scale_16(img->png);
#else
png_set_strip_16(img->png);
#endif
}
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);
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);
}
}
for (i = 0; i < num_trans; i++) {
if (trans_alpha[i] > 0)
full_alpha[i] = false;
else
full_alpha[i] = true;
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);
/*
* Lets us free the palette manually instead of leaving it to libpng,
* which lets us handle a PLTE palette and a built palette the same way.
*/
png_data_freer(img->png, img->info,
PNG_USER_WILL_FREE_DATA, PNG_FREE_PLTE);
}
/* A combined struct is needed to sort colors 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 rgba_build_palette(struct PNGImage *img,
png_color **palette_ptr_ptr, int *num_colors)
{
png_color *palette;
int y, value_index, i;
png_color cur_pixel_color;
png_byte cur_alpha;
bool color_exists;
png_color cur_palette_color;
struct ColorWithLuminance *palette_with_luminance;
/*
* 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++];
/*
* Transparent pixels don't count toward the palette,
* as they'll be replaced with color #0 later.
*/
if (cur_alpha == 0) {
continue;
}
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.
*/
color_exists = false;
for (i = 0; i < *num_colors; i++) {
cur_palette_color = palette[i];
if (cur_pixel_color.red == cur_palette_color.red &&
cur_pixel_color.green == cur_palette_color.green &&
cur_pixel_color.blue == cur_palette_color.blue) {
color_exists = true;
break;
}
}
free(full_alpha);
} else {
/* Set to the lightest color in the image. */
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] = cur_pixel_color;
(*num_colors)++;
}
}
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 {
palette_with_luminance =
malloc(sizeof(struct ColorWithLuminance) * colors);
for (i = 0; i < colors; i++) {
/*
* Eventually when this copies colors from the image itself,
* make sure order is lightest to darkest.
* Normally this would be done with floats, but since it's only
* used for comparison, we might as well use integer math.
*/
palette = malloc(sizeof(png_color) * colors);
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, colors,
sizeof(struct ColorWithLuminance), compare_luminance);
for (i = 0; i < colors; i++) {
palette[i] = palette_with_luminance[i].color;
}
free(palette_with_luminance);
}
if (strcmp(opts.infile, "rgb.png") == 0) {
palette[0].red = 0xFF;
palette[0].green = 0xEF;
palette[0].blue = 0xFF;
static uint8_t palette_index_of(const png_color *palette, int num_colors,
const png_color *color);
palette[1].red = 0xF7;
palette[1].green = 0xF7;
palette[1].blue = 0x8C;
static struct RawIndexedImage *processed_rgba_png_to_raw(
struct PNGImage *img, const png_color *palette, int colors_in_palette)
{
struct RawIndexedImage *raw_image;
int x, y, value_index;
png_color cur_color;
png_byte cur_alpha;
palette[2].red = 0x94;
palette[2].green = 0x94;
palette[2].blue = 0xC6;
raw_image = create_raw_image(img->width, img->height, colors);
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;
set_raw_image_palette(raw_image, palette, colors_in_palette);
palette[1].red = 0xA9;
palette[1].green = 0xA9;
palette[1].blue = 0xA9;
for (y = 0; y < img->height; y++) {
x = raw_image->width - 1;
value_index = img->width * 4 - 1;
palette[2].red = 0x55;
palette[2].green = 0x55;
palette[2].blue = 0x55;
palette[3].red = 0x00;
palette[3].green = 0x00;
palette[3].blue = 0x00;
while (x >= 0) {
cur_alpha = img->data[y][value_index];
if (cur_alpha == 0) {
raw_image->data[y][x] = 0;
value_index -= 4;
} else {
value_index--;
cur_color.blue = img->data[y][value_index--];
cur_color.green = img->data[y][value_index--];
cur_color.red = img->data[y][value_index--];
raw_image->data[y][x] =
palette_index_of(palette, colors_in_palette, &cur_color);
}
x--;
}
}
/*
* 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);
#else
png_set_quantize(img->png, palette, colors, colors, NULL, 1);
#endif
return raw_image;
}
if (!has_palette) {
png_set_PLTE(img->png, img->info, palette, colors);
free(palette);
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.");
}
/*
* If other useless chunks exist (sRGB, bKGD, pHYs, gAMA, cHRM, iCCP,
* etc.) offer to remove?
*/
static void read_png(struct PNGImage *img)
{
int y;
png_read_update_info(img->png, img->info);
img->data = malloc(sizeof(png_byte *) * img->height);
for (y = 0; y < img->height; y++)
for (y = 0; y < img->height; y++) {
img->data[y] = malloc(png_get_rowbytes(img->png, img->info));
}
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 +578,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);
}