/* 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; }