mirror of
https://git.do.srb2.org/STJr/SRB2.git
synced 2025-02-03 15:01:28 +00:00
1769 lines
45 KiB
C
1769 lines
45 KiB
C
// SONIC ROBO BLAST 2
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright (C) 1993-1996 by id Software, Inc.
|
|
// Copyright (C) 2005-2009 by Andrey "entryway" Budko.
|
|
// Copyright (C) 2018-2023 by Jaime "Lactozilla" Passos.
|
|
// Copyright (C) 2019-2023 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_patch.h"
|
|
#include "r_picformats.h"
|
|
#include "r_textures.h"
|
|
#include "r_things.h"
|
|
#include "r_draw.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
|
|
|
|
#ifdef PICTURE_PNG_USELOOKUP
|
|
static colorlookup_t png_colorlookup;
|
|
#endif
|
|
|
|
/** 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.
|
|
* \sa Picture_PatchConvert
|
|
* \sa Picture_FlatConvert
|
|
*/
|
|
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!");
|
|
|
|
(void)insize;
|
|
|
|
if (Picture_IsPatchFormat(outformat))
|
|
return Picture_PatchConvert(informat, picture, outformat, outsize, inwidth, inheight, inleftoffset, intopoffset, flags);
|
|
else if (Picture_IsFlatFormat(outformat))
|
|
return Picture_FlatConvert(informat, picture, outformat, outsize, inwidth, intopoffset, flags);
|
|
else
|
|
I_Error("Picture_Convert: unsupported input format!");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void *ReadPixelFunc_Patch(void *picture, pictureformat_t informat, INT32 x, INT32 y, INT32 inwidth, INT32 inheight, pictureflags_t flags)
|
|
{
|
|
(void)inwidth;
|
|
(void)inheight;
|
|
return Picture_GetPatchPixel((patch_t*)picture, informat, x, y, flags);
|
|
}
|
|
|
|
static void *ReadPixelFunc_Flat_8bpp(void *picture, pictureformat_t informat, INT32 x, INT32 y, INT32 inwidth, INT32 inheight, pictureflags_t flags)
|
|
{
|
|
(void)informat;
|
|
(void)flags;
|
|
(void)inheight;
|
|
return (UINT8 *)picture + ((y * inwidth) + x);
|
|
}
|
|
|
|
static void *ReadPixelFunc_Flat_16bpp(void *picture, pictureformat_t informat, INT32 x, INT32 y, INT32 inwidth, INT32 inheight, pictureflags_t flags)
|
|
{
|
|
(void)informat;
|
|
(void)flags;
|
|
(void)inheight;
|
|
return (UINT16 *)picture + ((y * inwidth) + x);
|
|
}
|
|
|
|
static void *ReadPixelFunc_Flat_32bpp(void *picture, pictureformat_t informat, INT32 x, INT32 y, INT32 inwidth, INT32 inheight, pictureflags_t flags)
|
|
{
|
|
(void)informat;
|
|
(void)flags;
|
|
(void)inheight;
|
|
return (UINT32 *)picture + ((y * inwidth) + x);
|
|
}
|
|
|
|
static UINT8 GetAlphaFunc_32bpp(void *input, pictureflags_t flags)
|
|
{
|
|
(void)flags;
|
|
RGBA_t px = *(RGBA_t *)input;
|
|
return px.s.alpha;
|
|
}
|
|
|
|
static UINT8 GetAlphaFunc_16bpp(void *input, pictureflags_t flags)
|
|
{
|
|
(void)flags;
|
|
UINT16 px = *(UINT16 *)input;
|
|
return (px & 0xFF00) >> 8;
|
|
}
|
|
|
|
static UINT8 GetAlphaFunc_8bpp(void *input, pictureflags_t flags)
|
|
{
|
|
UINT8 px = *(UINT8 *)input;
|
|
if (px == TRANSPARENTPIXEL && (flags & PICFLAGS_USE_TRANSPARENTPIXEL))
|
|
return 0;
|
|
else
|
|
return 255;
|
|
}
|
|
|
|
// input 32bpp output 32bpp
|
|
static void *WritePatchPixel_i32o32(void *ptr, void *input)
|
|
{
|
|
RGBA_t px = *(RGBA_t *)input;
|
|
WRITEUINT32(ptr, px.rgba);
|
|
return ptr;
|
|
}
|
|
|
|
// input 16bpp output 32bpp
|
|
static void *WritePatchPixel_i16o32(void *ptr, void *input)
|
|
{
|
|
RGBA_t px = pMasterPalette[*((UINT16 *)input) & 0xFF];
|
|
WRITEUINT32(ptr, px.rgba);
|
|
return ptr;
|
|
}
|
|
|
|
// input 8bpp output 32bpp
|
|
static void *WritePatchPixel_i8o32(void *ptr, void *input)
|
|
{
|
|
RGBA_t px = pMasterPalette[*((UINT8 *)input) & 0xFF];
|
|
WRITEUINT32(ptr, px.rgba);
|
|
return ptr;
|
|
}
|
|
|
|
// input 32bpp output 16bpp
|
|
static void *WritePatchPixel_i32o16(void *ptr, void *input)
|
|
{
|
|
RGBA_t in = *(RGBA_t *)input;
|
|
UINT8 px = NearestColor(in.s.red, in.s.green, in.s.blue);
|
|
WRITEUINT16(ptr, (0xFF00 | px));
|
|
return ptr;
|
|
}
|
|
|
|
// input 16bpp output 16bpp
|
|
static void *WritePatchPixel_i16o16(void *ptr, void *input)
|
|
{
|
|
WRITEUINT16(ptr, *(UINT16 *)input);
|
|
return ptr;
|
|
}
|
|
|
|
// input 8bpp output 16bpp
|
|
static void *WritePatchPixel_i8o16(void *ptr, void *input)
|
|
{
|
|
WRITEUINT16(ptr, (0xFF00 | (*(UINT8 *)input)));
|
|
return ptr;
|
|
}
|
|
|
|
// input 32bpp output 8bpp
|
|
static void *WritePatchPixel_i32o8(void *ptr, void *input)
|
|
{
|
|
RGBA_t in = *(RGBA_t *)input;
|
|
UINT8 px = NearestColor(in.s.red, in.s.green, in.s.blue);
|
|
WRITEUINT8(ptr, px);
|
|
return ptr;
|
|
}
|
|
|
|
// input 16bpp output 8bpp
|
|
static void *WritePatchPixel_i16o8(void *ptr, void *input)
|
|
{
|
|
UINT16 px = *(UINT16 *)input;
|
|
WRITEUINT8(ptr, (px & 0xFF));
|
|
return ptr;
|
|
}
|
|
|
|
// input 8bpp output 8bpp
|
|
static void *WritePatchPixel_i8o8(void *ptr, void *input)
|
|
{
|
|
WRITEUINT8(ptr, *(UINT8 *)input);
|
|
return ptr;
|
|
}
|
|
|
|
/** Converts a picture to a patch.
|
|
*
|
|
* \param informat Input picture format.
|
|
* \param picture Input picture data.
|
|
* \param outformat Output picture format.
|
|
* \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 *outsize,
|
|
INT32 inwidth, INT32 inheight, INT32 inleftoffset, INT32 intopoffset,
|
|
pictureflags_t flags)
|
|
{
|
|
// Shortcut: If converting a Doom patch into a regular patch, use Patch_Create
|
|
if (informat == PICFMT_DOOMPATCH && outformat == PICFMT_PATCH && flags == 0)
|
|
{
|
|
if (outsize != NULL)
|
|
*outsize = sizeof(patch_t);
|
|
return Patch_CreateFromDoomPatch(picture);
|
|
}
|
|
|
|
INT32 outbpp = Picture_FormatBPP(outformat);
|
|
INT32 inbpp = Picture_FormatBPP(informat);
|
|
patch_t *inpatch = NULL;
|
|
|
|
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 (Picture_IsDoomPatchFormat(outformat))
|
|
I_Error("Picture_PatchConvert: cannot convert to Doom patch!");
|
|
else if (informat == outformat)
|
|
I_Error("Picture_PatchConvert: input and output formats were the same!");
|
|
|
|
if (inbpp == PICDEPTH_NONE)
|
|
I_Error("Picture_PatchConvert: unknown input bits per pixel!");
|
|
if (outbpp == PICDEPTH_NONE)
|
|
I_Error("Picture_PatchConvert: unknown output bits per pixel!");
|
|
|
|
// If it's a patch, we can just figure out the dimensions from the header.
|
|
if (Picture_IsPatchFormat(informat))
|
|
{
|
|
inpatch = (patch_t *)picture;
|
|
if (Picture_IsDoomPatchFormat(informat))
|
|
{
|
|
softwarepatch_t *doompatch = (softwarepatch_t *)picture;
|
|
inwidth = SHORT(doompatch->width);
|
|
inheight = SHORT(doompatch->height);
|
|
inleftoffset = SHORT(doompatch->leftoffset);
|
|
intopoffset = SHORT(doompatch->topoffset);
|
|
}
|
|
else
|
|
{
|
|
inwidth = inpatch->width;
|
|
inheight = inpatch->height;
|
|
inleftoffset = inpatch->leftoffset;
|
|
intopoffset = inpatch->topoffset;
|
|
}
|
|
}
|
|
|
|
void *(*readPixelFunc)(void *, pictureformat_t, INT32, INT32, INT32, INT32, pictureflags_t) = NULL;
|
|
UINT8 (*getAlphaFunc)(void *, pictureflags_t) = NULL;
|
|
void *(*writePatchPixel)(void *, void *) = NULL;
|
|
|
|
if (Picture_IsPatchFormat(informat))
|
|
readPixelFunc = ReadPixelFunc_Patch;
|
|
else if (Picture_IsFlatFormat(informat))
|
|
{
|
|
switch (informat)
|
|
{
|
|
case PICFMT_FLAT32:
|
|
readPixelFunc = ReadPixelFunc_Flat_32bpp;
|
|
break;
|
|
case PICFMT_FLAT16:
|
|
readPixelFunc = ReadPixelFunc_Flat_16bpp;
|
|
break;
|
|
case PICFMT_FLAT:
|
|
readPixelFunc = ReadPixelFunc_Flat_8bpp;
|
|
break;
|
|
default:
|
|
I_Error("Picture_PatchConvert: unsupported flat input format!");
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
I_Error("Picture_PatchConvert: unsupported input format!");
|
|
|
|
if (inbpp == PICDEPTH_32BPP)
|
|
getAlphaFunc = GetAlphaFunc_32bpp;
|
|
else if (inbpp == PICDEPTH_16BPP)
|
|
getAlphaFunc = GetAlphaFunc_16bpp;
|
|
else if (inbpp == PICDEPTH_8BPP)
|
|
getAlphaFunc = GetAlphaFunc_8bpp;
|
|
|
|
switch (outformat)
|
|
{
|
|
case PICFMT_PATCH32:
|
|
case PICFMT_DOOMPATCH32:
|
|
{
|
|
if (inbpp == PICDEPTH_32BPP)
|
|
writePatchPixel = WritePatchPixel_i32o32;
|
|
else if (inbpp == PICDEPTH_16BPP)
|
|
writePatchPixel = WritePatchPixel_i16o32;
|
|
else // PICFMT_PATCH
|
|
writePatchPixel = WritePatchPixel_i8o32;
|
|
break;
|
|
}
|
|
case PICFMT_PATCH16:
|
|
case PICFMT_DOOMPATCH16:
|
|
if (inbpp == PICDEPTH_32BPP)
|
|
writePatchPixel = WritePatchPixel_i32o16;
|
|
else if (inbpp == PICDEPTH_16BPP)
|
|
writePatchPixel = WritePatchPixel_i16o16;
|
|
else // PICFMT_PATCH
|
|
writePatchPixel = WritePatchPixel_i8o16;
|
|
break;
|
|
default: // PICFMT_PATCH
|
|
{
|
|
if (inbpp == PICDEPTH_32BPP)
|
|
writePatchPixel = WritePatchPixel_i32o8;
|
|
else if (inbpp == PICDEPTH_16BPP)
|
|
writePatchPixel = WritePatchPixel_i16o8;
|
|
else // PICFMT_PATCH
|
|
writePatchPixel = WritePatchPixel_i8o8;
|
|
break;
|
|
}
|
|
}
|
|
|
|
patch_t *out = Z_Calloc(sizeof(patch_t), PU_PATCH, NULL);
|
|
|
|
out->width = inwidth;
|
|
out->height = inheight;
|
|
out->leftoffset = inleftoffset;
|
|
out->topoffset = intopoffset;
|
|
|
|
size_t max_pixels = out->width * out->height;
|
|
unsigned num_posts = 0;
|
|
|
|
out->columns = Z_Calloc(sizeof(column_t) * out->width, PU_PATCH_DATA, NULL);
|
|
out->pixels = Z_Calloc(max_pixels * (outbpp / 8), PU_PATCH_DATA, NULL);
|
|
out->posts = NULL;
|
|
|
|
UINT8 *imgptr = out->pixels;
|
|
|
|
unsigned *column_posts = Z_Calloc(sizeof(unsigned) * inwidth, PU_STATIC, NULL);
|
|
|
|
// Write columns
|
|
for (INT32 x = 0; x < inwidth; x++)
|
|
{
|
|
post_t *post = NULL;
|
|
size_t post_data_offset = 0;
|
|
boolean was_opaque = false;
|
|
|
|
column_t *column = &out->columns[x];
|
|
column->pixels = imgptr;
|
|
column->posts = NULL;
|
|
column->num_posts = 0;
|
|
|
|
column_posts[x] = (unsigned)-1;
|
|
|
|
// Write pixels
|
|
for (INT32 y = 0; y < inheight; y++)
|
|
{
|
|
boolean opaque = false;
|
|
|
|
// Read pixel
|
|
void *input = readPixelFunc(picture, informat, x, y, inwidth, inheight, flags);
|
|
|
|
// Determine opacity
|
|
if (input != NULL)
|
|
opaque = getAlphaFunc(input, flags) > 0;
|
|
|
|
// End span if we have a transparent pixel
|
|
if (!opaque)
|
|
{
|
|
was_opaque = false;
|
|
continue;
|
|
}
|
|
|
|
if (!was_opaque)
|
|
{
|
|
num_posts++;
|
|
|
|
out->posts = Z_Realloc(out->posts, sizeof(post_t) * num_posts, PU_PATCH_DATA, NULL);
|
|
post = &out->posts[num_posts - 1];
|
|
post->topdelta = (size_t)y;
|
|
post->length = 0;
|
|
post->data_offset = post_data_offset;
|
|
if (column_posts[x] == (unsigned)-1)
|
|
column_posts[x] = num_posts - 1;
|
|
column->num_posts++;
|
|
}
|
|
|
|
was_opaque = true;
|
|
|
|
// Write the pixel
|
|
UINT8 *last_ptr = imgptr;
|
|
imgptr = writePatchPixel(last_ptr, input);
|
|
|
|
post->length++;
|
|
post_data_offset += imgptr - last_ptr;
|
|
}
|
|
}
|
|
|
|
UINT8 *old_pixels = out->pixels;
|
|
size_t total_pixels = imgptr - out->pixels;
|
|
if (total_pixels != max_pixels)
|
|
out->pixels = Z_Realloc(out->pixels, total_pixels, PU_PATCH_DATA, NULL);
|
|
|
|
for (INT16 x = 0; x < inwidth; x++)
|
|
{
|
|
column_t *column = &out->columns[x];
|
|
if (column->num_posts > 0)
|
|
column->posts = &out->posts[column_posts[x]];
|
|
if (old_pixels != out->pixels)
|
|
column->pixels = out->pixels + (column->pixels - old_pixels);
|
|
}
|
|
|
|
Z_Free(column_posts);
|
|
|
|
if (outsize != NULL)
|
|
*outsize = sizeof(patch_t);
|
|
|
|
return (void *)out;
|
|
}
|
|
|
|
/** Converts a picture to a flat.
|
|
*
|
|
* \param informat Input picture format.
|
|
* \param picture Input picture data.
|
|
* \param outformat Output picture format.
|
|
* \param outsize Output picture size, as a pointer.
|
|
* \param inwidth Input picture width.
|
|
* \param inheight Input picture height.
|
|
* \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 *outsize,
|
|
INT32 inwidth, INT32 inheight,
|
|
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;
|
|
|
|
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 == PICDEPTH_NONE)
|
|
I_Error("Picture_FlatConvert: unknown input bits per pixel?!");
|
|
if (outbpp == PICDEPTH_NONE)
|
|
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;
|
|
if (Picture_IsDoomPatchFormat(informat))
|
|
{
|
|
softwarepatch_t *doompatch = ((softwarepatch_t *)picture);
|
|
inwidth = SHORT(doompatch->width);
|
|
inheight = SHORT(doompatch->height);
|
|
}
|
|
else
|
|
{
|
|
inwidth = inpatch->width;
|
|
inheight = inpatch->height;
|
|
}
|
|
}
|
|
|
|
size = (inwidth * inheight) * (outbpp / 8);
|
|
outflat = Z_Calloc(size, PU_STATIC, NULL);
|
|
if (outsize)
|
|
*outsize = size;
|
|
|
|
// Set transparency
|
|
if (outbpp == PICDEPTH_8BPP)
|
|
memset(outflat, TRANSPARENTPIXEL, size);
|
|
|
|
for (y = 0; y < inheight; y++)
|
|
for (x = 0; x < inwidth; x++)
|
|
{
|
|
void *input = NULL;
|
|
int sx = x;
|
|
int sy = y;
|
|
|
|
if (flags & PICFLAGS_XFLIP)
|
|
sx = inwidth - x - 1;
|
|
if (flags & PICFLAGS_YFLIP)
|
|
sy = inheight - y - 1;
|
|
|
|
size_t in_offs = ((sy * inwidth) + sx);
|
|
size_t out_offs = ((y * inwidth) + x);
|
|
|
|
// Read pixel
|
|
if (Picture_IsPatchFormat(informat))
|
|
input = Picture_GetPatchPixel(inpatch, informat, sx, sy, 0);
|
|
else if (Picture_IsFlatFormat(informat))
|
|
input = (UINT8 *)picture + (in_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 == PICDEPTH_32BPP)
|
|
{
|
|
RGBA_t out = *(RGBA_t *)input;
|
|
f32[out_offs] = out.rgba;
|
|
}
|
|
else if (inbpp == PICDEPTH_16BPP)
|
|
{
|
|
RGBA_t out = pMasterPalette[*((UINT16 *)input) & 0xFF];
|
|
f32[out_offs] = out.rgba;
|
|
}
|
|
else // PICFMT_PATCH
|
|
{
|
|
RGBA_t out = pMasterPalette[*((UINT8 *)input) & 0xFF];
|
|
f32[out_offs] = out.rgba;
|
|
}
|
|
break;
|
|
}
|
|
case PICFMT_FLAT16:
|
|
{
|
|
UINT16 *f16 = (UINT16 *)outflat;
|
|
if (inbpp == PICDEPTH_32BPP)
|
|
{
|
|
RGBA_t in = *(RGBA_t *)input;
|
|
UINT8 out = NearestColor(in.s.red, in.s.green, in.s.blue);
|
|
f16[out_offs] = (0xFF00 | out);
|
|
}
|
|
else if (inbpp == PICDEPTH_16BPP)
|
|
f16[out_offs] = *(UINT16 *)input;
|
|
else // PICFMT_PATCH
|
|
f16[out_offs] = (0xFF00 | *((UINT8 *)input));
|
|
break;
|
|
}
|
|
case PICFMT_FLAT:
|
|
{
|
|
UINT8 *f8 = (UINT8 *)outflat;
|
|
if (inbpp == PICDEPTH_32BPP)
|
|
{
|
|
RGBA_t in = *(RGBA_t *)input;
|
|
UINT8 out = NearestColor(in.s.red, in.s.green, in.s.blue);
|
|
f8[out_offs] = out;
|
|
}
|
|
else if (inbpp == PICDEPTH_16BPP)
|
|
{
|
|
UINT16 out = *(UINT16 *)input;
|
|
f8[out_offs] = (out & 0xFF);
|
|
}
|
|
else // PICFMT_PATCH
|
|
f8[out_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)
|
|
{
|
|
INT32 inbpp = Picture_FormatBPP(informat);
|
|
softwarepatch_t *doompatch = (softwarepatch_t *)patch;
|
|
boolean isdoompatch = Picture_IsDoomPatchFormat(informat);
|
|
|
|
if (patch == NULL)
|
|
I_Error("Picture_GetPatchPixel: patch == NULL");
|
|
|
|
INT16 width = (isdoompatch ? SHORT(doompatch->width) : patch->width);
|
|
INT16 height = (isdoompatch ? SHORT(doompatch->height) : patch->height);
|
|
|
|
if (x < 0 || x >= width || y < 0 || y >= height)
|
|
return NULL;
|
|
|
|
INT32 sx = (flags & PICFLAGS_XFLIP) ? (width-1)-x : x;
|
|
INT32 sy = (flags & PICFLAGS_YFLIP) ? (height-1)-y : y;
|
|
UINT8 *s8 = NULL;
|
|
UINT16 *s16 = NULL;
|
|
UINT32 *s32 = NULL;
|
|
|
|
if (isdoompatch)
|
|
{
|
|
INT32 prevdelta = -1;
|
|
INT32 colofs = LONG(doompatch->columnofs[sx]);
|
|
|
|
// Column offsets are pointers, so no casting is required.
|
|
doompost_t *column = (doompost_t *)((UINT8 *)doompatch + colofs);
|
|
|
|
while (column->topdelta != 0xff)
|
|
{
|
|
INT32 topdelta = column->topdelta;
|
|
if (topdelta <= prevdelta)
|
|
topdelta += prevdelta;
|
|
prevdelta = topdelta;
|
|
|
|
size_t ofs = sy - topdelta;
|
|
|
|
if (sy >= topdelta && ofs < column->length)
|
|
{
|
|
s8 = (UINT8 *)(column) + 3;
|
|
switch (inbpp)
|
|
{
|
|
case PICDEPTH_32BPP:
|
|
s32 = (UINT32 *)s8;
|
|
return &s32[ofs];
|
|
case PICDEPTH_16BPP:
|
|
s16 = (UINT16 *)s8;
|
|
return &s16[ofs];
|
|
default: // PICDEPTH_8BPP
|
|
return &s8[ofs];
|
|
}
|
|
}
|
|
|
|
if (inbpp == PICDEPTH_32BPP)
|
|
column = (doompost_t *)((UINT32 *)column + column->length);
|
|
else if (inbpp == PICDEPTH_16BPP)
|
|
column = (doompost_t *)((UINT16 *)column + column->length);
|
|
else
|
|
column = (doompost_t *)((UINT8 *)column + column->length);
|
|
column = (doompost_t *)((UINT8 *)column + 4);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
column_t *column = &patch->columns[sx];
|
|
for (unsigned i = 0; i < column->num_posts; i++)
|
|
{
|
|
post_t *post = &column->posts[i];
|
|
|
|
size_t ofs = sy - post->topdelta;
|
|
|
|
if (sy >= (INT32)post->topdelta && ofs < post->length)
|
|
{
|
|
s8 = column->pixels + post->data_offset;
|
|
switch (inbpp)
|
|
{
|
|
case PICDEPTH_32BPP:
|
|
s32 = (UINT32 *)s8;
|
|
return &s32[ofs];
|
|
case PICDEPTH_16BPP:
|
|
s16 = (UINT16 *)s8;
|
|
return &s16[ofs];
|
|
default: // PICDEPTH_8BPP
|
|
return &s8[ofs];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 = PICDEPTH_NONE;
|
|
switch (format)
|
|
{
|
|
case PICFMT_PATCH32:
|
|
case PICFMT_FLAT32:
|
|
case PICFMT_DOOMPATCH32:
|
|
case PICFMT_PNG:
|
|
bpp = PICDEPTH_32BPP;
|
|
break;
|
|
case PICFMT_PATCH16:
|
|
case PICFMT_FLAT16:
|
|
case PICFMT_DOOMPATCH16:
|
|
bpp = PICDEPTH_16BPP;
|
|
break;
|
|
case PICFMT_PATCH:
|
|
case PICFMT_FLAT:
|
|
case PICFMT_DOOMPATCH:
|
|
bpp = PICDEPTH_8BPP;
|
|
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 (Picture_IsInternalPatchFormat(format) || Picture_IsDoomPatchFormat(format));
|
|
}
|
|
|
|
/** Checks if the specified picture format is an internal patch.
|
|
*
|
|
* \param format Input picture format.
|
|
* \return True if the picture format is an internal patch, false if not.
|
|
*/
|
|
boolean Picture_IsInternalPatchFormat(pictureformat_t format)
|
|
{
|
|
switch (format)
|
|
{
|
|
case PICFMT_PATCH:
|
|
case PICFMT_PATCH16:
|
|
case PICFMT_PATCH32:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/** Checks if the specified picture format is a Doom patch.
|
|
*
|
|
* \param format Input picture format.
|
|
* \return True if the picture format is a Doom patch, false if not.
|
|
*/
|
|
boolean Picture_IsDoomPatchFormat(pictureformat_t format)
|
|
{
|
|
switch (format)
|
|
{
|
|
case PICFMT_DOOMPATCH:
|
|
case PICFMT_DOOMPATCH16:
|
|
case PICFMT_DOOMPATCH32:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/** 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 Doom patch.
|
|
* PICFMT_DOOMPATCH only.
|
|
*
|
|
* \param patch Input patch.
|
|
* \param picture Input patch size.
|
|
* \return True if the input patch is valid.
|
|
*/
|
|
boolean Picture_CheckIfDoomPatch(softwarepatch_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);
|
|
|
|
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 texnum The texture number.
|
|
* \return The converted flat.
|
|
*/
|
|
void *Picture_TextureToFlat(size_t texnum)
|
|
{
|
|
texture_t *texture;
|
|
|
|
UINT8 *converted;
|
|
size_t flatsize;
|
|
UINT8 *desttop, *dest, *deststop;
|
|
UINT8 *source;
|
|
|
|
if (texnum >= (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
|
|
texture = textures[texnum];
|
|
R_CheckTextureCache(texnum);
|
|
|
|
// 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 (size_t col = 0; col < (size_t)texture->width; col++, desttop++)
|
|
{
|
|
column_t *column = (column_t *)R_GetColumn(texnum, col);
|
|
for (unsigned i = 0; i < column->num_posts; i++)
|
|
{
|
|
post_t *post = &column->posts[i];
|
|
dest = desttop + (post->topdelta * texture->width);
|
|
source = column->pixels + post->data_offset;
|
|
for (size_t ofs = 0; dest < deststop && ofs < post->length; ofs++)
|
|
{
|
|
if (source[ofs] != TRANSPARENTPIXEL)
|
|
*dest = source[ofs];
|
|
dest += texture->width;
|
|
}
|
|
}
|
|
}
|
|
|
|
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) // https://web.archive.org/web/20230524232139/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);
|
|
}
|
|
|
|
static void PNG_warn(png_structp PNG, png_const_charp pngtext)
|
|
{
|
|
CONS_Debug(DBG_RENDER, "libpng warning at %p: %s", PNG, pngtext);
|
|
}
|
|
|
|
static png_byte grAb_chunk[5] = {'g', 'r', 'A', 'b', (png_byte)'\0'};
|
|
|
|
static png_bytep *PNG_Read(
|
|
const UINT8 *png,
|
|
INT32 *w, INT32 *h, INT16 *topoffset, INT16 *leftoffset,
|
|
boolean *use_palette, 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;
|
|
|
|
png_colorp palette;
|
|
int palette_size;
|
|
|
|
png_bytep trans = NULL;
|
|
int num_trans = 0;
|
|
|
|
#ifdef PNG_SETJMP_SUPPORTED
|
|
#ifdef USE_FAR_KEYWORD
|
|
jmp_buf jmpbuf;
|
|
#endif
|
|
#endif
|
|
|
|
png_io_t png_io;
|
|
png_bytep *row_pointers;
|
|
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
|
|
|
|
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;
|
|
|
|
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);
|
|
|
|
palette = NULL;
|
|
*use_palette = false;
|
|
|
|
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)
|
|
{
|
|
boolean usepal = false;
|
|
|
|
// Lactozilla: Check if the PNG has a palette, and if its color count
|
|
// matches the color count of SRB2's palette: 256 colors.
|
|
if (png_get_PLTE(png_ptr, png_info_ptr, &palette, &palette_size))
|
|
{
|
|
if (palette_size == 256 && pMasterPalette)
|
|
{
|
|
png_colorp pal = palette;
|
|
INT32 i;
|
|
|
|
usepal = true;
|
|
|
|
for (i = 0; i < 256; i++)
|
|
{
|
|
byteColor_t *curpal = &(pMasterPalette[i].s);
|
|
if (pal->red != curpal->red || pal->green != curpal->green || pal->blue != curpal->blue)
|
|
{
|
|
usepal = false;
|
|
break;
|
|
}
|
|
pal++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If any of the tRNS colors have an alpha lower than 0xFF, and that
|
|
// color is present on the image, the palette flag is disabled.
|
|
if (usepal)
|
|
{
|
|
png_uint_32 result = png_get_tRNS(png_ptr, png_info_ptr, &trans, &num_trans, NULL);
|
|
|
|
if ((result & PNG_INFO_tRNS) && num_trans > 0 && trans != NULL)
|
|
{
|
|
INT32 i;
|
|
for (i = 0; i < num_trans; i++)
|
|
{
|
|
// libpng will transform this image into RGBA even if
|
|
// the transparent index does not exist in the image,
|
|
// and there is no way around that.
|
|
if (trans[i] < 0xFF)
|
|
{
|
|
usepal = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (usepal)
|
|
*use_palette = true;
|
|
else
|
|
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);
|
|
}
|
|
|
|
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;
|
|
boolean palette = false;
|
|
png_bytep *row_pointers = NULL;
|
|
png_uint_32 width, height;
|
|
|
|
INT32 pngwidth, pngheight;
|
|
INT16 loffs = 0, toffs = 0;
|
|
|
|
if (png == NULL)
|
|
I_Error("Picture_PNGConvert: picture was NULL!");
|
|
|
|
if (w == NULL)
|
|
w = &pngwidth;
|
|
if (h == NULL)
|
|
h = &pngheight;
|
|
if (topoffset == NULL)
|
|
topoffset = &toffs;
|
|
if (leftoffset == NULL)
|
|
leftoffset = &loffs;
|
|
|
|
row_pointers = PNG_Read(png, w, h, topoffset, leftoffset, &palette, insize);
|
|
width = *w;
|
|
height = *h;
|
|
|
|
if (row_pointers == NULL)
|
|
I_Error("Picture_PNGConvert: row_pointers was NULL!");
|
|
|
|
// Find the output format's bits per pixel amount
|
|
outbpp = Picture_FormatBPP(outformat);
|
|
|
|
// Hack for patches because you'll want to preserve transparency.
|
|
if (Picture_IsPatchFormat(outformat))
|
|
{
|
|
// Force a higher bit depth
|
|
if (outbpp == PICDEPTH_8BPP)
|
|
outbpp = PICDEPTH_16BPP;
|
|
}
|
|
|
|
// Shouldn't happen.
|
|
if (outbpp == PICDEPTH_NONE)
|
|
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 == PICDEPTH_8BPP)
|
|
memset(flat, TRANSPARENTPIXEL, (width * height));
|
|
|
|
#ifdef PICTURE_PNG_USELOOKUP
|
|
if (outbpp != PICDEPTH_32BPP)
|
|
InitColorLUT(&png_colorlookup, pMasterPalette, false);
|
|
#endif
|
|
|
|
if (outbpp == PICDEPTH_32BPP)
|
|
{
|
|
RGBA_t out;
|
|
UINT32 *outflat = (UINT32 *)flat;
|
|
|
|
if (palette)
|
|
{
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
row = row_pointers[y];
|
|
for (x = 0; x < width; x++)
|
|
{
|
|
out = V_GetColor(row[x]);
|
|
outflat[((y * width) + x)] = out.rgba;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
row = row_pointers[y];
|
|
for (x = 0; x < width; x++)
|
|
{
|
|
png_bytep px = &(row[x * 4]);
|
|
if ((UINT8)px[3])
|
|
{
|
|
out.s.red = (UINT8)px[0];
|
|
out.s.green = (UINT8)px[1];
|
|
out.s.blue = (UINT8)px[2];
|
|
out.s.alpha = (UINT8)px[3];
|
|
outflat[((y * width) + x)] = out.rgba;
|
|
}
|
|
else
|
|
outflat[((y * width) + x)] = 0x00000000;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (outbpp == PICDEPTH_16BPP)
|
|
{
|
|
UINT16 *outflat = (UINT16 *)flat;
|
|
|
|
if (palette)
|
|
{
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
row = row_pointers[y];
|
|
for (x = 0; x < width; x++)
|
|
outflat[((y * width) + x)] = (0xFF << 8) | row[x];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
row = row_pointers[y];
|
|
for (x = 0; x < width; x++)
|
|
{
|
|
png_bytep px = &(row[x * 4]);
|
|
UINT8 red = (UINT8)px[0];
|
|
UINT8 green = (UINT8)px[1];
|
|
UINT8 blue = (UINT8)px[2];
|
|
UINT8 alpha = (UINT8)px[3];
|
|
|
|
if (alpha)
|
|
{
|
|
#ifdef PICTURE_PNG_USELOOKUP
|
|
UINT8 palidx = GetColorLUT(&png_colorlookup, red, green, blue);
|
|
#else
|
|
UINT8 palidx = NearestColor(red, green, blue);
|
|
#endif
|
|
outflat[((y * width) + x)] = (0xFF << 8) | palidx;
|
|
}
|
|
else
|
|
outflat[((y * width) + x)] = 0x0000;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else // 8bpp
|
|
{
|
|
UINT8 *outflat = (UINT8 *)flat;
|
|
|
|
if (palette)
|
|
{
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
row = row_pointers[y];
|
|
for (x = 0; x < width; x++)
|
|
outflat[((y * width) + x)] = row[x];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
row = row_pointers[y];
|
|
for (x = 0; x < width; x++)
|
|
{
|
|
png_bytep px = &(row[x * 4]);
|
|
UINT8 red = (UINT8)px[0];
|
|
UINT8 green = (UINT8)px[1];
|
|
UINT8 blue = (UINT8)px[2];
|
|
UINT8 alpha = (UINT8)px[3];
|
|
|
|
if (alpha)
|
|
{
|
|
#ifdef PICTURE_PNG_USELOOKUP
|
|
UINT8 palidx = GetColorLUT(&png_colorlookup, red, green, blue);
|
|
#else
|
|
UINT8 palidx = NearestColor(red, green, blue);
|
|
#endif
|
|
outflat[((y * width) + x)] = palidx;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Free the row pointers that we allocated for libpng.
|
|
for (y = 0; y < height; y++)
|
|
free(row_pointers[y]);
|
|
free(row_pointers);
|
|
|
|
// But wait, there's more!
|
|
if (Picture_IsPatchFormat(outformat))
|
|
{
|
|
void *converted;
|
|
pictureformat_t informat = PICFMT_NONE;
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Now, convert it!
|
|
converted = Picture_PatchConvert(informat, flat, outformat, outsize, (INT32)width, (INT32)height, *leftoffset, *topoffset, 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 topoffset A pointer to the input picture's vertical offset.
|
|
* \param leftoffset A pointer to the input picture's horizontal offset.
|
|
* \param size The input picture's size.
|
|
* \return True if reading the file succeeded, false if it failed.
|
|
*/
|
|
boolean Picture_PNGDimensions(UINT8 *png, INT32 *width, INT32 *height, INT16 *topoffset, INT16 *leftoffset, 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_voidp *user_chunk_ptr;
|
|
|
|
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, PNG_error, PNG_warn);
|
|
if (!png_ptr)
|
|
{
|
|
CONS_Alert(CONS_ERROR, "Picture_PNGDimensions: Couldn't initialize libpng!\n");
|
|
return false;
|
|
}
|
|
|
|
png_info_ptr = png_create_info_struct(png_ptr);
|
|
if (!png_info_ptr)
|
|
{
|
|
png_destroy_read_struct(&png_ptr, NULL, NULL);
|
|
CONS_Alert(CONS_ERROR, "Picture_PNGDimensions: libpng couldn't allocate memory!\n");
|
|
return false;
|
|
}
|
|
|
|
#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);
|
|
CONS_Alert(CONS_ERROR, "Picture_PNGDimensions: libpng load error!\n");
|
|
return false;
|
|
}
|
|
#ifdef USE_FAR_KEYWORD
|
|
png_memcpy(png_jmpbuf(png_ptr), jmpbuf, sizeof jmp_buf);
|
|
#endif
|
|
|
|
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;
|
|
|
|
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, &w, &h, &bit_depth, &color_type, NULL, NULL, NULL);
|
|
|
|
// 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);
|
|
}
|
|
|
|
png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL);
|
|
if (chunk.data)
|
|
Z_Free(chunk.data);
|
|
|
|
*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;
|
|
INT16 frameXPivot = 0;
|
|
INT16 frameYPivot = 0;
|
|
|
|
// 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)
|
|
{
|
|
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);
|
|
}
|
|
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
|
|
info->pivot[frameFrame].x = frameXPivot;
|
|
info->pivot[frameFrame].y = frameYPivot;
|
|
}
|
|
|
|
//
|
|
// R_ParseSpriteInfo
|
|
//
|
|
// Parse a SPRTINFO lump.
|
|
//
|
|
static void R_ParseSpriteInfo(boolean spr2)
|
|
{
|
|
spriteinfo_t *info;
|
|
char *sprinfoToken;
|
|
size_t sprinfoTokenLength;
|
|
char newSpriteName[MAXSPRITENAME + 1]; // no longer dynamically allocated
|
|
spritenum_t sprnum = NUMSPRITES;
|
|
playersprite_t spr2num = NUMPLAYERSPRITES;
|
|
INT32 i;
|
|
UINT8 *skinnumbers = NULL;
|
|
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 > MAXSPRITENAME)
|
|
I_Error("Error parsing SPRTINFO lump: Sprite name \"%s\" is longer than %d characters", sprinfoToken, MAXSPRITENAME);
|
|
strcpy(newSpriteName, sprinfoToken);
|
|
strupr(newSpriteName); // Just do this now so we don't have to worry about it
|
|
Z_Free(sprinfoToken);
|
|
|
|
if (!spr2)
|
|
{
|
|
sprnum = R_GetSpriteNumByName(newSpriteName);
|
|
if (sprnum == NUMSPRITES)
|
|
I_Error("Error parsing SPRTINFO lump: Unknown sprite name \"%s\"", newSpriteName);
|
|
}
|
|
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;
|
|
|
|
// 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);
|
|
|
|
if (skinnumbers == NULL)
|
|
skinnumbers = Z_Malloc(sizeof(UINT8) * numskins, PU_STATIC, NULL);
|
|
skinnumbers[foundskins] = (UINT8)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++)
|
|
{
|
|
skin_t *skin = skins[skinnumbers[i]];
|
|
spriteinfo_t *sprinfo = skin->sprinfo;
|
|
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);
|
|
if (skinnumbers)
|
|
Z_Free(skinnumbers);
|
|
}
|
|
|
|
//
|
|
// 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 and SPR_ lumps as SpriteInfo
|
|
if (!memcmp(name, "SPRTINFO", 8) || !memcmp(name, "SPR_", 4))
|
|
R_ParseSPRTINFOLump(wadnum, i);
|
|
}
|
|
}
|