From 8520285a10a0a82c9d1009ca6d61c39b48724842 Mon Sep 17 00:00:00 2001 From: Denis Pauk Date: Tue, 30 Jul 2024 09:04:34 +0300 Subject: [PATCH] renders: support 24bit pcx images Based on ffmpeg code and checked with https://samples.ffmpeg.org/image-samples/pcx/ --- src/client/cl_cin.c | 16 +- src/client/cl_image.c | 16 +- src/client/header/screen.h | 2 +- src/client/refresh/files/pcx.c | 489 ++++++++++++++++++++++---------- src/client/refresh/files/stb.c | 6 +- src/client/refresh/ref_shared.h | 3 +- 6 files changed, 361 insertions(+), 171 deletions(-) diff --git a/src/client/cl_cin.c b/src/client/cl_cin.c index 6b244ca8..30cc212c 100644 --- a/src/client/cl_cin.c +++ b/src/client/cl_cin.c @@ -68,19 +68,19 @@ cinematics_t cin; static void SCR_LoadPCX(char *filename, byte **pic, byte **palette, int *width, int *height, - int *bitesPerPixel) + int *bitsPerPixel) { byte *data, *palette_in; *pic = NULL; *palette = NULL; - SCR_LoadImageWithPalette(filename, &data, &palette_in, width, height, bitesPerPixel); + SCR_LoadImageWithPalette(filename, &data, &palette_in, width, height, bitsPerPixel); if (data) { - *pic = Z_Malloc((*width) * (*height) * (*bitesPerPixel) / 8); - memcpy(*pic, data, (*height) * (*width) * (*bitesPerPixel) / 8); + *pic = Z_Malloc((*width) * (*height) * (*bitsPerPixel) / 8); + memcpy(*pic, data, (*height) * (*width) * (*bitsPerPixel) / 8); free(data); } else @@ -567,7 +567,7 @@ SCR_DrawCinematic(void) static byte * SCR_LoadHiColor(const char* namewe, const char *ext, int *width, int *height, - int *bitesPerPixel) + int *bitsPerPixel) { byte *pic, *data = NULL, *palette = NULL; char filename[256]; @@ -577,7 +577,7 @@ SCR_LoadHiColor(const char* namewe, const char *ext, int *width, int *height, Q_strlcat(filename, ext, sizeof(filename)); SCR_LoadImageWithPalette(filename, &data, &palette, - width, height, bitesPerPixel); + width, height, bitsPerPixel); if (data == NULL) { return NULL; @@ -589,8 +589,8 @@ SCR_LoadHiColor(const char* namewe, const char *ext, int *width, int *height, free(palette); } - pic = Z_Malloc(cin.height * cin.width * (*bitesPerPixel) / 8); - memcpy(pic, data, cin.height * cin.width * (*bitesPerPixel) / 8); + pic = Z_Malloc(cin.height * cin.width * (*bitsPerPixel) / 8); + memcpy(pic, data, cin.height * cin.width * (*bitsPerPixel) / 8); free(data); return pic; diff --git a/src/client/cl_image.c b/src/client/cl_image.c index 3784753a..a46232cd 100644 --- a/src/client/cl_image.c +++ b/src/client/cl_image.c @@ -95,7 +95,7 @@ PCX_RLE_Decode(byte *pix, byte *pix_max, const byte *raw, const byte *raw_max, static void PCX_Decode(const char *name, const byte *raw, int len, byte **pic, byte **palette, - int *width, int *height, int *bitesPerPixel) + int *width, int *height, int *bitsPerPixel) { const pcx_t *pcx; int full_size; @@ -105,7 +105,7 @@ PCX_Decode(const char *name, const byte *raw, int len, byte **pic, byte **palett const byte *data; *pic = NULL; - *bitesPerPixel = 8; + *bitsPerPixel = 8; if (palette) { @@ -144,7 +144,7 @@ PCX_Decode(const char *name, const byte *raw, int len, byte **pic, byte **palett if (pcx->color_planes == 3 && pcx->bits_per_pixel == 8) { full_size *= 4; - *bitesPerPixel = 32; + *bitsPerPixel = 32; } out = malloc(full_size); @@ -366,7 +366,7 @@ PCX_Decode(const char *name, const byte *raw, int len, byte **pic, byte **palett void SCR_LoadImageWithPalette(char *filename, byte **pic, byte **palette, - int *width, int *height, int *bitesPerPixel) + int *width, int *height, int *bitsPerPixel) { const char* ext; int len, ident; @@ -392,11 +392,11 @@ SCR_LoadImageWithPalette(char *filename, byte **pic, byte **palette, ident = LittleShort(*((short*)raw)); if (!strcmp(ext, "pcx") && (ident == PCX_IDENT)) { - PCX_Decode(filename, raw, len, pic, palette, width, height, bitesPerPixel); + PCX_Decode(filename, raw, len, pic, palette, width, height, bitsPerPixel); } else { - int sourcebitesPerPixel = 0; + int sourcebitsPerPixel = 0; /* other formats does not have palette directly */ if (palette) @@ -405,7 +405,7 @@ SCR_LoadImageWithPalette(char *filename, byte **pic, byte **palette, } *pic = stbi_load_from_memory(raw, len, width, height, - &sourcebitesPerPixel, STBI_rgb_alpha); + &sourcebitsPerPixel, STBI_rgb_alpha); if (*pic == NULL) { @@ -413,7 +413,7 @@ SCR_LoadImageWithPalette(char *filename, byte **pic, byte **palette, __func__, filename, stbi_failure_reason()); } - *bitesPerPixel = 32; + *bitsPerPixel = 32; } FS_FreeFile(raw); diff --git a/src/client/header/screen.h b/src/client/header/screen.h index 609757c9..b4c8fe76 100644 --- a/src/client/header/screen.h +++ b/src/client/header/screen.h @@ -60,7 +60,7 @@ void SCR_AddDirtyPoint(int x, int y); void SCR_DirtyScreen(void); void SCR_LoadImageWithPalette(char *filename, byte **pic, byte **palette, - int *width, int *height, int *bitesPerPixel); + int *width, int *height, int *bitsPerPixel); void SCR_PlayCinematic(char *name); qboolean SCR_DrawCinematic(void); void SCR_RunCinematic(void); diff --git a/src/client/refresh/files/pcx.c b/src/client/refresh/files/pcx.c index 07b91f35..54d929e9 100644 --- a/src/client/refresh/files/pcx.c +++ b/src/client/refresh/files/pcx.c @@ -116,18 +116,338 @@ fixQuitScreen(byte* px) } } -void -LoadPCX(const char *origname, byte **pic, byte **palette, int *width, int *height) +static const byte * +PCX_RLE_Decode(byte *pix, byte *pix_max, const byte *raw, const byte *raw_max, + int bytes_per_line, qboolean *image_issues) { - byte *raw; - pcx_t *pcx; - int x, y; - int len, full_size; - int pcx_width, pcx_height; + int x; + + for (x = 0; x < bytes_per_line; ) + { + int runLength; + byte dataByte; + + if (raw >= raw_max) + { + // no place for read + *image_issues = true; + return raw; + } + dataByte = *raw++; + + if ((dataByte & 0xC0) == 0xC0) + { + runLength = dataByte & 0x3F; + if (raw >= raw_max) + { + // no place for read + *image_issues = true; + return raw; + } + dataByte = *raw++; + } + else + { + runLength = 1; + } + + while (runLength-- > 0) + { + if (pix_max <= (pix + x)) + { + // no place for write + *image_issues = true; + return raw; + } + else + { + pix[x++] = dataByte; + } + } + } + return raw; +} + +static void +PCX_Decode(const char *name, const byte *raw, int len, byte **pic, byte **palette, + int *width, int *height, int *bitsPerPixel) +{ + const pcx_t *pcx; + int full_size; + int pcx_width, pcx_height, bytes_per_line; qboolean image_issues = false; - int dataByte, runLength; byte *out, *pix; + const byte *data; + + *pic = NULL; + *bitsPerPixel = 8; + + if (palette) + { + *palette = NULL; + } + + if (len < sizeof(pcx_t)) + { + return; + } + + /* parse the PCX file */ + pcx = (const pcx_t *)raw; + + data = &pcx->data; + + bytes_per_line = LittleShort(pcx->bytes_per_line); + pcx_width = LittleShort(pcx->xmax) - LittleShort(pcx->xmin); + pcx_height = LittleShort(pcx->ymax) - LittleShort(pcx->ymin); + + if ((pcx->manufacturer != 0x0a) || + (pcx->version != 5) || + (pcx->encoding != 1) || + (pcx_width <= 0) || + (pcx_height <= 0) || + (bytes_per_line <= 0) || + (pcx->color_planes <= 0) || + (pcx->bits_per_pixel <= 0)) + { + R_Printf(PRINT_ALL, "%s: Bad pcx file %s: version: %d:%d, encoding: %d\n", + __func__, name, pcx->manufacturer, pcx->version, pcx->encoding); + return; + } + + full_size = (pcx_height + 1) * (pcx_width + 1); + if (pcx->color_planes == 3 && pcx->bits_per_pixel == 8) + { + full_size *= 4; + *bitsPerPixel = 32; + } + + out = malloc(full_size); + if (!out) + { + R_Printf(PRINT_ALL, "%s: Can't allocate for %s\n", __func__, name); + return; + } + + *pic = out; + + pix = out; + + if (width) + { + *width = pcx_width + 1; + } + + if (height) + { + *height = pcx_height + 1; + } + + if ((pcx->color_planes == 3 || pcx->color_planes == 4) + && pcx->bits_per_pixel == 8) + { + int x, y, linesize; + byte *line; + + if (bytes_per_line <= pcx_width) + { + image_issues = true; + } + + /* clean image alpha */ + memset(pix, 255, full_size); + + linesize = Q_max(bytes_per_line, pcx_width + 1) * pcx->color_planes; + line = malloc(linesize); + + for (y = 0; y <= pcx_height; y++, pix += (pcx_width + 1) * 4) + { + data = PCX_RLE_Decode(line, line + linesize, + data, (byte *)pcx + len, + bytes_per_line * pcx->color_planes, &image_issues); + + for (x = 0; x <= pcx_width; x++) { + int j; + + for (j = 0; j < pcx->color_planes; j++) + { + pix[4 * x + j] = line[x + bytes_per_line * j]; + } + } + } + + free(line); + } + else if (pcx->bits_per_pixel == 1) + { + byte *line; + int y; + + if (palette) + { + *palette = malloc(768); + + if (!(*palette)) + { + R_Printf(PRINT_ALL, "%s: Can't allocate for %s\n", __func__, name); + free(out); + return; + } + + memcpy(*palette, pcx->palette, sizeof(pcx->palette)); + } + + line = malloc(bytes_per_line * pcx->color_planes); + + for (y = 0; y <= pcx_height; y++, pix += pcx_width + 1) + { + int x; + + data = PCX_RLE_Decode(line, line + bytes_per_line * pcx->color_planes, + data, (byte *)pcx + len, + bytes_per_line * pcx->color_planes, &image_issues); + + for (x = 0; x <= pcx_width; x++) + { + int m, i, v; + + m = 0x80 >> (x & 7); + v = 0; + + for (i = pcx->color_planes - 1; i >= 0; i--) { + v <<= 1; + v += (line[i * bytes_per_line + (x >> 3)] & m) ? 1 : 0; + } + pix[x] = v; + } + } + free(line); + } + else if (pcx->color_planes == 1 && pcx->bits_per_pixel == 8) + { + int y, linesize; + byte *line; + + if (palette) + { + *palette = malloc(768); + + if (!(*palette)) + { + R_Printf(PRINT_ALL, "%s: Can't allocate for %s\n", __func__, name); + free(out); + return; + } + + if ((len > 768) && (((byte *)pcx)[len - 769] == 0x0C)) + { + memcpy(*palette, (byte *)pcx + len - 768, 768); + } + else + { + image_issues = true; + } + } + + if (bytes_per_line <= pcx_width) + { + image_issues = true; + } + + linesize = Q_max(bytes_per_line, pcx_width + 1); + line = malloc(linesize); + for (y = 0; y <= pcx_height; y++, pix += pcx_width + 1) + { + data = PCX_RLE_Decode(line, line + linesize, + data, (byte *)pcx + len, + bytes_per_line, &image_issues); + /* copy only visible part */ + memcpy(pix, line, pcx_width + 1); + } + free(line); + } + else if (pcx->color_planes == 1 && + (pcx->bits_per_pixel == 2 || pcx->bits_per_pixel == 4)) + { + int y; + + byte *line; + + if (palette) + { + *palette = malloc(768); + + if (!(*palette)) + { + R_Printf(PRINT_ALL, "%s: Can't allocate for %s\n", __func__, name); + free(out); + return; + } + + memcpy(*palette, pcx->palette, sizeof(pcx->palette)); + } + + line = malloc(bytes_per_line); + + for (y = 0; y <= pcx_height; y++, pix += pcx_width + 1) + { + int x, mask, div; + + data = PCX_RLE_Decode(line, line + bytes_per_line, + data, (byte *)pcx + len, + bytes_per_line, &image_issues); + + mask = (1 << pcx->bits_per_pixel) - 1; + div = 8 / pcx->bits_per_pixel; + + for (x = 0; x <= pcx_width; x++) + { + unsigned v, shift; + + v = line[x / div] & 0xFF; + /* for 2 bits: + * 0 -> 6 + * 1 -> 4 + * 3 -> 2 + * 4 -> 0 + */ + shift = pcx->bits_per_pixel * ((div - 1) - x % div); + pix[x] = (v >> shift) & mask; + } + } + + free(line); + } + else + { + R_Printf(PRINT_ALL, "%s: Bad pcx file %s: planes: %d, bits: %d\n", + __func__, name, pcx->color_planes, pcx->bits_per_pixel); + free(*pic); + *pic = NULL; + } + + if (data - (byte *)pcx > len) + { + R_Printf(PRINT_DEVELOPER, "%s: %s file was malformed\n", + __func__, name); + free(*pic); + *pic = NULL; + } + + if (image_issues) + { + R_Printf(PRINT_ALL, "%s: %s file has possible size issues.\n", + __func__, name); + } +} + +void +LoadPCX(const char *origname, byte **pic, byte **palette, int *width, int *height, + int *bitsPerPixel) +{ char filename[256]; + byte *raw; + int len; FixFileExt(origname, "pcx", filename, sizeof(filename)); @@ -140,156 +460,25 @@ LoadPCX(const char *origname, byte **pic, byte **palette, int *width, int *heigh /* load the file */ len = ri.FS_LoadFile(filename, (void **)&raw); - - if (!raw || len < sizeof(pcx_t)) + if (!raw) { - R_Printf(PRINT_DEVELOPER, "Bad pcx file %s\n", filename); + /* no such file */ return; } - /* parse the PCX file */ - pcx = (pcx_t *)raw; + PCX_Decode(filename, raw, len, pic, palette, width, height, bitsPerPixel); - pcx->xmin = LittleShort(pcx->xmin); - pcx->ymin = LittleShort(pcx->ymin); - pcx->xmax = LittleShort(pcx->xmax); - pcx->ymax = LittleShort(pcx->ymax); - pcx->hres = LittleShort(pcx->hres); - pcx->vres = LittleShort(pcx->vres); - pcx->bytes_per_line = LittleShort(pcx->bytes_per_line); - pcx->palette_type = LittleShort(pcx->palette_type); - - raw = &pcx->data; - - pcx_width = pcx->xmax - pcx->xmin; - pcx_height = pcx->ymax - pcx->ymin; - - if ((pcx->manufacturer != 0x0a) || (pcx->version != 5) || - (pcx->encoding != 1) || (pcx->bits_per_pixel != 8) || - (pcx_width >= 4096) || (pcx_height >= 4096)) - { - R_Printf(PRINT_ALL, "Bad pcx file %s\n", filename); - ri.FS_FreeFile(pcx); - return; - } - - if (pcx->bytes_per_line <= pcx_width) - { - pcx->bytes_per_line = pcx_width + 1; - image_issues = true; - } - - full_size = (pcx_height + 1) * (pcx_width + 1); - out = malloc(full_size); - if (!out) - { - R_Printf(PRINT_ALL, "Can't allocate\n"); - ri.FS_FreeFile(pcx); - return; - } - - *pic = out; - - pix = out; - - if (palette) - { - *palette = malloc(768); - if (!(*palette)) - { - R_Printf(PRINT_ALL, "Can't allocate\n"); - free(out); - ri.FS_FreeFile(pcx); - return; - } - if (len > 768) - { - memcpy(*palette, (byte *)pcx + len - 768, 768); - } - else - { - image_issues = true; - } - } - - if (width) - { - *width = pcx_width + 1; - } - - if (height) - { - *height = pcx_height + 1; - } - - for (y = 0; y <= pcx_height; y++, pix += pcx_width + 1) - { - for (x = 0; x < pcx->bytes_per_line; ) - { - if (raw - (byte *)pcx > len) - { - // no place for read - image_issues = true; - x = pcx_width; - break; - } - dataByte = *raw++; - - if ((dataByte & 0xC0) == 0xC0) - { - runLength = dataByte & 0x3F; - if (raw - (byte *)pcx > len) - { - // no place for read - image_issues = true; - x = pcx_width; - break; - } - dataByte = *raw++; - } - else - { - runLength = 1; - } - - while (runLength-- > 0) - { - if ((*pic + full_size) <= (pix + x)) - { - // no place for write - image_issues = true; - x += runLength; - runLength = 0; - } - else - { - pix[x++] = dataByte; - } - } - } - } - - if (raw - (byte *)pcx > len) - { - R_Printf(PRINT_DEVELOPER, "PCX file %s was malformed", filename); - free(*pic); - *pic = NULL; - } - else if(pcx_width == 319 && pcx_height == 239 - && Q_strcasecmp(filename, "pics/quit.pcx") == 0 - && Com_BlockChecksum(pcx, len) == 3329419434u) + if(*pic && *bitsPerPixel == 8 && width && height + && *width == 319 && *height == 239 + && Q_strcasecmp(filename, "pics/quit.pcx") == 0 + && Com_BlockChecksum(raw, len) == 3329419434u) { // it's the quit screen, and the baseq2 one (identified by checksum) // so fix it fixQuitScreen(*pic); } - if (image_issues) - { - R_Printf(PRINT_ALL, "PCX file %s has possible size issues.\n", filename); - } - - ri.FS_FreeFile(pcx); + ri.FS_FreeFile(raw); } void @@ -327,11 +516,11 @@ void GetPCXPalette (byte **colormap, unsigned *d_8to24table) { byte *pal; - int i; + int i, bitsPerPixel; /* get the palette and colormap */ - LoadPCX ("pics/colormap.pcx", colormap, &pal, NULL, NULL); - if (!*colormap || !pal) + LoadPCX ("pics/colormap.pcx", colormap, &pal, NULL, NULL, &bitsPerPixel); + if (!*colormap || !pal || bitsPerPixel != 8) { ri.Sys_Error (ERR_FATAL, "%s: Couldn't load pics/colormap.pcx", __func__); diff --git a/src/client/refresh/files/stb.c b/src/client/refresh/files/stb.c index 858bb6df..4f0ea195 100644 --- a/src/client/refresh/files/stb.c +++ b/src/client/refresh/files/stb.c @@ -509,16 +509,16 @@ R_LoadImage(const char *name, const char* namewe, const char *ext, imagetype_t t { byte *pic = NULL; byte *palette = NULL; - int width = 0, height = 0; + int width = 0, height = 0, bitsPerPixel = 8; - LoadPCX (namewe, &pic, &palette, &width, &height); + LoadPCX (namewe, &pic, &palette, &width, &height, &bitsPerPixel); if (!pic) return NULL; image = load_image(name, pic, width, width, height, height, - width * height, type, 8); + width * height, type, bitsPerPixel); if (palette) { diff --git a/src/client/refresh/ref_shared.h b/src/client/refresh/ref_shared.h index a8421d79..9e90df74 100644 --- a/src/client/refresh/ref_shared.h +++ b/src/client/refresh/ref_shared.h @@ -85,7 +85,8 @@ extern struct image_s* LoadM8(const char *origname, imagetype_t type, loadimage_ extern struct image_s* LoadM32(const char *origname, imagetype_t type, loadimage_t load_image); extern void FixFileExt(const char *origname, const char *ext, char *filename, size_t size); extern void GetPCXPalette(byte **colormap, unsigned *d_8to24table); -extern void LoadPCX(const char *origname, byte **pic, byte **palette, int *width, int *height); +extern void LoadPCX(const char *origname, byte **pic, byte **palette, int *width, int *height, + int *bitsPerPixel); extern void GetPCXInfo(const char *origname, int *width, int *height); extern void GetWalInfo(const char *name, int *width, int *height); extern void GetM8Info(const char *name, int *width, int *height);