In previous versions of NetHack, setting -DUSE_TILES enabled the tile
support, while setting -DSUPPRESS_GRAPHICS produced a NetHack that
would write its TTY output to standard output, and rely on ANSI.SYS or
similar to do screen control. (USE_TILES is now TILES_IN_GLYPHMAP.)
This change ensures that the current NetHack can be built the same
ways.
One twist is that previous NetHacks would drop all support for graphical
modes when tiles were not supported. Thus sys/msdos/vid{vga,vesa}.c have
very disordered use of TILES_IN_GLYPHMAP. There was no need to check
this. But now, the graphical modes also support Unicode. A non-tiled
build should have the graphical modes, with only the text functions
present, provided that ENHANCED_SYMBOLS is defined.
Some unused and locally used symbols were cleaned up along the way.
356 lines
10 KiB
C
356 lines
10 KiB
C
/* NetHack 5.0 tileset.c $NHDT-Date: 1781973099 2026/06/20 16:31:39 $ $NHDT-Branch: NetHack-5.0 $:$NHDT-Revision: 1.6 $ */
|
|
/* Copyright (c) Ray Chason, 2016. */
|
|
/* NetHack may be freely redistributed. See license for details. */
|
|
|
|
#include "config.h"
|
|
|
|
#ifdef TILES_IN_GLYPHMAP
|
|
|
|
#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;
|
|
|
|
if (tiles != NULL)
|
|
return TRUE;
|
|
|
|
/* 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, 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) {
|
|
unsigned y = y1 * iflags.wc_tile_height + y2;
|
|
unsigned x = x1 * iflags.wc_tile_width;
|
|
i = y * image->width;
|
|
j = y2 * tile->width;
|
|
memcpy(tile->pixels + j, image->pixels + i + x,
|
|
sizeof(tile->pixels[0]) * iflags.wc_tile_width);
|
|
if (image->indexes != NULL) {
|
|
memcpy(tile->indexes + j, image->indexes + i + x,
|
|
iflags.wc_tile_width);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
#endif /* TILES_IN_GLYPHMAP */
|