Files
nethack/win/share/tileset.c
2021-01-26 21:06:16 -05:00

349 lines
10 KiB
C

/* NetHack 3.7 tileset.c $NHDT-Date: 1596498341 2020/08/03 23:45:41 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.3 $ */
/* Copyright (c) Ray Chason, 2016. */
/* NetHack may be freely redistributed. See license for details. */
#include "config.h"
#include "objclass.h"
#include "flag.h"
#include "tileset.h"
static void get_tile_map(const char *);
static unsigned gcd(unsigned, unsigned);
static void split_tiles(const struct TileSetImage *);
static void free_image(struct TileSetImage *);
static struct TileImage *tiles;
static unsigned num_tiles;
static struct TileImage blank_tile; /* for graceful undefined tile handling */
static boolean have_palette;
static struct Pixel palette[256];
boolean
read_tiles(const char *filename, boolean true_color)
{
static const unsigned char png_sig[] = {
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A
};
struct TileSetImage image;
FILE *fp;
char header[16];
boolean ok;
/* Fill the image structure with known values */
image.width = 0;
image.height = 0;
image.pixels = NULL; /* custodial */
image.indexes = NULL; /* custodial */
image.image_desc = NULL; /* custodial */
image.tile_width = 0;
image.tile_height = 0;
/* Identify the image type */
fp = fopen(filename, "rb");
if (!fp)
goto error;
memset(header, 0, sizeof(header));
fread(header, 1, sizeof(header), fp);
fclose(fp), fp = (FILE *) 0;
/* Call the loader appropriate for the image */
if (memcmp(header, "BM", 2) == 0) {
ok = read_bmp_tiles(filename, &image);
} else if (memcmp(header, "GIF87a", 6) == 0
|| memcmp(header, "GIF89a", 6) == 0) {
ok = read_gif_tiles(filename, &image);
} else if (memcmp(header, png_sig, sizeof(png_sig)) == 0) {
ok = read_png_tiles(filename, &image);
} else {
ok = FALSE;
}
if (!ok)
goto error;
/* Reject if the interface cannot handle direct color and the image does
not use a palette */
if (!true_color && image.indexes == NULL)
goto error;
/* Save the palette if present */
have_palette = image.indexes != NULL;
memcpy(palette, image.palette, sizeof(palette));
/* Set defaults for tile metadata */
if (image.tile_width == 0) {
image.tile_width = image.width / 40;
}
if (image.tile_height == 0) {
image.tile_height = image.tile_width;
}
/* Set the tile dimensions if the user has not done so */
if (iflags.wc_tile_width == 0) {
iflags.wc_tile_width = image.tile_width;
}
if (iflags.wc_tile_height == 0) {
iflags.wc_tile_height = image.tile_height;
}
/* Parse the tile map */
get_tile_map(image.image_desc);
/* Split the image into tiles */
split_tiles(&image);
free_image(&image);
return TRUE;
error:
if (fp)
fclose(fp);
free_image(&image);
return FALSE;
}
/* Free tile memory not required by the chosen display mode */
void
set_tile_type(boolean true_color)
{
unsigned i;
if (tiles) {
if (true_color) {
for (i = 0; i < num_tiles; ++i) {
free((genericptr_t) tiles[i].indexes);
tiles[i].indexes = NULL;
}
have_palette = FALSE;
} else {
for (i = 0; i < num_tiles; ++i) {
free((genericptr_t) tiles[i].pixels);
tiles[i].pixels = NULL;
}
}
}
}
const struct Pixel *
get_palette(void)
{
return have_palette ? palette : NULL;
}
/* TODO: derive tile_map from image_desc */
static void
get_tile_map(const char *image_desc UNUSED)
{
return;
}
void
free_tiles(void)
{
unsigned i;
if (tiles) {
for (i = 0; i < num_tiles; ++i) {
free((genericptr_t) tiles[i].pixels);
free((genericptr_t) tiles[i].indexes);
}
free((genericptr_t) tiles), tiles = NULL;
}
num_tiles = 0;
if (blank_tile.pixels)
free((genericptr_t) blank_tile.pixels), blank_tile.pixels = NULL;
if (blank_tile.indexes)
free((genericptr_t) blank_tile.indexes), blank_tile.indexes = NULL;
}
static void
free_image(struct TileSetImage *image)
{
if (image->pixels)
free((genericptr_t) image->pixels), image->pixels = NULL;
if (image->indexes)
free((genericptr_t) image->indexes), image->indexes = NULL;
if (image->image_desc)
free((genericptr_t) image->image_desc), image->image_desc = NULL;
}
const struct TileImage *
get_tile(unsigned tile_index)
{
if (tile_index >= num_tiles)
return &blank_tile;
return &tiles[tile_index];
}
/* Note that any tile returned by this function must be freed */
struct TileImage *
stretch_tile(const struct TileImage *inp_tile,
unsigned out_width, unsigned out_height)
{
unsigned x_scale_inp, x_scale_out, y_scale_inp, y_scale_out;
unsigned divisor;
unsigned size;
struct TileImage *out_tile;
unsigned x_inp, y_inp, x2, y2, x_out, y_out;
unsigned pos;
/* Derive the scale factors */
divisor = gcd(out_width, inp_tile->width);
x_scale_inp = inp_tile->width / divisor;
x_scale_out = out_width / divisor;
divisor = gcd(out_height, inp_tile->height);
y_scale_inp = inp_tile->height / divisor;
y_scale_out = out_height / divisor;
/* Derive the stretched tile */
out_tile = (struct TileImage *) alloc(sizeof(struct TileImage));
out_tile->width = out_width;
out_tile->height = out_height;
size = out_width * out_height;
if (inp_tile->pixels != NULL) {
out_tile->pixels = (struct Pixel *) alloc(size * sizeof(struct Pixel));
divisor = x_scale_inp * y_scale_inp;
for (y_out = 0; y_out < out_height; ++y_out) {
for (x_out = 0; x_out < out_width; ++x_out) {
unsigned r, g, b, a;
/* Derive output pixels by blending input pixels */
r = 0;
g = 0;
b = 0;
a = 0;
for (y2 = 0; y2 < y_scale_inp; ++y2) {
y_inp = (y_out * y_scale_inp + y2) / y_scale_out;
for (x2 = 0; x2 < x_scale_inp; ++x2) {
x_inp = (x_out * x_scale_inp + x2) / x_scale_out;
pos = y_inp * inp_tile->width + x_inp;
r += inp_tile->pixels[pos].r;
g += inp_tile->pixels[pos].g;
b += inp_tile->pixels[pos].b;
a += inp_tile->pixels[pos].a;
}
}
pos = y_out * out_width + x_out;
out_tile->pixels[pos].r = r / divisor;
out_tile->pixels[pos].g = g / divisor;
out_tile->pixels[pos].b = b / divisor;
out_tile->pixels[pos].a = a / divisor;
}
}
} else {
out_tile->pixels = NULL;
}
/* If the output device uses a palette, we can't blend; just pick
a subset of the pixels */
if (inp_tile->indexes != NULL) {
out_tile->indexes = (unsigned char *) alloc(size);
for (y_out = 0; y_out < out_height; ++y_out) {
for (x_out = 0; x_out < out_width; ++x_out) {
pos = y_out * out_width + x_out;
x_inp = x_out * x_scale_inp / x_scale_out;
y_inp = y_out * y_scale_inp / y_scale_out;
out_tile->indexes[pos] =
inp_tile->indexes[y_inp * inp_tile->width + x_inp];
}
}
} else {
out_tile->indexes = NULL;
}
return out_tile;
}
/* Free a tile returned by stretch_tile */
/* Do NOT use this with tiles returned by get_tile */
void
free_tile(struct TileImage *tile)
{
if (tile != NULL) {
free(tile->indexes);
free(tile->pixels);
free(tile);
}
}
/* Return the greatest common divisor */
static unsigned
gcd(unsigned a, unsigned b)
{
while (TRUE) {
if (b == 0) return a;
a %= b;
if (a == 0) return b;
b %= a;
}
}
static void
split_tiles(const struct TileSetImage *image)
{
unsigned tile_rows, tile_cols;
size_t tile_size, i, j;
unsigned x1, y1, x2, y2;
/* Get the number of tiles */
tile_rows = image->height / iflags.wc_tile_height;
tile_cols = image->width / iflags.wc_tile_width;
num_tiles = tile_rows * tile_cols;
tile_size = (size_t) iflags.wc_tile_height * (size_t) iflags.wc_tile_width;
/* Allocate the tile array */
tiles = (struct TileImage *) alloc(num_tiles * sizeof(tiles[0]));
memset(tiles, 0, num_tiles * sizeof(tiles[0]));
/* Copy the pixels into the tile structures */
for (y1 = 0; y1 < tile_rows; ++y1) {
for (x1 = 0; x1 < tile_cols; ++x1) {
struct TileImage *tile = &tiles[y1 * tile_cols + x1];
tile->width = iflags.wc_tile_width;
tile->height = iflags.wc_tile_height;
tile->pixels = (struct Pixel *)
alloc(tile_size * sizeof (struct Pixel));
if (image->indexes != NULL) {
tile->indexes = (unsigned char *) alloc(tile_size);
}
for (y2 = 0; y2 < (unsigned) iflags.wc_tile_height; ++y2) {
for (x2 = 0; x2 < (unsigned) iflags.wc_tile_width; ++x2) {
unsigned x = x1 * iflags.wc_tile_width + x2;
unsigned y = y1 * iflags.wc_tile_height + y2;
i = y * image->width + x;
j = y2 * tile->width + x2;
tile->pixels[j] = image->pixels[i];
if (image->indexes != NULL) {
tile->indexes[j] = image->indexes[i];
}
}
}
}
}
/* Create a blank tile for use when the tile index is invalid */
blank_tile.width = iflags.wc_tile_width;
blank_tile.height = iflags.wc_tile_height;
blank_tile.pixels = (struct Pixel *)
alloc(tile_size * sizeof (struct Pixel));
for (i = 0; i < tile_size; ++i) {
blank_tile.pixels[i].r = 0;
blank_tile.pixels[i].g = 0;
blank_tile.pixels[i].b = 0;
blank_tile.pixels[i].a = 255;
}
if (image->indexes) {
blank_tile.indexes = (unsigned char *) alloc(tile_size);
memset(blank_tile.indexes, 0, tile_size);
}
}
boolean
read_png_tiles(const char *filename UNUSED, struct TileSetImage *image UNUSED)
{
/* stub */
return FALSE;
}