// SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- // Copyright (C) 1993-1996 by id Software, Inc. // Copyright (C) 2005-2009 by Andrey "entryway" Budko. // Copyright (C) 2018-2020 by Jaime "Lactozilla" Passos. // Copyright (C) 2019-2020 by Sonic Team Junior. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- /// \file r_picformats.c /// \brief Picture generation. #include "byteptr.h" #include "dehacked.h" #include "i_video.h" #include "r_data.h" #include "r_draw.h" #include "r_picformats.h" #include "r_things.h" #include "v_video.h" #include "z_zone.h" #include "w_wad.h" #ifdef HWRENDER #include "hardware/hw_glob.h" #endif #ifdef HAVE_PNG #ifndef _MSC_VER #ifndef _LARGEFILE64_SOURCE #define _LARGEFILE64_SOURCE #endif #endif #ifndef _LFS64_LARGEFILE #define _LFS64_LARGEFILE #endif #ifndef _FILE_OFFSET_BITS #define _FILE_OFFSET_BITS 0 #endif #include "png.h" #ifndef PNG_READ_SUPPORTED #undef HAVE_PNG #endif #endif static unsigned char imgbuf[1<<26]; fixed_t cosang2rad[ROTANGLES]; fixed_t sinang2rad[ROTANGLES]; /** Converts a picture between two formats. * * \param informat Input picture format. * \param picture Input picture data. * \param outformat Output picture format. * \param insize Input picture size. * \param outsize Output picture size, as a pointer. * \param inwidth Input picture width. * \param inheight Input picture height. * \param inleftoffset Input picture left offset, for patches. * \param intopoffset Input picture top offset, for patches. * \param flags Input picture flags. * \return A pointer to the converted picture. */ void *Picture_Convert( pictureformat_t informat, void *picture, pictureformat_t outformat, size_t insize, size_t *outsize, INT32 inwidth, INT32 inheight, INT32 inleftoffset, INT32 intopoffset, pictureflags_t flags) { if (informat == PICFMT_NONE) I_Error("Picture_Convert: input format was PICFMT_NONE!"); else if (outformat == PICFMT_NONE) I_Error("Picture_Convert: output format was PICFMT_NONE!"); else if (informat == outformat) I_Error("Picture_Convert: input and output formats were the same!"); if (Picture_IsPatchFormat(outformat)) return Picture_PatchConvert(informat, picture, outformat, insize, outsize, inwidth, inheight, inleftoffset, intopoffset, flags); else if (Picture_IsFlatFormat(outformat)) return Picture_FlatConvert(informat, picture, outformat, insize, outsize, inwidth, inheight, inleftoffset, intopoffset, flags); else I_Error("Picture_Convert: unsupported input format!"); return NULL; } /** Converts a picture to a patch. * * \param informat Input picture format. * \param picture Input picture data. * \param outformat Output picture format. * \param insize Input picture size. * \param outsize Output picture size, as a pointer. * \param inwidth Input picture width. * \param inheight Input picture height. * \param inleftoffset Input picture left offset, for patches. * \param intopoffset Input picture top offset, for patches. * \param flags Input picture flags. * \return A pointer to the converted picture. */ void *Picture_PatchConvert( pictureformat_t informat, void *picture, pictureformat_t outformat, size_t insize, size_t *outsize, INT16 inwidth, INT16 inheight, INT16 inleftoffset, INT16 intopoffset, pictureflags_t flags) { INT16 x, y; UINT8 *img; UINT8 *imgptr = imgbuf; UINT8 *colpointers, *startofspan; size_t size = 0; patch_t *inpatch = NULL; INT32 inbpp = Picture_FormatBPP(informat); (void)insize; // ignore if (informat == PICFMT_NONE) I_Error("Picture_PatchConvert: input format was PICFMT_NONE!"); else if (outformat == PICFMT_NONE) I_Error("Picture_PatchConvert: output format was PICFMT_NONE!"); else if (informat == outformat) I_Error("Picture_PatchConvert: input and output formats were the same!"); if (!inbpp) I_Error("Picture_PatchConvert: unknown input bits per pixel?!"); // If it's a patch, you can just figure out // the dimensions from the header. if (Picture_IsPatchFormat(informat)) { inpatch = (patch_t *)picture; inwidth = SHORT(inpatch->width); inheight = SHORT(inpatch->height); inleftoffset = SHORT(inpatch->leftoffset); intopoffset = SHORT(inpatch->topoffset); } // Write image size and offset WRITEINT16(imgptr, inwidth); WRITEINT16(imgptr, inheight); WRITEINT16(imgptr, inleftoffset); WRITEINT16(imgptr, intopoffset); // Leave placeholder to column pointers colpointers = imgptr; imgptr += inwidth*4; // Write columns for (x = 0; x < inwidth; x++) { int lastStartY = 0; int spanSize = 0; startofspan = NULL; // Write column pointer WRITEINT32(colpointers, imgptr - imgbuf); // Write pixels for (y = 0; y < inheight; y++) { void *input = NULL; boolean opaque = false; // Read pixel if (Picture_IsPatchFormat(informat)) input = Picture_GetPatchPixel(inpatch, informat, x, y, flags); else if (Picture_IsFlatFormat(informat)) { size_t offs = ((y * inwidth) + x); switch (informat) { case PICFMT_FLAT32: input = (UINT32 *)picture + offs; break; case PICFMT_FLAT16: input = (UINT16 *)picture + offs; break; case PICFMT_FLAT: input = (UINT8 *)picture + offs; break; default: I_Error("Picture_PatchConvert: unsupported flat input format!"); break; } } else I_Error("Picture_PatchConvert: unsupported input format!"); // Determine opacity if (input != NULL) { UINT8 alpha = 0xFF; if (inbpp == 32) { RGBA_t px = *(RGBA_t *)input; alpha = px.s.alpha; } else if (inbpp == 16) { UINT16 px = *(UINT16 *)input; alpha = (px & 0xFF00) >> 8; } else if (inbpp == 8) { UINT8 px = *(UINT8 *)input; if (px == TRANSPARENTPIXEL) alpha = 0; } opaque = (alpha > 1); } // End span if we have a transparent pixel if (!opaque) { if (startofspan) WRITEUINT8(imgptr, 0); startofspan = NULL; continue; } // Start new column if we need to if (!startofspan || spanSize == 255) { int writeY = y; // If we reached the span size limit, finish the previous span if (startofspan) WRITEUINT8(imgptr, 0); if (y > 254) { // Make sure we're aligned to 254 if (lastStartY < 254) { WRITEUINT8(imgptr, 254); WRITEUINT8(imgptr, 0); imgptr += 2; lastStartY = 254; } // Write stopgap empty spans if needed writeY = y - lastStartY; while (writeY > 254) { WRITEUINT8(imgptr, 254); WRITEUINT8(imgptr, 0); imgptr += 2; writeY -= 254; } } startofspan = imgptr; WRITEUINT8(imgptr, writeY); imgptr += 2; spanSize = 0; lastStartY = y; } // Write the pixel switch (outformat) { case PICFMT_PATCH32: { if (inbpp == 32) { RGBA_t out = *(RGBA_t *)input; WRITEUINT32(imgptr, out.rgba); } else if (inbpp == 16) { RGBA_t out = pMasterPalette[*((UINT16 *)input) & 0xFF]; WRITEUINT32(imgptr, out.rgba); } else // PICFMT_PATCH { RGBA_t out = pMasterPalette[*((UINT8 *)input) & 0xFF]; WRITEUINT32(imgptr, out.rgba); } break; } case PICFMT_PATCH16: if (inbpp == 32) { RGBA_t in = *(RGBA_t *)input; UINT8 out = NearestColor(in.s.red, in.s.green, in.s.blue); WRITEUINT16(imgptr, (0xFF00 | out)); } else if (inbpp == 16) WRITEUINT16(imgptr, *(UINT16 *)input); else // PICFMT_PATCH WRITEUINT16(imgptr, (0xFF00 | (*(UINT8 *)input))); break; default: // PICFMT_PATCH { if (inbpp == 32) { RGBA_t in = *(RGBA_t *)input; UINT8 out = NearestColor(in.s.red, in.s.green, in.s.blue); WRITEUINT8(imgptr, out); } else if (inbpp == 16) { UINT16 out = *(UINT16 *)input; WRITEUINT8(imgptr, (out & 0xFF)); } else // PICFMT_PATCH WRITEUINT8(imgptr, *(UINT8 *)input); break; } } spanSize++; startofspan[1] = spanSize; } if (startofspan) WRITEUINT8(imgptr, 0); WRITEUINT8(imgptr, 0xFF); } size = imgptr-imgbuf; img = Z_Malloc(size, PU_STATIC, NULL); memcpy(img, imgbuf, size); if (outsize != NULL) *outsize = size; return img; } /** Converts a picture to a flat. * * \param informat Input picture format. * \param picture Input picture data. * \param outformat Output picture format. * \param insize Input picture size. * \param outsize Output picture size, as a pointer. * \param inwidth Input picture width. * \param inheight Input picture height. * \param inleftoffset Input picture left offset, for patches. * \param intopoffset Input picture top offset, for patches. * \param flags Input picture flags. * \return A pointer to the converted picture. */ void *Picture_FlatConvert( pictureformat_t informat, void *picture, pictureformat_t outformat, size_t insize, size_t *outsize, INT16 inwidth, INT16 inheight, INT16 inleftoffset, INT16 intopoffset, pictureflags_t flags) { void *outflat; patch_t *inpatch = NULL; INT32 inbpp = Picture_FormatBPP(informat); INT32 outbpp = Picture_FormatBPP(outformat); INT32 x, y; size_t size; (void)insize; // ignore (void)inleftoffset; // ignore (void)intopoffset; // ignore if (informat == PICFMT_NONE) I_Error("Picture_FlatConvert: input format was PICFMT_NONE!"); else if (outformat == PICFMT_NONE) I_Error("Picture_FlatConvert: output format was PICFMT_NONE!"); else if (informat == outformat) I_Error("Picture_FlatConvert: input and output formats were the same!"); if (!inbpp) I_Error("Picture_FlatConvert: unknown input bits per pixel?!"); if (!outbpp) I_Error("Picture_FlatConvert: unknown output bits per pixel?!"); // If it's a patch, you can just figure out // the dimensions from the header. if (Picture_IsPatchFormat(informat)) { inpatch = (patch_t *)picture; inwidth = SHORT(inpatch->width); inheight = SHORT(inpatch->height); } size = (inwidth * inheight) * (outbpp / 8); outflat = Z_Calloc(size, PU_STATIC, NULL); if (outsize) *outsize = size; // Set transparency if (outbpp == 8) memset(outflat, TRANSPARENTPIXEL, size); for (y = 0; y < inheight; y++) for (x = 0; x < inwidth; x++) { void *input; size_t offs = ((y * inwidth) + x); // Read pixel if (Picture_IsPatchFormat(informat)) input = Picture_GetPatchPixel(inpatch, informat, x, y, flags); else if (Picture_IsFlatFormat(informat)) input = (UINT8 *)picture + (offs * (inbpp / 8)); else I_Error("Picture_FlatConvert: unsupported input format!"); if (!input) continue; switch (outformat) { case PICFMT_FLAT32: { UINT32 *f32 = (UINT32 *)outflat; if (inbpp == 32) { RGBA_t out = *(RGBA_t *)input; f32[offs] = out.rgba; } else if (inbpp == 16) { RGBA_t out = pMasterPalette[*((UINT16 *)input) & 0xFF]; f32[offs] = out.rgba; } else // PICFMT_PATCH { RGBA_t out = pMasterPalette[*((UINT8 *)input) & 0xFF]; f32[offs] = out.rgba; } break; } case PICFMT_FLAT16: { UINT16 *f16 = (UINT16 *)outflat; if (inbpp == 32) { RGBA_t in = *(RGBA_t *)input; UINT8 out = NearestColor(in.s.red, in.s.green, in.s.blue); f16[offs] = (0xFF00 | out); } else if (inbpp == 16) f16[offs] = *(UINT16 *)input; else // PICFMT_PATCH f16[offs] = (0xFF00 | *((UINT8 *)input)); break; } case PICFMT_FLAT: { UINT8 *f8 = (UINT8 *)outflat; if (inbpp == 32) { RGBA_t in = *(RGBA_t *)input; UINT8 out = NearestColor(in.s.red, in.s.green, in.s.blue); f8[offs] = out; } else if (inbpp == 16) { UINT16 out = *(UINT16 *)input; f8[offs] = (out & 0xFF); } else // PICFMT_PATCH f8[offs] = *(UINT8 *)input; break; } default: I_Error("Picture_FlatConvert: unsupported output format!"); } } return outflat; } /** Returns a pixel from a patch. * * \param patch Input patch. * \param informat Input picture format. * \param x Pixel X position. * \param y Pixel Y position. * \param flags Input picture flags. * \return A pointer to a pixel in the patch. Returns NULL if not opaque. */ void *Picture_GetPatchPixel( patch_t *patch, pictureformat_t informat, INT32 x, INT32 y, pictureflags_t flags) { fixed_t ofs; column_t *column; UINT8 *s8 = NULL; UINT16 *s16 = NULL; UINT32 *s32 = NULL; if (patch == NULL) I_Error("Picture_GetPatchPixel: patch == NULL"); if (x >= 0 && x < SHORT(patch->width)) { INT32 topdelta, prevdelta = -1; INT32 colofs = 0; if (flags & PICFLAGS_XFLIP) colofs = LONG(patch->columnofs[(SHORT(patch->width)-1)-x]); else colofs = LONG(patch->columnofs[x]); // Column offsets are pointers so no casting required column = (column_t *)((UINT8 *)patch + colofs); while (column->topdelta != 0xff) { topdelta = column->topdelta; if (topdelta <= prevdelta) topdelta += prevdelta; prevdelta = topdelta; s8 = (UINT8 *)(column) + 3; if (informat == PICFMT_PATCH32) s32 = (UINT32 *)s8; else if (informat == PICFMT_PATCH16) s16 = (UINT16 *)s8; for (ofs = 0; ofs < column->length; ofs++) { if ((topdelta + ofs) == y) { if (informat == PICFMT_PATCH32) return (s32 + ofs); else if (informat == PICFMT_PATCH16) return (s16 + ofs); else // PICFMT_PATCH return (s8 + ofs); } } column = (column_t *)((UINT8 *)column + column->length + 4); } } return NULL; } /** Returns the amount of bits per pixel in the specified picture format. * * \param format Input picture format. * \return The bits per pixel amount of the picture format. */ INT32 Picture_FormatBPP(pictureformat_t format) { INT32 bpp = 0; switch (format) { case PICFMT_PATCH32: case PICFMT_FLAT32: case PICFMT_PNG: bpp = 32; break; case PICFMT_PATCH16: case PICFMT_FLAT16: bpp = 16; break; case PICFMT_PATCH: case PICFMT_FLAT: bpp = 8; break; default: break; } return bpp; } /** Checks if the specified picture format is a patch. * * \param format Input picture format. * \return True if the picture format is a patch, false if not. */ boolean Picture_IsPatchFormat(pictureformat_t format) { return (format == PICFMT_PATCH || format == PICFMT_PATCH16 || format == PICFMT_PATCH32); } /** Checks if the specified picture format is a flat. * * \param format Input picture format. * \return True if the picture format is a flat, false if not. */ boolean Picture_IsFlatFormat(pictureformat_t format) { return (format == PICFMT_FLAT || format == PICFMT_FLAT16 || format == PICFMT_FLAT32); } /** Returns true if the lump is a valid patch. * PICFMT_PATCH only, I think?? * * \param patch Input patch. * \param picture Input patch size. * \return True if the input patch is valid. */ boolean Picture_CheckIfPatch(patch_t *patch, size_t size) { INT16 width, height; boolean result; // minimum length of a valid Doom patch if (size < 13) return false; width = SHORT(patch->width); height = SHORT(patch->height); result = (height > 0 && height <= 16384 && width > 0 && width <= 16384 && width < (INT16)(size / 4)); if (result) { // The dimensions seem like they might be valid for a patch, so // check the column directory for extra security. All columns // must begin after the column directory, and none of them must // point past the end of the patch. INT16 x; for (x = 0; x < width; x++) { UINT32 ofs = LONG(patch->columnofs[x]); // Need one byte for an empty column (but there's patches that don't know that!) if (ofs < (UINT32)width * 4 + 8 || ofs >= (UINT32)size) { result = false; break; } } } return result; } /** Converts a texture to a flat. * * \param trickytex The texture number. * \return The converted flat. */ void *Picture_TextureToFlat(size_t trickytex) { texture_t *texture; size_t tex; UINT8 *converted; size_t flatsize; fixed_t col, ofs; column_t *column; UINT8 *desttop, *dest, *deststop; UINT8 *source; if (trickytex >= (unsigned)numtextures) I_Error("Picture_TextureToFlat: invalid texture number!"); // Check the texture cache // If the texture's not there, it'll be generated right now tex = trickytex; texture = textures[tex]; R_CheckTextureCache(tex); // Allocate the flat flatsize = (texture->width * texture->height); converted = Z_Malloc(flatsize, PU_STATIC, NULL); memset(converted, TRANSPARENTPIXEL, flatsize); // Now we're gonna write to it desttop = converted; deststop = desttop + flatsize; for (col = 0; col < texture->width; col++, desttop++) { // no post_t info if (!texture->holes) { column = (column_t *)(R_GetColumn(tex, col)); source = (UINT8 *)(column); dest = desttop; for (ofs = 0; dest < deststop && ofs < texture->height; ofs++) { if (source[ofs] != TRANSPARENTPIXEL) *dest = source[ofs]; dest += texture->width; } } else { INT32 topdelta, prevdelta = -1; column = (column_t *)((UINT8 *)R_GetColumn(tex, col) - 3); while (column->topdelta != 0xff) { topdelta = column->topdelta; if (topdelta <= prevdelta) topdelta += prevdelta; prevdelta = topdelta; dest = desttop + (topdelta * texture->width); source = (UINT8 *)column + 3; for (ofs = 0; dest < deststop && ofs < column->length; ofs++) { if (source[ofs] != TRANSPARENTPIXEL) *dest = source[ofs]; dest += texture->width; } column = (column_t *)((UINT8 *)column + column->length + 4); } } } return converted; } /** Returns true if the lump is a valid PNG. * * \param d The lump to be checked. * \param s The lump size. * \return True if the lump is a PNG image. */ boolean Picture_IsLumpPNG(const UINT8 *d, size_t s) { if (s < 67) // http://garethrees.org/2007/11/14/pngcrush/ return false; // Check for PNG file signature using memcmp // As it may be faster on CPUs with slow unaligned memory access // Ref: http://www.libpng.org/pub/png/spec/1.2/PNG-Rationale.html#R.PNG-file-signature return (memcmp(&d[0], "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a", 8) == 0); } #ifndef NO_PNG_LUMPS #ifdef HAVE_PNG /*#if PNG_LIBPNG_VER_DLLNUM < 14 typedef PNG_CONST png_byte *png_const_bytep; #endif*/ typedef struct { const UINT8 *buffer; UINT32 size; UINT32 position; } png_io_t; static void PNG_IOReader(png_structp png_ptr, png_bytep data, png_size_t length) { png_io_t *f = png_get_io_ptr(png_ptr); if (length > (f->size - f->position)) png_error(png_ptr, "PNG_IOReader: buffer overrun"); memcpy(data, f->buffer + f->position, length); f->position += length; } typedef struct { char name[4]; void *data; size_t size; } png_chunk_t; static png_byte *chunkname = NULL; static png_chunk_t chunk; static int PNG_ChunkReader(png_structp png_ptr, png_unknown_chunkp chonk) { (void)png_ptr; if (!memcmp(chonk->name, chunkname, 4)) { memcpy(chunk.name, chonk->name, 4); chunk.size = chonk->size; chunk.data = Z_Malloc(chunk.size, PU_STATIC, NULL); memcpy(chunk.data, chonk->data, chunk.size); return 1; } return 0; } static void PNG_error(png_structp PNG, png_const_charp pngtext) { CONS_Debug(DBG_RENDER, "libpng error at %p: %s", PNG, pngtext); //I_Error("libpng error at %p: %s", PNG, pngtext); } static void PNG_warn(png_structp PNG, png_const_charp pngtext) { CONS_Debug(DBG_RENDER, "libpng warning at %p: %s", PNG, pngtext); } static png_bytep *PNG_Read(const UINT8 *png, INT32 *w, INT32 *h, INT16 *topoffset, INT16 *leftoffset, size_t size) { png_structp png_ptr; png_infop png_info_ptr; png_uint_32 width, height; int bit_depth, color_type; png_uint_32 y; #ifdef PNG_SETJMP_SUPPORTED #ifdef USE_FAR_KEYWORD jmp_buf jmpbuf; #endif #endif png_io_t png_io; png_bytep *row_pointers; png_byte grAb_chunk[5] = {'g', 'r', 'A', 'b', (png_byte)'\0'}; png_voidp *user_chunk_ptr; png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, PNG_error, PNG_warn); if (!png_ptr) I_Error("PNG_Read: Couldn't initialize libpng!"); png_info_ptr = png_create_info_struct(png_ptr); if (!png_info_ptr) { png_destroy_read_struct(&png_ptr, NULL, NULL); I_Error("PNG_Read: libpng couldn't allocate memory!"); } #ifdef USE_FAR_KEYWORD if (setjmp(jmpbuf)) #else if (setjmp(png_jmpbuf(png_ptr))) #endif { png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL); I_Error("PNG_Read: libpng load error!"); } #ifdef USE_FAR_KEYWORD png_memcpy(png_jmpbuf(png_ptr), jmpbuf, sizeof jmp_buf); #endif // set our own read function png_io.buffer = png; png_io.size = size; png_io.position = 0; png_set_read_fn(png_ptr, &png_io, PNG_IOReader); memset(&chunk, 0x00, sizeof(png_chunk_t)); chunkname = grAb_chunk; // I want to read a grAb chunk user_chunk_ptr = png_get_user_chunk_ptr(png_ptr); png_set_read_user_chunk_fn(png_ptr, user_chunk_ptr, PNG_ChunkReader); png_set_keep_unknown_chunks(png_ptr, 2, chunkname, 1); #ifdef PNG_SET_USER_LIMITS_SUPPORTED png_set_user_limits(png_ptr, 2048, 2048); #endif png_read_info(png_ptr, png_info_ptr); png_get_IHDR(png_ptr, png_info_ptr, &width, &height, &bit_depth, &color_type, NULL, NULL, NULL); if (bit_depth == 16) png_set_strip_16(png_ptr); if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(png_ptr); else if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png_ptr); if (png_get_valid(png_ptr, png_info_ptr, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png_ptr); else if (color_type != PNG_COLOR_TYPE_RGB_ALPHA && color_type != PNG_COLOR_TYPE_GRAY_ALPHA) { #if PNG_LIBPNG_VER < 10207 png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER); #else png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER); #endif } png_read_update_info(png_ptr, png_info_ptr); // Read the image row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * height); for (y = 0; y < height; y++) row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png_ptr, png_info_ptr)); png_read_image(png_ptr, row_pointers); // Read grAB chunk if ((topoffset || leftoffset) && (chunk.data != NULL)) { INT32 *offsets = (INT32 *)chunk.data; // read left offset if (leftoffset != NULL) *leftoffset = (INT16)BIGENDIAN_LONG(*offsets); offsets++; // read top offset if (topoffset != NULL) *topoffset = (INT16)BIGENDIAN_LONG(*offsets); } // bye png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL); if (chunk.data) Z_Free(chunk.data); *w = (INT32)width; *h = (INT32)height; return row_pointers; } /** Converts a PNG to a picture. * * \param png The PNG image. * \param outformat The output picture's format. * \param w The output picture's width, as a pointer. * \param h The output picture's height, as a pointer. * \param topoffset The output picture's top offset, for sprites, as a pointer. * \param leftoffset The output picture's left offset, for sprites, as a pointer. * \param insize The input picture's size. * \param outsize A pointer to the output picture's size. * \param flags Input picture flags. * \return A pointer to the converted picture. */ void *Picture_PNGConvert( const UINT8 *png, pictureformat_t outformat, INT32 *w, INT32 *h, INT16 *topoffset, INT16 *leftoffset, size_t insize, size_t *outsize, pictureflags_t flags) { void *flat; INT32 outbpp; size_t flatsize; png_uint_32 x, y; png_bytep *row_pointers = PNG_Read(png, w, h, topoffset, leftoffset, insize); png_uint_32 width = *w, height = *h; // Hack for patches because you'll want to preserve transparency. if (Picture_IsPatchFormat(outformat)) outbpp = 16; else { outbpp = Picture_FormatBPP(outformat); if (!outbpp) I_Error("Picture_PNGConvert: unknown output bits per pixel?!"); } // Figure out the size flatsize = (width * height) * (outbpp / 8); if (outsize) *outsize = flatsize; // Convert the image flat = Z_Calloc(flatsize, PU_STATIC, NULL); // Set transparency if (outbpp == 8) memset(flat, TRANSPARENTPIXEL, (width * height)); for (y = 0; y < height; y++) { png_bytep row = row_pointers[y]; for (x = 0; x < width; x++) { png_bytep px = &(row[x * 4]); if ((UINT8)px[3]) { UINT8 red = (UINT8)px[0]; UINT8 green = (UINT8)px[1]; UINT8 blue = (UINT8)px[2]; UINT8 alpha = (UINT8)px[3]; if (outbpp == 32) { UINT32 *outflat = (UINT32 *)flat; RGBA_t out; out.s.red = red; out.s.green = green; out.s.blue = blue; out.s.alpha = alpha; outflat[((y * width) + x)] = out.rgba; } else { UINT8 palidx = NearestColor(red, green, blue); if (outbpp == 16) { UINT16 *outflat = (UINT16 *)flat; outflat[((y * width) + x)] = (alpha << 8) | palidx; } else // 8bpp { UINT8 *outflat = (UINT8 *)flat; outflat[((y * width) + x)] = palidx; } } } } } // Free the row pointers that we allocated for libpng. free(row_pointers); // But wait, there's more! if (Picture_IsPatchFormat(outformat)) { void *converted; pictureformat_t informat = PICFMT_NONE; INT16 patleftoffset = 0, pattopoffset = 0; // Figure out the format of the flat, from the bit depth of the output format switch (outbpp) { case 32: informat = PICFMT_FLAT32; break; case 16: informat = PICFMT_FLAT16; break; default: informat = PICFMT_FLAT; break; } // Also find out if leftoffset and topoffset aren't pointing to NULL. if (leftoffset) patleftoffset = *leftoffset; if (topoffset) pattopoffset = *topoffset; // Now, convert it! converted = Picture_PatchConvert(informat, flat, outformat, insize, outsize, (INT16)width, (INT16)height, patleftoffset, pattopoffset, flags); Z_Free(flat); return converted; } // Return the converted flat! return flat; } /** Returns the dimensions of a PNG image, but doesn't perform any conversions. * * \param png The PNG image. * \param width A pointer to the input picture's width. * \param height A pointer to the input picture's height. * \param size The input picture's size. * \return True if reading the file succeeded, false if it failed. */ boolean Picture_PNGDimensions(UINT8 *png, INT16 *width, INT16 *height, size_t size) { png_structp png_ptr; png_infop png_info_ptr; png_uint_32 w, h; int bit_depth, color_type; #ifdef PNG_SETJMP_SUPPORTED #ifdef USE_FAR_KEYWORD jmp_buf jmpbuf; #endif #endif png_io_t png_io; png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, PNG_error, PNG_warn); if (!png_ptr) I_Error("Picture_PNGDimensions: Couldn't initialize libpng!"); png_info_ptr = png_create_info_struct(png_ptr); if (!png_info_ptr) { png_destroy_read_struct(&png_ptr, NULL, NULL); I_Error("Picture_PNGDimensions: libpng couldn't allocate memory!"); } #ifdef USE_FAR_KEYWORD if (setjmp(jmpbuf)) #else if (setjmp(png_jmpbuf(png_ptr))) #endif { png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL); I_Error("Picture_PNGDimensions: libpng load error!"); } #ifdef USE_FAR_KEYWORD png_memcpy(png_jmpbuf(png_ptr), jmpbuf, sizeof jmp_buf); #endif // set our own read function png_io.buffer = png; png_io.size = size; png_io.position = 0; png_set_read_fn(png_ptr, &png_io, PNG_IOReader); #ifdef PNG_SET_USER_LIMITS_SUPPORTED png_set_user_limits(png_ptr, 2048, 2048); #endif png_read_info(png_ptr, png_info_ptr); png_get_IHDR(png_ptr, png_info_ptr, &w, &h, &bit_depth, &color_type, NULL, NULL, NULL); // okay done. stop. png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL); *width = (INT32)w; *height = (INT32)h; return true; } #endif #endif // // R_ParseSpriteInfoFrame // // Parse a SPRTINFO frame. // static void R_ParseSpriteInfoFrame(spriteinfo_t *info) { char *sprinfoToken; size_t sprinfoTokenLength; char *frameChar = NULL; UINT8 frameFrame = 0xFF; #ifdef ROTSPRITE INT16 frameXPivot = 0; INT16 frameYPivot = 0; rotaxis_t frameRotAxis = 0; #endif // Sprite identifier sprinfoToken = M_GetToken(NULL); if (sprinfoToken == NULL) { I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite frame should be"); } sprinfoTokenLength = strlen(sprinfoToken); if (sprinfoTokenLength != 1) { I_Error("Error parsing SPRTINFO lump: Invalid frame \"%s\"",sprinfoToken); } else frameChar = sprinfoToken; frameFrame = R_Char2Frame(frameChar[0]); Z_Free(sprinfoToken); // Left Curly Brace sprinfoToken = M_GetToken(NULL); if (sprinfoToken == NULL) I_Error("Error parsing SPRTINFO lump: Missing sprite info"); else { if (strcmp(sprinfoToken,"{")==0) { Z_Free(sprinfoToken); sprinfoToken = M_GetToken(NULL); if (sprinfoToken == NULL) { I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite info should be"); } while (strcmp(sprinfoToken,"}")!=0) { #ifdef ROTSPRITE if (stricmp(sprinfoToken, "XPIVOT")==0) { Z_Free(sprinfoToken); sprinfoToken = M_GetToken(NULL); frameXPivot = atoi(sprinfoToken); } else if (stricmp(sprinfoToken, "YPIVOT")==0) { Z_Free(sprinfoToken); sprinfoToken = M_GetToken(NULL); frameYPivot = atoi(sprinfoToken); } else if (stricmp(sprinfoToken, "ROTAXIS")==0) { Z_Free(sprinfoToken); sprinfoToken = M_GetToken(NULL); if ((stricmp(sprinfoToken, "X")==0) || (stricmp(sprinfoToken, "XAXIS")==0) || (stricmp(sprinfoToken, "ROLL")==0)) frameRotAxis = ROTAXIS_X; else if ((stricmp(sprinfoToken, "Y")==0) || (stricmp(sprinfoToken, "YAXIS")==0) || (stricmp(sprinfoToken, "PITCH")==0)) frameRotAxis = ROTAXIS_Y; else if ((stricmp(sprinfoToken, "Z")==0) || (stricmp(sprinfoToken, "ZAXIS")==0) || (stricmp(sprinfoToken, "YAW")==0)) frameRotAxis = ROTAXIS_Z; } #endif Z_Free(sprinfoToken); sprinfoToken = M_GetToken(NULL); if (sprinfoToken == NULL) { I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite info or right curly brace should be"); } } } Z_Free(sprinfoToken); } // set fields #ifdef ROTSPRITE info->pivot[frameFrame].x = frameXPivot; info->pivot[frameFrame].y = frameYPivot; info->pivot[frameFrame].rotaxis = frameRotAxis; #endif } // // R_ParseSpriteInfo // // Parse a SPRTINFO lump. // static void R_ParseSpriteInfo(boolean spr2) { spriteinfo_t *info; char *sprinfoToken; size_t sprinfoTokenLength; char newSpriteName[5]; // no longer dynamically allocated spritenum_t sprnum = NUMSPRITES; playersprite_t spr2num = NUMPLAYERSPRITES; INT32 i; INT32 skinnumbers[MAXSKINS]; INT32 foundskins = 0; // Sprite name sprinfoToken = M_GetToken(NULL); if (sprinfoToken == NULL) { I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite name should be"); } sprinfoTokenLength = strlen(sprinfoToken); if (sprinfoTokenLength != 4) { I_Error("Error parsing SPRTINFO lump: Sprite name \"%s\" isn't 4 characters long",sprinfoToken); } else { memset(&newSpriteName, 0, 5); M_Memcpy(newSpriteName, sprinfoToken, sprinfoTokenLength); // ^^ we've confirmed that the token is == 4 characters so it will never overflow a 5 byte char buffer strupr(newSpriteName); // Just do this now so we don't have to worry about it } Z_Free(sprinfoToken); if (!spr2) { for (i = 0; i <= NUMSPRITES; i++) { if (i == NUMSPRITES) I_Error("Error parsing SPRTINFO lump: Unknown sprite name \"%s\"", newSpriteName); if (!memcmp(newSpriteName,sprnames[i],4)) { sprnum = i; break; } } } else { for (i = 0; i <= NUMPLAYERSPRITES; i++) { if (i == NUMPLAYERSPRITES) I_Error("Error parsing SPRTINFO lump: Unknown sprite2 name \"%s\"", newSpriteName); if (!memcmp(newSpriteName,spr2names[i],4)) { spr2num = i; break; } } } // allocate a spriteinfo info = Z_Calloc(sizeof(spriteinfo_t), PU_STATIC, NULL); info->available = true; #ifdef ROTSPRITE if ((sprites != NULL) && (!spr2)) R_FreeSingleRotSprite(&sprites[sprnum]); #endif // Left Curly Brace sprinfoToken = M_GetToken(NULL); if (sprinfoToken == NULL) { I_Error("Error parsing SPRTINFO lump: Unexpected end of file where open curly brace for sprite \"%s\" should be",newSpriteName); } if (strcmp(sprinfoToken,"{")==0) { Z_Free(sprinfoToken); sprinfoToken = M_GetToken(NULL); if (sprinfoToken == NULL) { I_Error("Error parsing SPRTINFO lump: Unexpected end of file where definition for sprite \"%s\" should be",newSpriteName); } while (strcmp(sprinfoToken,"}")!=0) { if (stricmp(sprinfoToken, "SKIN")==0) { INT32 skinnum; char *skinName = NULL; if (!spr2) I_Error("Error parsing SPRTINFO lump: \"SKIN\" token found outside of a sprite2 definition"); Z_Free(sprinfoToken); // Skin name sprinfoToken = M_GetToken(NULL); if (sprinfoToken == NULL) { I_Error("Error parsing SPRTINFO lump: Unexpected end of file where skin frame should be"); } // copy skin name yada yada sprinfoTokenLength = strlen(sprinfoToken); skinName = (char *)Z_Malloc((sprinfoTokenLength+1)*sizeof(char),PU_STATIC,NULL); M_Memcpy(skinName,sprinfoToken,sprinfoTokenLength*sizeof(char)); skinName[sprinfoTokenLength] = '\0'; strlwr(skinName); Z_Free(sprinfoToken); skinnum = R_SkinAvailable(skinName); if (skinnum == -1) I_Error("Error parsing SPRTINFO lump: Unknown skin \"%s\"", skinName); skinnumbers[foundskins] = skinnum; foundskins++; } else if (stricmp(sprinfoToken, "FRAME")==0) { R_ParseSpriteInfoFrame(info); Z_Free(sprinfoToken); if (spr2) { if (!foundskins) I_Error("Error parsing SPRTINFO lump: No skins specified in this sprite2 definition"); for (i = 0; i < foundskins; i++) { size_t skinnum = skinnumbers[i]; skin_t *skin = &skins[skinnum]; spriteinfo_t *sprinfo = skin->sprinfo; #ifdef ROTSPRITE R_FreeSkinRotSprite(skinnum); #endif M_Memcpy(&sprinfo[spr2num], info, sizeof(spriteinfo_t)); } } else M_Memcpy(&spriteinfo[sprnum], info, sizeof(spriteinfo_t)); } else { I_Error("Error parsing SPRTINFO lump: Unknown keyword \"%s\" in sprite %s",sprinfoToken,newSpriteName); } sprinfoToken = M_GetToken(NULL); if (sprinfoToken == NULL) { I_Error("Error parsing SPRTINFO lump: Unexpected end of file where sprite info or right curly brace for sprite \"%s\" should be",newSpriteName); } } } else { I_Error("Error parsing SPRTINFO lump: Expected \"{\" for sprite \"%s\", got \"%s\"",newSpriteName,sprinfoToken); } Z_Free(sprinfoToken); Z_Free(info); } // // R_ParseSPRTINFOLump // // Read a SPRTINFO lump. // void R_ParseSPRTINFOLump(UINT16 wadNum, UINT16 lumpNum) { char *sprinfoLump; size_t sprinfoLumpLength; char *sprinfoText; char *sprinfoToken; // Since lumps AREN'T \0-terminated like I'd assumed they should be, I'll // need to make a space of memory where I can ensure that it will terminate // correctly. Start by loading the relevant data from the WAD. sprinfoLump = (char *)W_CacheLumpNumPwad(wadNum, lumpNum, PU_STATIC); // If that didn't exist, we have nothing to do here. if (sprinfoLump == NULL) return; // If we're still here, then it DOES exist; figure out how long it is, and allot memory accordingly. sprinfoLumpLength = W_LumpLengthPwad(wadNum, lumpNum); sprinfoText = (char *)Z_Malloc((sprinfoLumpLength+1)*sizeof(char),PU_STATIC,NULL); // Now move the contents of the lump into this new location. memmove(sprinfoText,sprinfoLump,sprinfoLumpLength); // Make damn well sure the last character in our new memory location is \0. sprinfoText[sprinfoLumpLength] = '\0'; // Finally, free up the memory from the first data load, because we really // don't need it. Z_Free(sprinfoLump); sprinfoToken = M_GetToken(sprinfoText); while (sprinfoToken != NULL) { if (!stricmp(sprinfoToken, "SPRITE")) R_ParseSpriteInfo(false); else if (!stricmp(sprinfoToken, "SPRITE2")) R_ParseSpriteInfo(true); else I_Error("Error parsing SPRTINFO lump: Unknown keyword \"%s\"", sprinfoToken); Z_Free(sprinfoToken); sprinfoToken = M_GetToken(NULL); } Z_Free((void *)sprinfoText); } // // R_LoadSpriteInfoLumps // // Load and read every SPRTINFO lump from the specified file. // void R_LoadSpriteInfoLumps(UINT16 wadnum, UINT16 numlumps) { lumpinfo_t *lumpinfo = wadfiles[wadnum]->lumpinfo; UINT16 i; char *name; for (i = 0; i < numlumps; i++, lumpinfo++) { name = lumpinfo->name; // load SPRTINFO lumps if (!stricmp(name, "SPRTINFO")) R_ParseSPRTINFOLump(wadnum, i); // load SPR_ lumps (as DEHACKED lump) else if (!memcmp(name, "SPR_", 4)) DEH_LoadDehackedLumpPwad(wadnum, i, false); } } #ifdef ROTSPRITE // // R_CacheRotSprite // // Create a rotated sprite. // void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, spriteframe_t *sprframe, INT32 rot, UINT8 flip) { INT32 angle; patch_t *patch; patch_t *newpatch; UINT16 *rawdst; size_t size; pictureflags_t bflip = (flip) ? PICFLAGS_XFLIP : 0; #define SPRITE_XCENTER (leftoffset) #define SPRITE_YCENTER (height / 2) #define ROTSPRITE_XCENTER (newwidth / 2) #define ROTSPRITE_YCENTER (newheight / 2) if (!sprframe->rotsprite.cached[rot]) { INT32 dx, dy; INT32 px, py; INT32 width, height, leftoffset; fixed_t ca, sa; lumpnum_t lump = sprframe->lumppat[rot]; size_t lumplength; if (lump == LUMPERROR) return; patch = (patch_t *)W_CacheLumpNum(lump, PU_STATIC); lumplength = W_LumpLength(lump); // Because there's something wrong with SPR_DFLM, I guess if (!Picture_CheckIfPatch(patch, lumplength)) return; width = patch->width; height = patch->height; leftoffset = patch->leftoffset; // rotation pivot px = SPRITE_XCENTER; py = SPRITE_YCENTER; // get correct sprite info for sprite if (sprinfo == NULL) sprinfo = &spriteinfo[sprnum]; if (sprinfo->available) { px = sprinfo->pivot[frame].x; py = sprinfo->pivot[frame].y; } if (bflip) { px = width - px; leftoffset = width - leftoffset; } // Don't cache angle = 0 for (angle = 1; angle < ROTANGLES; angle++) { INT32 newwidth, newheight; ca = cosang2rad[angle]; sa = sinang2rad[angle]; // Find the dimensions of the rotated patch. { INT32 w1 = abs(FixedMul(width << FRACBITS, ca) - FixedMul(height << FRACBITS, sa)); INT32 w2 = abs(FixedMul(-(width << FRACBITS), ca) - FixedMul(height << FRACBITS, sa)); INT32 h1 = abs(FixedMul(width << FRACBITS, sa) + FixedMul(height << FRACBITS, ca)); INT32 h2 = abs(FixedMul(-(width << FRACBITS), sa) + FixedMul(height << FRACBITS, ca)); w1 = FixedInt(FixedCeil(w1 + (FRACUNIT/2))); w2 = FixedInt(FixedCeil(w2 + (FRACUNIT/2))); h1 = FixedInt(FixedCeil(h1 + (FRACUNIT/2))); h2 = FixedInt(FixedCeil(h2 + (FRACUNIT/2))); newwidth = max(width, max(w1, w2)); newheight = max(height, max(h1, h2)); } // check boundaries { fixed_t top[2][2]; fixed_t bottom[2][2]; top[0][0] = FixedMul((-ROTSPRITE_XCENTER) << FRACBITS, ca) + FixedMul((-ROTSPRITE_YCENTER) << FRACBITS, sa) + (px << FRACBITS); top[0][1] = FixedMul((-ROTSPRITE_XCENTER) << FRACBITS, sa) + FixedMul((-ROTSPRITE_YCENTER) << FRACBITS, ca) + (py << FRACBITS); top[1][0] = FixedMul((newwidth-ROTSPRITE_XCENTER) << FRACBITS, ca) + FixedMul((-ROTSPRITE_YCENTER) << FRACBITS, sa) + (px << FRACBITS); top[1][1] = FixedMul((newwidth-ROTSPRITE_XCENTER) << FRACBITS, sa) + FixedMul((-ROTSPRITE_YCENTER) << FRACBITS, ca) + (py << FRACBITS); bottom[0][0] = FixedMul((-ROTSPRITE_XCENTER) << FRACBITS, ca) + FixedMul((newheight-ROTSPRITE_YCENTER) << FRACBITS, sa) + (px << FRACBITS); bottom[0][1] = -FixedMul((-ROTSPRITE_XCENTER) << FRACBITS, sa) + FixedMul((newheight-ROTSPRITE_YCENTER) << FRACBITS, ca) + (py << FRACBITS); bottom[1][0] = FixedMul((newwidth-ROTSPRITE_XCENTER) << FRACBITS, ca) + FixedMul((newheight-ROTSPRITE_YCENTER) << FRACBITS, sa) + (px << FRACBITS); bottom[1][1] = -FixedMul((newwidth-ROTSPRITE_XCENTER) << FRACBITS, sa) + FixedMul((newheight-ROTSPRITE_YCENTER) << FRACBITS, ca) + (py << FRACBITS); top[0][0] >>= FRACBITS; top[0][1] >>= FRACBITS; top[1][0] >>= FRACBITS; top[1][1] >>= FRACBITS; bottom[0][0] >>= FRACBITS; bottom[0][1] >>= FRACBITS; bottom[1][0] >>= FRACBITS; bottom[1][1] >>= FRACBITS; #define BOUNDARYWCHECK(b) (b[0] < 0 || b[0] >= width) #define BOUNDARYHCHECK(b) (b[1] < 0 || b[1] >= height) #define BOUNDARYADJUST(x) x *= 2 // top left/right if (BOUNDARYWCHECK(top[0]) || BOUNDARYWCHECK(top[1])) BOUNDARYADJUST(newwidth); // bottom left/right else if (BOUNDARYWCHECK(bottom[0]) || BOUNDARYWCHECK(bottom[1])) BOUNDARYADJUST(newwidth); // top left/right if (BOUNDARYHCHECK(top[0]) || BOUNDARYHCHECK(top[1])) BOUNDARYADJUST(newheight); // bottom left/right else if (BOUNDARYHCHECK(bottom[0]) || BOUNDARYHCHECK(bottom[1])) BOUNDARYADJUST(newheight); #undef BOUNDARYWCHECK #undef BOUNDARYHCHECK #undef BOUNDARYADJUST } // Draw the rotated sprite to a temporary buffer. size = (newwidth * newheight); if (!size) size = (width * height); rawdst = Z_Calloc(size * sizeof(UINT16), PU_STATIC, NULL); for (dy = 0; dy < newheight; dy++) { for (dx = 0; dx < newwidth; dx++) { INT32 x = (dx-ROTSPRITE_XCENTER) << FRACBITS; INT32 y = (dy-ROTSPRITE_YCENTER) << FRACBITS; INT32 sx = FixedMul(x, ca) + FixedMul(y, sa) + (px << FRACBITS); INT32 sy = -FixedMul(x, sa) + FixedMul(y, ca) + (py << FRACBITS); sx >>= FRACBITS; sy >>= FRACBITS; if (sx >= 0 && sy >= 0 && sx < width && sy < height) { void *input = Picture_GetPatchPixel(patch, PICFMT_PATCH, sx, sy, bflip); if (input != NULL) rawdst[(dy*newwidth)+dx] = *(UINT16 *)input; } } } // make patch newpatch = (patch_t *)Picture_Convert(PICFMT_FLAT16, rawdst, PICFMT_PATCH, 0, &size, newwidth, newheight, 0, 0, 0); { newpatch->leftoffset = (newpatch->width / 2) + (leftoffset - px); newpatch->topoffset = (newpatch->height / 2) + (patch->topoffset - py); } //BP: we cannot use special tric in hardware mode because feet in ground caused by z-buffer if (rendermode != render_none) // not for psprite newpatch->topoffset += FEETADJUST>>FRACBITS; // P_PrecacheLevel if (devparm) spritememory += size; #ifdef HWRENDER if (rendermode == render_opengl) { GLPatch_t *grPatch = HWR_GetCachedGLRotSprite(sprframe->rotsprite.hardware_patch[rot], angle, newpatch); HWR_MakePatch(newpatch, grPatch, grPatch->mipmap, false); sprframe->rotsprite.patch[rot][angle] = (patch_t *)grPatch; } else #endif // HWRENDER sprframe->rotsprite.patch[rot][angle] = newpatch; // free rotated image data Z_Free(rawdst); } // This rotation is cached now sprframe->rotsprite.cached[rot] = true; // free image data Z_Free(patch); } #undef SPRITE_XCENTER #undef SPRITE_YCENTER #undef ROTSPRITE_XCENTER #undef ROTSPRITE_YCENTER } // // R_FreeSingleRotSprite // // Free sprite rotation data from memory, for a single spritedef. // void R_FreeSingleRotSprite(spritedef_t *spritedef) { UINT8 frame; INT32 rot, ang; for (frame = 0; frame < spritedef->numframes; frame++) { spriteframe_t *sprframe = &spritedef->spriteframes[frame]; for (rot = 0; rot < 8; rot++) { if (sprframe->rotsprite.cached[rot]) { for (ang = 0; ang < ROTANGLES; ang++) { patch_t *rotsprite = sprframe->rotsprite.patch[rot][ang]; if (rotsprite) { #ifdef HWRENDER if (rendermode == render_opengl) { GLPatch_t *grPatch = (GLPatch_t *)rotsprite; if (grPatch->rawpatch) { Z_Free(grPatch->rawpatch); grPatch->rawpatch = NULL; } if (grPatch->mipmap) { if (grPatch->mipmap->grInfo.data) { Z_Free(grPatch->mipmap->grInfo.data); grPatch->mipmap->grInfo.data = NULL; } Z_Free(grPatch->mipmap); grPatch->mipmap = NULL; } } #endif Z_Free(rotsprite); } } sprframe->rotsprite.cached[rot] = false; } } } } // // R_FreeSkinRotSprite // // Free sprite rotation data from memory, for a skin. // Calls R_FreeSingleRotSprite. // void R_FreeSkinRotSprite(size_t skinnum) { size_t i; skin_t *skin = &skins[skinnum]; spritedef_t *skinsprites = skin->sprites; for (i = 0; i < NUMPLAYERSPRITES*2; i++) { R_FreeSingleRotSprite(skinsprites); skinsprites++; } } // // R_FreeAllRotSprite // // Free ALL sprite rotation data from memory. // void R_FreeAllRotSprite(void) { INT32 i; size_t s; for (s = 0; s < numsprites; s++) R_FreeSingleRotSprite(&sprites[s]); for (i = 0; i < numskins; ++i) R_FreeSkinRotSprite(i); } #endif