/* NetHack 3.7 bmptiles.c $NHDT-Date: 1596498334 2020/08/03 23:45:34 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.1 $ */ /* Copyright (c) Ray Chason, 2016. */ /* NetHack may be freely redistributed. See license for details. */ #include "config.h" #include "tileset.h" /* First BMP file header */ struct BitmapHeader { char magic[2]; uint32 bmp_size; uint32 img_offset; }; /* Color model information for larger BMP headers */ struct CIE_XYZ { uint32 ciexyzX; uint32 ciexyzY; uint32 ciexyzZ; }; struct CIE_XYZTriple { struct CIE_XYZ ciexyzRed; struct CIE_XYZ ciexyzGreen; struct CIE_XYZ ciexyzBlue; }; /* Second BMP file header */ /* This one can vary in size; contents can vary according to the size */ struct BitmapInfoHeader { uint32 Size; /* 12 40 52 56 108 124 64 */ int32 Width; /* 12 40 52 56 108 124 64 */ int32 Height; /* 12 40 52 56 108 124 64 */ uint16 NumPlanes; /* 12 40 52 56 108 124 64 */ uint16 BitsPerPixel; /* 12 40 52 56 108 124 64 */ uint32 Compression; /* 40 52 56 108 124 64 */ uint32 ImageDataSize; /* 40 52 56 108 124 64 */ int32 XResolution; /* 40 52 56 108 124 64 */ int32 YResolution; /* 40 52 56 108 124 64 */ uint32 ColorsUsed; /* 40 52 56 108 124 64 */ uint32 ColorsImportant; /* 40 52 56 108 124 64 */ uint32 RedMask; /* 52 56 108 124 */ uint32 GreenMask; /* 52 56 108 124 */ uint32 BlueMask; /* 52 56 108 124 */ uint32 AlphaMask; /* 56 108 124 */ uint32 CSType; /* 108 124 */ struct CIE_XYZTriple Endpoints; /* 108 124 */ uint32 GammaRed; /* 108 124 */ uint32 GammaGreen; /* 108 124 */ uint32 GammaBlue; /* 108 124 */ uint32 Intent; /* 124 */ uint32 ProfileData; /* 124 */ uint32 ProfileSize; /* 124 */ }; /* Compression */ #define BI_RGB 0 #define BI_RLE8 1 #define BI_RLE4 2 #define BI_BITFIELDS 3 #define BI_JPEG 4 #define BI_PNG 5 static uint16 read_u16(const unsigned char buf[2]); static uint32 read_u32(const unsigned char buf[4]); static int32 read_s32(const unsigned char buf[4]); static struct Pixel build_pixel(const struct BitmapInfoHeader *, uint32); static unsigned char pixel_element(uint32, uint32); static boolean read_header(FILE *, struct BitmapHeader *); static boolean read_info_header(FILE *, struct BitmapInfoHeader *); static boolean check_info_header(const struct BitmapInfoHeader *); static unsigned get_palette_size(const struct BitmapInfoHeader *); static boolean read_palette(FILE *, struct Pixel *, unsigned); /* Read a .BMP file into the image structure */ /* Return TRUE if successful, FALSE on any error */ boolean read_bmp_tiles(const char *filename, struct TileSetImage *image) { struct BitmapHeader header1; struct BitmapInfoHeader header2; unsigned palette_size; size_t num_pixels, size; unsigned x, y, y_start, y_end, y_inc; unsigned bytes_per_row; FILE *fp = NULL; /* custodial */ unsigned char *row_bytes = NULL; /* custodial */ image->width = 0; image->height = 0; image->pixels = NULL; /* custodial, returned */ image->indexes = NULL; /* custodial, returned */ image->image_desc = NULL; /* custodial, returned */ image->tile_width = 0; image->tile_height = 0; fp = fopen(filename, "rb"); if (fp == NULL) goto error; /* Read the headers */ if (!read_header(fp, &header1)) goto error; if (memcmp(header1.magic, "BM", 2) != 0) goto error; if (!read_info_header(fp, &header2)) goto error; if (!check_info_header(&header2)) goto error; /* header2.Height < 0 means the Y coordinate is reversed; the origin is * top left rather than bottom left */ image->width = header2.Width; image->height = labs(header2.Height); /* Allocate pixel area; watch out for overflow */ num_pixels = (size_t) image->width * (size_t) image->height; if (num_pixels / image->width != image->height) goto error; /* overflow */ size = num_pixels * sizeof(image->pixels[0]); if (size / sizeof(image->pixels[0]) != num_pixels) goto error; /* overflow */ image->pixels = (struct Pixel *) alloc(size); if (header2.BitsPerPixel <= 8) { image->indexes = (unsigned char *) alloc(num_pixels); } /* Read the palette */ palette_size = get_palette_size(&header2); if (!read_palette(fp, image->palette, palette_size)) goto error; /* Read the pixels */ fseek(fp, header1.img_offset, SEEK_SET); if (header2.Height < 0) { y_start = 0; y_end = image->height; y_inc = 1; } else { y_start = image->height - 1; y_end = (unsigned) -1; y_inc = -1; } if (header2.Compression == BI_RLE4 || header2.Compression == BI_RLE8) { unsigned char *p; p = image->indexes; memset(p, 0, num_pixels); x = 0; y = image->height - 1; while (TRUE) { int b1, b2; b1 = fgetc(fp); if (b1 == EOF) goto error; b2 = fgetc(fp); if (b2 == EOF) goto error; /* * b1 b2 * 0 0 end of line * 0 1 end of bitmap * 0 2 next two bytes are x and y offset * 0 >2 b2 is a count of bytes * >0 any repeat b2, b1 times */ if (b1 == 0) { if (b2 == 0) { /* end of line */ --y; x = 0; } else if (b2 == 1) { /* end of bitmap */ break; } else if (b2 == 2) { /* next two bytes are x and y offset */ b1 = fgetc(fp); if (b1 == EOF) break; b2 = fgetc(fp); if (b2 == EOF) break; x += b1; y += b2; } else { /* get bytes */ int i; if (y < image->height) { p = image->indexes + y * image->width; for (i = 0; i < b2; ++i) { b1 = fgetc(fp); if (b1 == EOF) break; if (header2.BitsPerPixel == 8) { if (x < image->width) { p[x] = b1; } ++x; } else { if (x < image->width) { p[x] = b1 >> 4; } ++x; if (x < image->width) { p[x] = b1 & 0xF; } ++x; } } if (b2 & 1) { b1 = fgetc(fp); if (b1 == EOF) break; } } } } else { /* repeat b2, b1 times */ int i; if (y < image->height) { p = image->indexes + y * image->width; for (i = 0; i < b1; ++i) { if (header2.BitsPerPixel == 8) { if (x < image->width) { p[x] = b2; } ++x; } else { if (x < image->width) { p[x] = b2 >> 4; } ++x; if (x < image->width) { p[x] = b2 & 0xF; } ++x; } } } } } } else { bytes_per_row = (image->width * header2.BitsPerPixel + 31) / 32 * 4; row_bytes = (unsigned char *) alloc(bytes_per_row); if (header2.Compression == BI_RGB) { switch (header2.BitsPerPixel) { case 16: header2.RedMask = 0x001F; header2.GreenMask = 0x07E0; header2.BlueMask = 0xF800; header2.AlphaMask = 0x0000; break; case 32: header2.RedMask = 0x000000FF; header2.GreenMask = 0x0000FF00; header2.BlueMask = 0x00FF0000; header2.AlphaMask = 0xFF000000; break; } } for (y = y_start; y != y_end; y += y_inc) { struct Pixel *row = image->pixels + y * image->width; unsigned char *ind = image->indexes + y * image->width; size = fread(row_bytes, 1, bytes_per_row, fp); if (size < bytes_per_row) goto error; switch (header2.BitsPerPixel) { case 1: for (x = 0; x < image->width; ++x) { unsigned byte = x / 8; unsigned shift = x % 8; unsigned color = (row_bytes[byte] >> shift) & 1; ind[x] = color; } break; case 4: for (x = 0; x < image->width; ++x) { unsigned byte = x / 2; unsigned shift = (x % 2) * 4; unsigned color = (row_bytes[byte] >> shift) & 1; ind[x] = color; } break; case 8: for (x = 0; x < image->width; ++x) { ind[x] = row_bytes[x]; } break; case 16: for (x = 0; x < image->width; ++x) { uint16 color = read_u16(row_bytes + x * 2); row[x] = build_pixel(&header2, color); } break; case 24: for (x = 0; x < image->width; ++x) { row[x].r = row_bytes[x * 3 + 2]; row[x].g = row_bytes[x * 3 + 1]; row[x].b = row_bytes[x * 3 + 0]; row[x].a = 255; } break; case 32: for (x = 0; x < image->width; ++x) { uint32 color = read_u32(row_bytes + x * 4); row[x] = build_pixel(&header2, color); } break; } } free(row_bytes); row_bytes = NULL; } if (image->indexes != NULL) { size_t i; for (i = 0; i < num_pixels; ++i) { image->pixels[i] = image->palette[image->indexes[i]]; } } fclose(fp); return TRUE; error: if (fp) fclose(fp); free(row_bytes); free(image->pixels); image->pixels = NULL; free(image->indexes); image->indexes = NULL; free(image->image_desc); image->image_desc = NULL; return FALSE; } /* Read and decode the first header */ static boolean read_header(FILE *fp, struct BitmapHeader *header) { unsigned char buf[14]; size_t size; size = fread(buf, 1, sizeof(buf), fp); if (size < sizeof(buf)) return FALSE; memcpy(header->magic, buf + 0, 2); header->bmp_size = read_u32(buf + 2); /* 6 and 8 are 16 bit integers giving the hotspot of a cursor */ header->img_offset = read_u32(buf + 10); return TRUE; } /* Read and decode the second header */ static boolean read_info_header(FILE *fp, struct BitmapInfoHeader *header) { unsigned char buf[124]; /* maximum size */ size_t size; boolean have_color_mask; memset(header, 0, sizeof(*header)); /* Get the header size */ size = fread(buf, 1, 4, fp); if (size < 4) return FALSE; header->Size = read_u32(buf + 0); if (header->Size > sizeof(buf)) return FALSE; /* Get the rest of the header */ size = fread(buf + 4, 1, header->Size - 4, fp); if (size < header->Size - 4) return FALSE; have_color_mask = FALSE; switch (header->Size) { case 124: /* BITMAPV5INFOHEADER */ /* 120 is reserved */ header->ProfileSize = read_u32(buf + 116); header->ProfileData = read_u32(buf + 112); header->Intent = read_u32(buf + 108); /* fall through */ case 108: /* BITMAPV4INFOHEADER */ header->GammaBlue = read_u32(buf + 104); header->GammaGreen = read_u32(buf + 100); header->GammaRed = read_u32(buf + 96); header->Endpoints.ciexyzBlue.ciexyzZ = read_u32(buf + 92); header->Endpoints.ciexyzBlue.ciexyzY = read_u32(buf + 88); header->Endpoints.ciexyzBlue.ciexyzX = read_u32(buf + 84); header->Endpoints.ciexyzGreen.ciexyzZ = read_u32(buf + 80); header->Endpoints.ciexyzGreen.ciexyzY = read_u32(buf + 76); header->Endpoints.ciexyzGreen.ciexyzX = read_u32(buf + 72); header->Endpoints.ciexyzRed.ciexyzZ = read_u32(buf + 68); header->Endpoints.ciexyzRed.ciexyzY = read_u32(buf + 64); header->Endpoints.ciexyzRed.ciexyzX = read_u32(buf + 60); header->CSType = read_u32(buf + 56); /* fall through */ case 56: /* BITMAPV3INFOHEADER */ header->AlphaMask = read_u32(buf + 52); /* fall through */ case 52: /* BITMAPV2INFOHEADER */ header->BlueMask = read_u32(buf + 48); header->GreenMask = read_u32(buf + 44); header->RedMask = read_u32(buf + 40); have_color_mask = TRUE; /* fall through */ case 40: /* BITMAPINFOHEADER */ case 64: /* OS22XBITMAPHEADER */ /* The last 24 bytes in OS22XBITMAPHEADER are incompatible with the * later Microsoft versions of the header */ header->ColorsImportant = read_u32(buf + 36); header->ColorsUsed = read_u32(buf + 32); header->YResolution = read_s32(buf + 28); header->XResolution = read_s32(buf + 24); header->ImageDataSize = read_u32(buf + 20); header->Compression = read_u32(buf + 16); header->BitsPerPixel = read_u16(buf + 14); header->NumPlanes = read_u16(buf + 12); header->Height = read_s32(buf + 8); header->Width = read_s32(buf + 4); break; case 12: /* BITMAPCOREHEADER */ header->BitsPerPixel = read_u16(buf + 10); header->NumPlanes = read_u16(buf + 8); header->Height = read_u16(buf + 6); header->Width = read_u16(buf + 4); break; default: return FALSE; } /* For BI_BITFIELDS, the next three 32 bit words are the color masks */ if (header->Compression == BI_BITFIELDS && !have_color_mask) { size = fread(buf, 1, 12, fp); if (size < 12) return FALSE; header->RedMask = read_u32(buf + 0); header->GreenMask = read_u32(buf + 4); header->BlueMask = read_u32(buf + 8); } return TRUE; } /* Check the second header for consistency and unsupported features */ static boolean check_info_header(const struct BitmapInfoHeader *header) { if (header->NumPlanes != 1) return FALSE; switch (header->BitsPerPixel) { #if 0 /* TODO */ case 0: if (header->Compression != BI_PNG) return FALSE; /* JPEG not supported */ break; #endif case 1: case 24: if (header->Compression != BI_RGB) return FALSE; break; case 4: if (header->Compression != BI_RGB && header->Compression != BI_RLE4) return FALSE; break; case 8: if (header->Compression != BI_RGB && header->Compression != BI_RLE8) return FALSE; break; case 16: case 32: if (header->Compression != BI_RGB && header->Compression != BI_BITFIELDS) return FALSE; /* Any of the color masks could conceivably be zero; the bitmap, though * limited, would still be meaningful */ if (header->Compression == BI_BITFIELDS && header->RedMask == 0 && header->GreenMask == 0 && header->BlueMask == 0) return FALSE; break; default: return FALSE; } if (header->Height < 0 && header->Compression != BI_RGB && header->Compression != BI_BITFIELDS) return FALSE; return TRUE; } /* Return the number of palette entries to read from the file */ static unsigned get_palette_size(const struct BitmapInfoHeader *header) { switch (header->BitsPerPixel) { case 1: return 2; case 4: return header->ColorsUsed ? header->ColorsUsed : 16; case 8: return header->ColorsUsed ? header->ColorsUsed : 256; default: return 0; } } /* * Read the palette from the file * palette_size is the number of entries to read, but no more than 256 will * be written into the palette array * Return TRUE if successful, FALSE on any error */ static boolean read_palette(FILE *fp, struct Pixel *palette, unsigned palette_size) { unsigned i; unsigned char buf[4]; unsigned read_size; read_size = (palette_size < 256) ? palette_size : 256; for (i = 0; i < read_size; ++i) { size_t size = fread(buf, 1, sizeof(buf), fp); if (size < sizeof(buf)) return FALSE; palette[i].b = buf[0]; palette[i].g = buf[1]; palette[i].r = buf[2]; palette[i].a = 255; } for (; i < 256; ++i) { palette[i].b = 0; palette[i].g = 0; palette[i].r = 0; palette[i].a = 255; } fseek(fp, 4 * (palette_size - read_size), SEEK_CUR); return TRUE; } /* Decode an unsigned 16 bit quantity */ static uint16 read_u16(const unsigned char buf[2]) { return ((uint16)buf[0] << 0) | ((uint16)buf[1] << 8); } /* Decode an unsigned 32 bit quantity */ static uint32 read_u32(const unsigned char buf[4]) { return ((uint32)buf[0] << 0) | ((uint32)buf[1] << 8) | ((uint32)buf[2] << 16) | ((uint32)buf[3] << 24); } /* Decode a signed 32 bit quantity */ static int32 read_s32(const unsigned char buf[4]) { return (int32)((read_u32(buf) ^ 0x80000000) - 0x80000000); } /* Build a pixel structure, given the mask words in the second header and * a packed 16 or 32 bit pixel */ static struct Pixel build_pixel(const struct BitmapInfoHeader *header, uint32 color) { struct Pixel pixel; pixel.r = pixel_element(header->RedMask, color); pixel.g = pixel_element(header->GreenMask, color); pixel.b = pixel_element(header->BlueMask, color); pixel.a = header->AlphaMask ? pixel_element(header->AlphaMask, color) : 255; return pixel; } /* Extract one element (red, green, blue or alpha) from a pixel */ static unsigned char pixel_element(uint32 mask, uint32 color) { uint32 bits, shift; if (mask == 0) return 0; bits = 0xFFFF; /* 0xFF, 0xF, 0x3, 0x1 */ shift = 16; /* 8, 4, 2, 1 */ while (shift != 0) { if ((mask & bits) == 0) { mask >>= shift; color >>= shift; } shift /= 2; bits >>= shift; } color &= mask; return color * 255 / mask; }