diff --git a/.gitignore b/.gitignore index d52256030..1f6ed08e1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,11 +2,6 @@ obj/ eduke32 -mapster32 -voidsw -voidsw-editor -kenbuild -kenbuild-editor apps/ *.exe /*.dll @@ -25,7 +20,6 @@ apps/ *.log *.cache *.cfg -textures texturecache *.index diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 55fe28d0a..5277f6fb2 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -684,6 +684,8 @@ file( GLOB HEADER_FILES #sw/src/*.h Shadow Warrior does not work yet. thirdparty/include/*.h thirdparty/include/*.hpp + common/textures/*.h + common/textures/formats/*.h ) @@ -967,6 +969,20 @@ set (PCH_SOURCES common/utility/file_zip.cpp common/utility/resourcefile.cpp common/utility/matrix.cpp + common/utility/m_png.cpp + common/utility/memarena.cpp + + common/textures/bitmap.cpp + common/textures/texture.cpp + common/textures/image.cpp + common/textures/imagetexture.cpp + common/textures/formats/buildtexture.cpp + common/textures/formats/ddstexture.cpp + common/textures/formats/jpegtexture.cpp + common/textures/formats/pcxtexture.cpp + common/textures/formats/pngtexture.cpp + common/textures/formats/tgatexture.cpp + ) if( MSVC ) @@ -1023,6 +1039,7 @@ include_directories( common common/utility common/console + common/textures platform ${CMAKE_BINARY_DIR}/libraries/gdtoa @@ -1123,6 +1140,8 @@ install(TARGETS demolition COMPONENT "Game executable") source_group("Utility" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/utility/.+") +source_group("Code\\Textures" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/textures/.+") +source_group("Code\\Textures\\Formats" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/textures/formats/.+") source_group("Utility\\Audiolib" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/audiolib/.+") source_group("Utility\\Audiolib Headers" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/audiolib/include/.+") source_group("Utility\\Audiolib Sources" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/audiolib/src/.+") diff --git a/source/common/textures/bitmap.cpp b/source/common/textures/bitmap.cpp new file mode 100644 index 000000000..2f786eb69 --- /dev/null +++ b/source/common/textures/bitmap.cpp @@ -0,0 +1,142 @@ +/* +** bitmap.cpp +** +**--------------------------------------------------------------------------- +** Copyright 2008 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +** +*/ + +#include +#include "bitmap.h" + + +//=========================================================================== +// +// multi-format pixel copy with colormap application +// requires the previously defined conversion classes to work +// +//=========================================================================== +template +void iCopyColors(uint8_t *pout, const uint8_t *pin, int count, int step, + uint8_t tr, uint8_t tg, uint8_t tb) +{ + int i; + int a; + + for(i=0;i, + iCopyColors, + iCopyColors, + iCopyColors, + iCopyColors, + iCopyColors, + iCopyColors, + iCopyColors, + iCopyColors, + iCopyColors, + iCopyColors +}; + +//=========================================================================== +// +// True Color texture copy function +// +//=========================================================================== +void FBitmap::CopyPixelDataRGB(int originx, int originy, const uint8_t *patch, int srcwidth, + int srcheight, int step_x, int step_y, int rotate, int ct, + int r, int g, int b) +{ + uint8_t *buffer = data + 4 * originx + Pitch * originy; + for (int y=0;y +void iCopyPaletted(uint8_t *buffer, const uint8_t * patch, int srcwidth, int srcheight, int Pitch, + int step_x, int step_y, int rotate, PalEntry * palette) +{ + int x,y,pos; + + for (y=0;y(buffer, patch, srcwidth, srcheight, Pitch, + step_x, step_y, rotate, palette); +} + diff --git a/source/common/textures/bitmap.h b/source/common/textures/bitmap.h new file mode 100644 index 000000000..b5b1bb257 --- /dev/null +++ b/source/common/textures/bitmap.h @@ -0,0 +1,360 @@ +/* +** bitmap.h +** +**--------------------------------------------------------------------------- +** Copyright 2008 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +** +*/ + + +#ifndef __BITMAP_H__ +#define __BITMAP_H__ + +#include +#include "palentry.h" +#include +#include +#include "templates.h" + +struct FCopyInfo; + +struct FClipRect +{ + int x, y, width, height; + + bool Intersect(int ix, int iy, int iw, int ih); +}; + +typedef int blend_t; + +enum +{ + BLENDBITS = 16, + BLENDUNIT = (1<>8; } +}; + +struct cRGBT +{ + static __forceinline unsigned char R(const unsigned char * p) { return p[0]; } + static __forceinline unsigned char G(const unsigned char * p) { return p[1]; } + static __forceinline unsigned char B(const unsigned char * p) { return p[2]; } + static __forceinline unsigned char A(const unsigned char * p, uint8_t r, uint8_t g, uint8_t b) { return (p[0] != r || p[1] != g || p[2] != b) ? 255 : 0; } + static __forceinline int Gray(const unsigned char * p) { return (p[0]*77 + p[1]*143 + p[2]*36)>>8; } +}; + +struct cRGBA +{ + enum + { + RED = 0, + GREEN = 1, + BLUE = 1, + ALPHA = 3 + }; + static __forceinline unsigned char R(const unsigned char * p) { return p[0]; } + static __forceinline unsigned char G(const unsigned char * p) { return p[1]; } + static __forceinline unsigned char B(const unsigned char * p) { return p[2]; } + static __forceinline unsigned char A(const unsigned char * p, uint8_t x, uint8_t y, uint8_t z) { return p[3]; } + static __forceinline int Gray(const unsigned char * p) { return (p[0]*77 + p[1]*143 + p[2]*36)>>8; } +}; + +struct cIA +{ + static __forceinline unsigned char R(const unsigned char * p) { return p[0]; } + static __forceinline unsigned char G(const unsigned char * p) { return p[0]; } + static __forceinline unsigned char B(const unsigned char * p) { return p[0]; } + static __forceinline unsigned char A(const unsigned char * p, uint8_t x, uint8_t y, uint8_t z) { return p[1]; } + static __forceinline int Gray(const unsigned char * p) { return p[0]; } +}; + +struct cCMYK +{ + static __forceinline unsigned char R(const unsigned char * p) { return p[3] - (((256-p[0])*p[3]) >> 8); } + static __forceinline unsigned char G(const unsigned char * p) { return p[3] - (((256-p[1])*p[3]) >> 8); } + static __forceinline unsigned char B(const unsigned char * p) { return p[3] - (((256-p[2])*p[3]) >> 8); } + static __forceinline unsigned char A(const unsigned char * p, uint8_t x, uint8_t y, uint8_t z) { return 255; } + static __forceinline int Gray(const unsigned char * p) { return (R(p)*77 + G(p)*143 + B(p)*36)>>8; } +}; + +struct cYCbCr +{ + static __forceinline unsigned char R(const unsigned char * p) { return clamp((int)(p[0] + 1.40200 * (int(p[2]) - 0x80)), 0, 255); } + static __forceinline unsigned char G(const unsigned char * p) { return clamp((int)(p[0] - 0.34414 * (int(p[1] - 0x80)) - 0.71414 * (int(p[2]) - 0x80)), 0, 255); } + static __forceinline unsigned char B(const unsigned char * p) { return clamp((int)(p[0] + 1.77200 * (int(p[1]) - 0x80)), 0, 255); } + static __forceinline unsigned char A(const unsigned char * p, uint8_t x, uint8_t y, uint8_t z) { return 255; } + static __forceinline int Gray(const unsigned char * p) { return (R(p) * 77 + G(p) * 143 + B(p) * 36) >> 8; } +}; + +struct cBGR +{ + static __forceinline unsigned char R(const unsigned char * p) { return p[2]; } + static __forceinline unsigned char G(const unsigned char * p) { return p[1]; } + static __forceinline unsigned char B(const unsigned char * p) { return p[0]; } + static __forceinline unsigned char A(const unsigned char * p, uint8_t x, uint8_t y, uint8_t z) { return 255; } + static __forceinline int Gray(const unsigned char * p) { return (p[2]*77 + p[1]*143 + p[0]*36)>>8; } +}; + +struct cBGRA +{ + enum + { + RED = 2, + GREEN = 1, + BLUE = 0, + ALPHA = 3 + }; + static __forceinline unsigned char R(const unsigned char * p) { return p[2]; } + static __forceinline unsigned char G(const unsigned char * p) { return p[1]; } + static __forceinline unsigned char B(const unsigned char * p) { return p[0]; } + static __forceinline unsigned char A(const unsigned char * p, uint8_t x, uint8_t y, uint8_t z) { return p[3]; } + static __forceinline int Gray(const unsigned char * p) { return (p[2]*77 + p[1]*143 + p[0]*36)>>8; } +}; + +struct cARGB +{ + enum + { + RED = 1, + GREEN = 2, + BLUE = 3, + ALPHA = 0 + }; + static __forceinline unsigned char R(const unsigned char * p) { return p[1]; } + static __forceinline unsigned char G(const unsigned char * p) { return p[2]; } + static __forceinline unsigned char B(const unsigned char * p) { return p[3]; } + static __forceinline unsigned char A(const unsigned char * p, uint8_t x, uint8_t y, uint8_t z) { return p[0]; } + static __forceinline int Gray(const unsigned char * p) { return (p[1]*77 + p[2]*143 + p[3]*36)>>8; } +}; + +struct cI16 +{ + static __forceinline unsigned char R(const unsigned char * p) { return p[1]; } + static __forceinline unsigned char G(const unsigned char * p) { return p[1]; } + static __forceinline unsigned char B(const unsigned char * p) { return p[1]; } + static __forceinline unsigned char A(const unsigned char * p, uint8_t x, uint8_t y, uint8_t z) { return 255; } + static __forceinline int Gray(const unsigned char * p) { return p[1]; } +}; + +struct cRGB555 +{ + static __forceinline unsigned char R(const unsigned char * p) { return (((*(uint16_t*)p)&0x1f)<<3); } + static __forceinline unsigned char G(const unsigned char * p) { return (((*(uint16_t*)p)&0x3e0)>>2); } + static __forceinline unsigned char B(const unsigned char * p) { return (((*(uint16_t*)p)&0x7c00)>>7); } + static __forceinline unsigned char A(const unsigned char * p, uint8_t x, uint8_t y, uint8_t z) { return 255; } + static __forceinline int Gray(const unsigned char * p) { return (R(p)*77 + G(p)*143 + B(p)*36)>>8; } +}; + +struct cPalEntry +{ + static __forceinline unsigned char R(const unsigned char * p) { return ((PalEntry*)p)->r; } + static __forceinline unsigned char G(const unsigned char * p) { return ((PalEntry*)p)->g; } + static __forceinline unsigned char B(const unsigned char * p) { return ((PalEntry*)p)->b; } + static __forceinline unsigned char A(const unsigned char * p, uint8_t x, uint8_t y, uint8_t z) { return ((PalEntry*)p)->a; } + static __forceinline int Gray(const unsigned char * p) { return (R(p)*77 + G(p)*143 + B(p)*36)>>8; } +}; + +struct bCopy +{ + static __forceinline void OpC(uint8_t &d, uint8_t s, uint8_t a) { d = s; } + static __forceinline void OpA(uint8_t &d, uint8_t s) { d = s; } + static __forceinline bool ProcessAlpha0() { return false; } +}; + + +#endif diff --git a/source/common/textures/formats/buildtexture.cpp b/source/common/textures/formats/buildtexture.cpp new file mode 100644 index 000000000..5ccd10c25 --- /dev/null +++ b/source/common/textures/formats/buildtexture.cpp @@ -0,0 +1,248 @@ +/* +** buildtexture.cpp +** Handling Build textures (now as a usable editing feature!) +** +**--------------------------------------------------------------------------- +** Copyright 2004-2006 Randy Heit +** Copyright 2018 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +** +*/ + +#include +#include "zstring.h" +#include "files.h" +#include "templates.h" +#include "bitmap.h" +#include "textures/textures.h" +#include "image.h" + + +//========================================================================== +// +// A texture defined in a Build TILESxxx.ART file +// +//========================================================================== + +class FBuildTexture : public FImageSource +{ +public: + FBuildTexture (const FString &pathprefix, int tilenum, const uint8_t *pixels, int width, int height, int left, int top); + TArray CreatePalettedPixels(int conversion) override; + +protected: + const uint8_t *RawPixels; +}; + + +//========================================================================== +// +// +// +//========================================================================== + +FBuildTexture::FBuildTexture(const FString &pathprefix, int tilenum, const uint8_t *pixels, int width, int height, int left, int top) +: RawPixels (pixels) +{ + Width = width; + Height = height; + LeftOffset = left; + TopOffset = top; +} + +TArray FBuildTexture::CreatePalettedPixels(int conversion) +{ + TArray Pixels(Width * Height, true); + memcpy(Pixels.Data(), RawPixels, Width * Height); + return Pixels; +} + +#if 0 +//=========================================================================== +// +// AddTiles +// +// Adds all the tiles in an artfile to the texture manager. +// +//=========================================================================== + +void FTextureManager::AddTiles (const FString &pathprefix, const void *tiles) +{ + +// int numtiles = LittleLong(((uint32_t *)tiles)[1]); // This value is not reliable + int tilestart = LittleLong(((uint32_t *)tiles)[2]); + int tileend = LittleLong(((uint32_t *)tiles)[3]); + const uint16_t *tilesizx = &((const uint16_t *)tiles)[8]; + const uint16_t *tilesizy = &tilesizx[tileend - tilestart + 1]; + const uint32_t *picanm = (const uint32_t *)&tilesizy[tileend - tilestart + 1]; + const uint8_t *tiledata = (const uint8_t *)&picanm[tileend - tilestart + 1]; + + for (int i = tilestart; i <= tileend; ++i) + { + int pic = i - tilestart; + int width = LittleShort(tilesizx[pic]); + int height = LittleShort(tilesizy[pic]); + uint32_t anm = LittleLong(picanm[pic]); + int xoffs = (int8_t)((anm >> 8) & 255) + width/2; + int yoffs = (int8_t)((anm >> 16) & 255) + height/2; + int size = width*height; + //FTextureID texnum; + FTexture *tex; + + if (width <= 0 || height <= 0) continue; + + tex = new FImageTexture(new FBuildTexture (pathprefix, i, tiledata, width, height, xoffs, yoffs)); + //texnum = AddTexture (tex); + tiledata += size; + tex->Name.Format("%sTILE%04d", pathprefix.GetChars(), i); + + // I have no idea if this makes sense or if we better leave animation control to the game code. + // To be decided later. +#if 0 + if ((picanm[pic] & 63) && (picanm[pic] & 192)) + { + int type, speed; + + switch (picanm[pic] & 192) + { + case 64: type = 2; break; + case 128: type = 0; break; + case 192: type = 1; break; + default: type = 0; break; // Won't happen, but GCC bugs me if I don't put this here. + } + + speed = (anm >> 24) & 15; + speed = MAX (1, (1 << speed) * 1000 / 120); // Convert from 120 Hz to 1000 Hz. + + AddSimpleAnim (texnum, picanm[pic] & 63, type, speed); + } +#endif + + // Blood's rotation types: + // 0 - Single + // 1 - 5 Full + // 2 - 8 Full + // 3 - Bounce (looks no different from Single; seems to signal bouncy sprites) + // 4 - 5 Half (not used in game) + // 5 - 3 Flat (not used in game) + // 6 - Voxel + // 7 - Spin Voxel + +#if 0 + int rotType = (anm >> 28) & 7; + if (rotType == 1) + { + spriteframe_t rot; + rot.Texture[0] = + rot.Texture[1] = texnum; + for (int j = 1; j < 4; ++j) + { + rot.Texture[j*2] = + rot.Texture[j*2+1] = + rot.Texture[16-j*2] = + rot.Texture[17-j*2] = texnum.GetIndex() + j; + } + rot.Texture[8] = + rot.Texture[9] = texnum.GetIndex() + 4; + rot.Flip = 0x00FC; + rot.Voxel = NULL; + tex->Rotations = SpriteFrames.Push (rot); + } + else if (rotType == 2) + { + spriteframe_t rot; + rot.Texture[0] = + rot.Texture[1] = texnum; + for (int j = 1; j < 8; ++j) + { + rot.Texture[16-j*2] = + rot.Texture[17-j*2] = texnum.GetIndex() + j; + } + rot.Flip = 0; + rot.Voxel = NULL; + tex->Rotations = SpriteFrames.Push (rot); + } +#endif + } +} + +//=========================================================================== +// +// CountTiles +// +// Returns the number of tiles provided by an artfile +// +//=========================================================================== + +static int CountTiles (const void *tiles) +{ + int version = LittleLong(*(uint32_t *)tiles); + if (version != 1) + { + return 0; + } + + int tilestart = LittleLong(((uint32_t *)tiles)[2]); + int tileend = LittleLong(((uint32_t *)tiles)[3]); + + return tileend >= tilestart ? tileend - tilestart + 1 : 0; +} + +//=========================================================================== +// +// R_CountBuildTiles +// +// Returns the number of tiles found. Also loads all the data for +// R_InitBuildTiles() to process later. +// +//=========================================================================== + +void FTextureManager::InitBuildTiles(TArray &tileFiles) +{ + int numtiles; + int totaltiles = 0; + + for (auto &artpath : tileFiles) + { + int lumpnum = fileSystem.FindFile(artpath); + if (lumpnum >= 0) + { + BuildTileData.Reserve(1); + auto &artdata = BuildTileData.Last(); + artdata = fileSystem.GetFileData(lumpnum); + + if ((numtiles = CountTiles(artdata.Data())) > 0) + { + AddTiles("", &artdata[0]); + totaltiles += numtiles; + } + } + } +} + +#endif \ No newline at end of file diff --git a/source/common/textures/formats/ddstexture.cpp b/source/common/textures/formats/ddstexture.cpp new file mode 100644 index 000000000..c9e1743e1 --- /dev/null +++ b/source/common/textures/formats/ddstexture.cpp @@ -0,0 +1,760 @@ +/* +** pngtexture.cpp +** Texture class for DDS images +** +**--------------------------------------------------------------------------- +** Copyright 2006-2007 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +** DDS is short for "DirectDraw Surface" and is essentially that. It's +** interesting to us because it is a standard file format for DXTC/S3TC +** encoded images. Look up "DDS File Reference" in the DirectX SDK or +** the online MSDN documentation to the specs for this file format. Look up +** "Compressed Texture Resources" for information about DXTC encoding. +** +** Perhaps the most important part of DXTC to realize is that every 4x4 +** pixel block can only have four different colors, and only two of those +** are discrete. So depending on the texture, there may be very noticable +** quality degradation, or it may look virtually indistinguishable from +** the uncompressed texture. +** +** Note: Although this class supports reading RGB textures from a DDS, +** DO NOT use DDS images with plain RGB data. PNG does everything useful +** better. Since DDS lets the R, G, B, and A components lie anywhere in +** the pixel data, it is fairly inefficient to process. +*/ + +#include +#include "files.h" +#include "bitmap.h" +#include "image.h" +#include "m_png.h" +#include "cache1d.h" + +// Since we want this to compile under Linux too, we need to define this +// stuff ourselves instead of including a DirectX header. + +enum +{ + ID_DDS = MAKE_ID('D', 'D', 'S', ' '), + ID_DXT1 = MAKE_ID('D', 'X', 'T', '1'), + ID_DXT2 = MAKE_ID('D', 'X', 'T', '2'), + ID_DXT3 = MAKE_ID('D', 'X', 'T', '3'), + ID_DXT4 = MAKE_ID('D', 'X', 'T', '4'), + ID_DXT5 = MAKE_ID('D', 'X', 'T', '5'), + + // Bits in dwFlags + DDSD_CAPS = 0x00000001, + DDSD_HEIGHT = 0x00000002, + DDSD_WIDTH = 0x00000004, + DDSD_PITCH = 0x00000008, + DDSD_PIXELFORMAT = 0x00001000, + DDSD_MIPMAPCOUNT = 0x00020000, + DDSD_LINEARSIZE = 0x00080000, + DDSD_DEPTH = 0x00800000, + + // Bits in ddpfPixelFormat + DDPF_ALPHAPIXELS = 0x00000001, + DDPF_FOURCC = 0x00000004, + DDPF_RGB = 0x00000040, + + // Bits in DDSCAPS2.dwCaps1 + DDSCAPS_COMPLEX = 0x00000008, + DDSCAPS_TEXTURE = 0x00001000, + DDSCAPS_MIPMAP = 0x00400000, + + // Bits in DDSCAPS2.dwCaps2 + DDSCAPS2_CUBEMAP = 0x00000200, + DDSCAPS2_CUBEMAP_POSITIVEX = 0x00000400, + DDSCAPS2_CUBEMAP_NEGATIVEX = 0x00000800, + DDSCAPS2_CUBEMAP_POSITIVEY = 0x00001000, + DDSCAPS2_CUBEMAP_NEGATIVEY = 0x00002000, + DDSCAPS2_CUBEMAP_POSITIVEZ = 0x00004000, + DDSCAPS2_CUBEMAP_NEGATIZEZ = 0x00008000, + DDSCAPS2_VOLUME = 0x00200000, +}; + +//========================================================================== +// +// +// +//========================================================================== + +struct DDPIXELFORMAT +{ + uint32_t Size; // Must be 32 + uint32_t Flags; + uint32_t FourCC; + uint32_t RGBBitCount; + uint32_t RBitMask, GBitMask, BBitMask; + uint32_t RGBAlphaBitMask; +}; + +struct DDCAPS2 +{ + uint32_t Caps1, Caps2; + uint32_t Reserved[2]; +}; + +struct DDSURFACEDESC2 +{ + uint32_t Size; // Must be 124. DevIL claims some writers set it to 'DDS ' instead. + uint32_t Flags; + uint32_t Height; + uint32_t Width; + union + { + int32_t Pitch; + uint32_t LinearSize; + }; + uint32_t Depth; + uint32_t MipMapCount; + uint32_t Reserved1[11]; + DDPIXELFORMAT PixelFormat; + DDCAPS2 Caps; + uint32_t Reserved2; +}; + +struct DDSFileHeader +{ + uint32_t Magic; + DDSURFACEDESC2 Desc; +}; + + +//========================================================================== +// +// A DDS image, with DXTx compression +// +//========================================================================== + +class FDDSTexture : public FImageSource +{ + enum + { + PIX_Palette = 0, + PIX_Alphatex = 1, + PIX_ARGB = 2 + }; +public: + FDDSTexture (FileReader &lump, void *surfdesc); + +protected: + uint32_t Format; + + uint32_t RMask, GMask, BMask, AMask; + uint8_t RShiftL, GShiftL, BShiftL, AShiftL; + uint8_t RShiftR, GShiftR, BShiftR, AShiftR; + + int32_t Pitch; + uint32_t LinearSize; + + static void CalcBitShift (uint32_t mask, uint8_t *lshift, uint8_t *rshift); + + void ReadRGB (FileReader &lump, uint8_t *buffer, int pixelmode); + void DecompressDXT1 (FileReader &lump, uint8_t *buffer, int pixelmode); + void DecompressDXT3 (FileReader &lump, bool premultiplied, uint8_t *buffer, int pixelmode); + void DecompressDXT5 (FileReader &lump, bool premultiplied, uint8_t *buffer, int pixelmode); + + int CopyPixels(FBitmap *bmp, int conversion) override; + + friend class FTexture; +}; + + +//========================================================================== +// +// +// +//========================================================================== + +static bool CheckDDS (FileReader &file) +{ + DDSFileHeader Header; + + file.Seek(0, FileReader::SeekSet); + if (file.Read (&Header, sizeof(Header)) != sizeof(Header)) + { + return false; + } + return Header.Magic == ID_DDS && + (LittleLong(Header.Desc.Size) == sizeof(DDSURFACEDESC2) || Header.Desc.Size == ID_DDS) && + LittleLong(Header.Desc.PixelFormat.Size) == sizeof(DDPIXELFORMAT) && + (LittleLong(Header.Desc.Flags) & (DDSD_CAPS | DDSD_PIXELFORMAT | DDSD_WIDTH | DDSD_HEIGHT)) == (DDSD_CAPS | DDSD_PIXELFORMAT | DDSD_WIDTH | DDSD_HEIGHT) && + Header.Desc.Width != 0 && + Header.Desc.Height != 0; +} + +//========================================================================== +// +// +// +//========================================================================== + +FImageSource *DDSImage_TryCreate (FileReader &data) +{ + union + { + DDSURFACEDESC2 surfdesc; + uint32_t byteswapping[sizeof(DDSURFACEDESC2) / 4]; + }; + + if (!CheckDDS(data)) return NULL; + + data.Seek(4, FileReader::SeekSet); + data.Read (&surfdesc, sizeof(surfdesc)); + +#ifdef __BIG_ENDIAN__ + // Every single element of the header is a uint32_t + for (unsigned int i = 0; i < sizeof(DDSURFACEDESC2) / 4; ++i) + { + byteswapping[i] = LittleLong(byteswapping[i]); + } + // Undo the byte swap for the pixel format + surfdesc.PixelFormat.FourCC = LittleLong(surfdesc.PixelFormat.FourCC); +#endif + + if (surfdesc.PixelFormat.Flags & DDPF_FOURCC) + { + // Check for supported FourCC + if (surfdesc.PixelFormat.FourCC != ID_DXT1 && + surfdesc.PixelFormat.FourCC != ID_DXT2 && + surfdesc.PixelFormat.FourCC != ID_DXT3 && + surfdesc.PixelFormat.FourCC != ID_DXT4 && + surfdesc.PixelFormat.FourCC != ID_DXT5) + { + return NULL; + } + if (!(surfdesc.Flags & DDSD_LINEARSIZE)) + { + return NULL; + } + } + else if (surfdesc.PixelFormat.Flags & DDPF_RGB) + { + if ((surfdesc.PixelFormat.RGBBitCount >> 3) < 1 || + (surfdesc.PixelFormat.RGBBitCount >> 3) > 4) + { + return NULL; + } + if ((surfdesc.Flags & DDSD_PITCH) && (surfdesc.Pitch <= 0)) + { + return NULL; + } + } + else + { + return NULL; + } + return new FDDSTexture (data, &surfdesc); +} + +//========================================================================== +// +// +// +//========================================================================== + +FDDSTexture::FDDSTexture (FileReader &lump, void *vsurfdesc) +{ + DDSURFACEDESC2 *surf = (DDSURFACEDESC2 *)vsurfdesc; + + bMasked = false; + Width = uint16_t(surf->Width); + Height = uint16_t(surf->Height); + + if (surf->PixelFormat.Flags & DDPF_FOURCC) + { + Format = surf->PixelFormat.FourCC; + Pitch = 0; + LinearSize = surf->LinearSize; + } + else // DDPF_RGB + { + Format = surf->PixelFormat.RGBBitCount >> 3; + CalcBitShift (RMask = surf->PixelFormat.RBitMask, &RShiftL, &RShiftR); + CalcBitShift (GMask = surf->PixelFormat.GBitMask, &GShiftL, &GShiftR); + CalcBitShift (BMask = surf->PixelFormat.BBitMask, &BShiftL, &BShiftR); + if (surf->PixelFormat.Flags & DDPF_ALPHAPIXELS) + { + CalcBitShift (AMask = surf->PixelFormat.RGBAlphaBitMask, &AShiftL, &AShiftR); + } + else + { + AMask = 0; + AShiftL = AShiftR = 0; + } + if (surf->Flags & DDSD_PITCH) + { + Pitch = surf->Pitch; + } + else + { + Pitch = (Width * Format + 3) & ~3; + } + LinearSize = Pitch * Height; + } +} + +//========================================================================== +// +// Returns the number of bits the color must be shifted to produce +// an 8-bit value, as in: +// +// c = (color & mask) << lshift; +// c |= c >> rshift; +// c >>= 24; +// +// For any color of at least 4 bits, this ensures that the result +// of the calculation for c will be fully saturated, given a maximum +// value for the input bit mask. +// +//========================================================================== + +void FDDSTexture::CalcBitShift (uint32_t mask, uint8_t *lshiftp, uint8_t *rshiftp) +{ + uint8_t shift; + + if (mask == 0) + { + *lshiftp = *rshiftp = 0; + return; + } + + shift = 0; + while ((mask & 0x80000000) == 0) + { + mask <<= 1; + shift++; + } + *lshiftp = shift; + + shift = 0; + while (mask & 0x80000000) + { + mask <<= 1; + shift++; + } + *rshiftp = shift; +} + +//========================================================================== +// +// Note that pixel size == 8 is column-major, but 32 is row-major! +// +//========================================================================== + +void FDDSTexture::ReadRGB (FileReader &lump, uint8_t *buffer, int pixelmode) +{ + uint32_t x, y; + uint32_t amask = AMask == 0 ? 0 : 0x80000000 >> AShiftL; + uint8_t *linebuff = new uint8_t[Pitch]; + + for (y = Height; y > 0; --y) + { + uint8_t *buffp = linebuff; + uint8_t *pixelp = pixelmode == PIX_ARGB ? buffer + 4 * (y - 1)*Width : buffer + y - 1; + lump.Read (linebuff, Pitch); + for (x = Width; x > 0; --x) + { + uint32_t c; + if (Format == 4) + { + c = LittleLong(*(uint32_t *)buffp); buffp += 4; + } + else if (Format == 2) + { + c = LittleShort(*(uint16_t *)buffp); buffp += 2; + } + else if (Format == 3) + { + c = buffp[0] | (buffp[1] << 8) | (buffp[2] << 16); buffp += 3; + } + else // Format == 1 + { + c = *buffp++; + } + if (pixelmode != PIX_ARGB) + { + assert(false); + } + else + { + uint32_t r = (c & RMask) << RShiftL; r |= r >> RShiftR; + uint32_t g = (c & GMask) << GShiftL; g |= g >> GShiftR; + uint32_t b = (c & BMask) << BShiftL; b |= b >> BShiftR; + uint32_t a = (c & AMask) << AShiftL; a |= a >> AShiftR; + pixelp[0] = (uint8_t)(b>>24); + pixelp[1] = (uint8_t)(g>>24); + pixelp[2] = (uint8_t)(r>>24); + pixelp[3] = (uint8_t)(a>>24); + pixelp+=4; + } + } + } + delete[] linebuff; +} + +//========================================================================== +// +// +// +//========================================================================== + +void FDDSTexture::DecompressDXT1 (FileReader &lump, uint8_t *buffer, int pixelmode) +{ + const long blocklinelen = ((Width + 3) >> 2) << 3; + uint8_t *blockbuff = new uint8_t[blocklinelen]; + uint8_t *block; + PalEntry color[4]; + uint8_t palcol[4] = { 0,0,0,0 }; // shut up compiler warnings. + int ox, oy, x, y, i; + + color[0].a = 255; + color[1].a = 255; + color[2].a = 255; + + for (oy = 0; oy < Height; oy += 4) + { + lump.Read (blockbuff, blocklinelen); + block = blockbuff; + for (ox = 0; ox < Width; ox += 4) + { + uint16_t color16[2] = { LittleShort(((uint16_t *)block)[0]), LittleShort(((uint16_t *)block)[1]) }; + + // Convert color from R5G6B5 to R8G8B8. + for (i = 1; i >= 0; --i) + { + color[i].r = ((color16[i] & 0xF800) >> 8) | (color16[i] >> 13); + color[i].g = ((color16[i] & 0x07E0) >> 3) | ((color16[i] & 0x0600) >> 9); + color[i].b = ((color16[i] & 0x001F) << 3) | ((color16[i] & 0x001C) >> 2); + } + if (color16[0] > color16[1]) + { // Four-color block: derive the other two colors. + color[2].r = (color[0].r + color[0].r + color[1].r + 1) / 3; + color[2].g = (color[0].g + color[0].g + color[1].g + 1) / 3; + color[2].b = (color[0].b + color[0].b + color[1].b + 1) / 3; + + color[3].r = (color[0].r + color[1].r + color[1].r + 1) / 3; + color[3].g = (color[0].g + color[1].g + color[1].g + 1) / 3; + color[3].b = (color[0].b + color[1].b + color[1].b + 1) / 3; + color[3].a = 255; + } + else + { // Three-color block: derive the other color. + color[2].r = (color[0].r + color[1].r) / 2; + color[2].g = (color[0].g + color[1].g) / 2; + color[2].b = (color[0].b + color[1].b) / 2; + + color[3].a = color[3].b = color[3].g = color[3].r = 0; + + // If you have a three-color block, presumably that transparent + // color is going to be used. + bMasked = true; + } + // Pick colors from the palette for each of the four colors. + if (pixelmode != PIX_ARGB) for (i = 3; i >= 0; --i) + { + assert(false); + } + // Now decode this 4x4 block to the pixel buffer. + for (y = 0; y < 4; ++y) + { + if (oy + y >= Height) + { + break; + } + uint8_t yslice = block[4 + y]; + for (x = 0; x < 4; ++x) + { + if (ox + x >= Width) + { + break; + } + int ci = (yslice >> (x + x)) & 3; + if (pixelmode != PIX_ARGB) + { + buffer[oy + y + (ox + x) * Height] = palcol[ci]; + } + else + { + uint8_t * tcp = &buffer[(ox + x)*4 + (oy + y) * Width*4]; + tcp[0] = color[ci].b; + tcp[1] = color[ci].g; + tcp[2] = color[ci].r; + tcp[3] = color[ci].a; + } + } + } + block += 8; + } + } + delete[] blockbuff; +} + +//========================================================================== +// +// DXT3: Decompression is identical to DXT1, except every 64-bit block is +// preceded by another 64-bit block with explicit alpha values. +// +//========================================================================== + +void FDDSTexture::DecompressDXT3 (FileReader &lump, bool premultiplied, uint8_t *buffer, int pixelmode) +{ + const long blocklinelen = ((Width + 3) >> 2) << 4; + uint8_t *blockbuff = new uint8_t[blocklinelen]; + uint8_t *block; + PalEntry color[4]; + uint8_t palcol[4] = { 0,0,0,0 }; + int ox, oy, x, y, i; + + for (oy = 0; oy < Height; oy += 4) + { + lump.Read (blockbuff, blocklinelen); + block = blockbuff; + for (ox = 0; ox < Width; ox += 4) + { + uint16_t color16[2] = { LittleShort(((uint16_t *)block)[4]), LittleShort(((uint16_t *)block)[5]) }; + + // Convert color from R5G6B5 to R8G8B8. + for (i = 1; i >= 0; --i) + { + color[i].r = ((color16[i] & 0xF800) >> 8) | (color16[i] >> 13); + color[i].g = ((color16[i] & 0x07E0) >> 3) | ((color16[i] & 0x0600) >> 9); + color[i].b = ((color16[i] & 0x001F) << 3) | ((color16[i] & 0x001C) >> 2); + } + // Derive the other two colors. + color[2].r = (color[0].r + color[0].r + color[1].r + 1) / 3; + color[2].g = (color[0].g + color[0].g + color[1].g + 1) / 3; + color[2].b = (color[0].b + color[0].b + color[1].b + 1) / 3; + + color[3].r = (color[0].r + color[1].r + color[1].r + 1) / 3; + color[3].g = (color[0].g + color[1].g + color[1].g + 1) / 3; + color[3].b = (color[0].b + color[1].b + color[1].b + 1) / 3; + + // Pick colors from the palette for each of the four colors. + if (pixelmode != PIX_ARGB) for (i = 3; i >= 0; --i) + { + assert(false); + } + + // Now decode this 4x4 block to the pixel buffer. + for (y = 0; y < 4; ++y) + { + if (oy + y >= Height) + { + break; + } + uint8_t yslice = block[12 + y]; + uint16_t yalphaslice = LittleShort(((uint16_t *)block)[y]); + for (x = 0; x < 4; ++x) + { + if (ox + x >= Width) + { + break; + } + if (pixelmode == PIX_Palette) + { + buffer[oy + y + (ox + x) * Height] = ((yalphaslice >> (x*4)) & 15) < 8 ? + (bMasked = true, 0) : palcol[(yslice >> (x + x)) & 3]; + } + else if (pixelmode == PIX_Alphatex) + { + int alphaval = ((yalphaslice >> (x * 4)) & 15); + int palval = palcol[(yslice >> (x + x)) & 3]; + buffer[oy + y + (ox + x) * Height] = palval * alphaval / 15; + } + else + { + uint8_t * tcp = &buffer[(ox + x)*4 + (oy + y) * Width*4]; + int c = (yslice >> (x + x)) & 3; + tcp[0] = color[c].b; + tcp[1] = color[c].g; + tcp[2] = color[c].r; + tcp[3] = ((yalphaslice >> (x * 4)) & 15) * 0x11; + } + } + } + block += 16; + } + } + delete[] blockbuff; +} + +//========================================================================== +// +// DXT5: Decompression is identical to DXT3, except every 64-bit alpha block +// contains interpolated alpha values, similar to the 64-bit color block. +// +//========================================================================== + +void FDDSTexture::DecompressDXT5 (FileReader &lump, bool premultiplied, uint8_t *buffer, int pixelmode) +{ + const long blocklinelen = ((Width + 3) >> 2) << 4; + uint8_t *blockbuff = new uint8_t[blocklinelen]; + uint8_t *block; + PalEntry color[4]; + uint8_t palcol[4] = { 0,0,0,0 }; + uint32_t yalphaslice = 0; + int ox, oy, x, y, i; + + for (oy = 0; oy < Height; oy += 4) + { + lump.Read (blockbuff, blocklinelen); + block = blockbuff; + for (ox = 0; ox < Width; ox += 4) + { + uint16_t color16[2] = { LittleShort(((uint16_t *)block)[4]), LittleShort(((uint16_t *)block)[5]) }; + uint8_t alpha[8]; + + // Calculate the eight alpha values. + alpha[0] = block[0]; + alpha[1] = block[1]; + + if (alpha[0] >= alpha[1]) + { // Eight-alpha block: derive the other six alphas. + for (i = 0; i < 6; ++i) + { + alpha[i + 2] = ((6 - i) * alpha[0] + (i + 1) * alpha[1] + 3) / 7; + } + } + else + { // Six-alpha block: derive the other four alphas. + for (i = 0; i < 4; ++i) + { + alpha[i + 2] = ((4 - i) * alpha[0] + (i + 1) * alpha[1] + 2) / 5; + } + alpha[6] = 0; + alpha[7] = 255; + } + + // Convert color from R5G6B5 to R8G8B8. + for (i = 1; i >= 0; --i) + { + color[i].r = ((color16[i] & 0xF800) >> 8) | (color16[i] >> 13); + color[i].g = ((color16[i] & 0x07E0) >> 3) | ((color16[i] & 0x0600) >> 9); + color[i].b = ((color16[i] & 0x001F) << 3) | ((color16[i] & 0x001C) >> 2); + } + // Derive the other two colors. + color[2].r = (color[0].r + color[0].r + color[1].r + 1) / 3; + color[2].g = (color[0].g + color[0].g + color[1].g + 1) / 3; + color[2].b = (color[0].b + color[0].b + color[1].b + 1) / 3; + + color[3].r = (color[0].r + color[1].r + color[1].r + 1) / 3; + color[3].g = (color[0].g + color[1].g + color[1].g + 1) / 3; + color[3].b = (color[0].b + color[1].b + color[1].b + 1) / 3; + + // Pick colors from the palette for each of the four colors. + if (pixelmode != PIX_ARGB) for (i = 3; i >= 0; --i) + { + assert(false); + } + // Now decode this 4x4 block to the pixel buffer. + for (y = 0; y < 4; ++y) + { + if (oy + y >= Height) + { + break; + } + // Alpha values are stored in 3 bytes for 2 rows + if ((y & 1) == 0) + { + yalphaslice = block[y*3] | (block[y*3+1] << 8) | (block[y*3+2] << 16); + } + else + { + yalphaslice >>= 12; + } + uint8_t yslice = block[12 + y]; + for (x = 0; x < 4; ++x) + { + if (ox + x >= Width) + { + break; + } + if (pixelmode == PIX_Palette) + { + buffer[oy + y + (ox + x) * Height] = alpha[((yalphaslice >> (x*3)) & 7)] < 128 ? + (bMasked = true, 0) : palcol[(yslice >> (x + x)) & 3]; + } + else if (pixelmode == PIX_Alphatex) + { + int alphaval = alpha[((yalphaslice >> (x * 3)) & 7)]; + int palval = palcol[(yslice >> (x + x)) & 3]; + buffer[oy + y + (ox + x) * Height] = palval * alphaval / 255; + } + else + { + uint8_t * tcp = &buffer[(ox + x)*4 + (oy + y) * Width*4]; + int c = (yslice >> (x + x)) & 3; + tcp[0] = color[c].b; + tcp[1] = color[c].g; + tcp[2] = color[c].r; + tcp[3] = alpha[((yalphaslice >> (x*3)) & 7)]; + } + } + } + block += 16; + } + } + delete[] blockbuff; +} + +//=========================================================================== +// +// FDDSTexture::CopyPixels +// +//=========================================================================== + +int FDDSTexture::CopyPixels(FBitmap *bmp, int conversion) +{ + auto lump = kopenFileReader(Name, 0); + if (!lump.isOpen()) return -1; // Just leave the texture blank. + + uint8_t *TexBuffer = bmp->GetPixels(); + + lump.Seek (sizeof(DDSURFACEDESC2) + 4, FileReader::SeekSet); + + if (Format >= 1 && Format <= 4) // RGB: Format is # of bytes per pixel + { + ReadRGB (lump, TexBuffer, PIX_ARGB); + } + else if (Format == ID_DXT1) + { + DecompressDXT1 (lump, TexBuffer, PIX_ARGB); + } + else if (Format == ID_DXT3 || Format == ID_DXT2) + { + DecompressDXT3 (lump, Format == ID_DXT2, TexBuffer, PIX_ARGB); + } + else if (Format == ID_DXT5 || Format == ID_DXT4) + { + DecompressDXT5 (lump, Format == ID_DXT4, TexBuffer, PIX_ARGB); + } + + return -1; +} diff --git a/source/common/textures/formats/jpegtexture.cpp b/source/common/textures/formats/jpegtexture.cpp new file mode 100644 index 000000000..7902c33e0 --- /dev/null +++ b/source/common/textures/formats/jpegtexture.cpp @@ -0,0 +1,339 @@ +/* +** jpegtexture.cpp +** Texture class for JPEG images +** +**--------------------------------------------------------------------------- +** Copyright 2006-2007 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +** +*/ + +#include + +#include "files.h" +#include "printf.h" +#include "bitmap.h" +#include "image.h" +#include "cache1d.h" + +extern "C" +{ +#include +} + + +struct FLumpSourceMgr : public jpeg_source_mgr +{ + FileReader *Lump; + JOCTET Buffer[4096]; + bool StartOfFile; + + FLumpSourceMgr (FileReader *lump, j_decompress_ptr cinfo); + static void InitSource (j_decompress_ptr cinfo); + static boolean FillInputBuffer (j_decompress_ptr cinfo); + static void SkipInputData (j_decompress_ptr cinfo, long num_bytes); + static void TermSource (j_decompress_ptr cinfo); +}; + + +//========================================================================== +// +// +// +//========================================================================== + +void FLumpSourceMgr::InitSource (j_decompress_ptr cinfo) +{ + ((FLumpSourceMgr *)(cinfo->src))->StartOfFile = true; +} + +//========================================================================== +// +// +// +//========================================================================== + +boolean FLumpSourceMgr::FillInputBuffer (j_decompress_ptr cinfo) +{ + FLumpSourceMgr *me = (FLumpSourceMgr *)(cinfo->src); + auto nbytes = me->Lump->Read (me->Buffer, sizeof(me->Buffer)); + + if (nbytes <= 0) + { + me->Buffer[0] = (JOCTET)0xFF; + me->Buffer[1] = (JOCTET)JPEG_EOI; + nbytes = 2; + } + me->next_input_byte = me->Buffer; + me->bytes_in_buffer = nbytes; + me->StartOfFile = false; + return TRUE; +} + +//========================================================================== +// +// +// +//========================================================================== + +void FLumpSourceMgr::SkipInputData (j_decompress_ptr cinfo, long num_bytes) +{ + FLumpSourceMgr *me = (FLumpSourceMgr *)(cinfo->src); + if (num_bytes <= (long)me->bytes_in_buffer) + { + me->bytes_in_buffer -= num_bytes; + me->next_input_byte += num_bytes; + } + else + { + num_bytes -= (long)me->bytes_in_buffer; + me->Lump->Seek (num_bytes, FileReader::SeekCur); + FillInputBuffer (cinfo); + } +} + +//========================================================================== +// +// +// +//========================================================================== + +void FLumpSourceMgr::TermSource (j_decompress_ptr cinfo) +{ +} + +//========================================================================== +// +// +// +//========================================================================== + +FLumpSourceMgr::FLumpSourceMgr (FileReader *lump, j_decompress_ptr cinfo) +: Lump (lump) +{ + cinfo->src = this; + init_source = InitSource; + fill_input_buffer = FillInputBuffer; + skip_input_data = SkipInputData; + resync_to_restart = jpeg_resync_to_restart; + term_source = TermSource; + bytes_in_buffer = 0; + next_input_byte = NULL; +} + +//========================================================================== +// +// +// +//========================================================================== + +void JPEG_ErrorExit (j_common_ptr cinfo) +{ + (*cinfo->err->output_message) (cinfo); + throw -1; +} + +//========================================================================== +// +// +// +//========================================================================== + +void JPEG_OutputMessage (j_common_ptr cinfo) +{ + char buffer[JMSG_LENGTH_MAX]; + + (*cinfo->err->format_message) (cinfo, buffer); + Printf (TEXTCOLOR_ORANGE "JPEG failure: %s\n", buffer); +} + +//========================================================================== +// +// A JPEG texture +// +//========================================================================== + +class FJPEGTexture : public FImageSource +{ +public: + FJPEGTexture (int width, int height); + + int CopyPixels(FBitmap *bmp, int conversion) override; +}; + +//========================================================================== +// +// +// +//========================================================================== + +FImageSource *JPEGImage_TryCreate(FileReader & data) +{ + union + { + uint32_t dw; + uint16_t w[2]; + uint8_t b[4]; + } first4bytes; + + data.Seek(0, FileReader::SeekSet); + if (data.Read(&first4bytes, 4) < 4) return NULL; + + if (first4bytes.b[0] != 0xFF || first4bytes.b[1] != 0xD8 || first4bytes.b[2] != 0xFF) + return NULL; + + // Find the SOFn marker to extract the image dimensions, + // where n is 0, 1, or 2 (other types are unsupported). + while ((unsigned)first4bytes.b[3] - 0xC0 >= 3) + { + if (data.Read (first4bytes.w, 2) != 2) + { + return NULL; + } + data.Seek (BigShort(first4bytes.w[0]) - 2, FileReader::SeekCur); + if (data.Read (first4bytes.b + 2, 2) != 2 || first4bytes.b[2] != 0xFF) + { + return NULL; + } + } + if (data.Read (first4bytes.b, 3) != 3) + { + return NULL; + } + if (BigShort (first4bytes.w[0]) < 5) + { + return NULL; + } + if (data.Read (first4bytes.b, 4) != 4) + { + return NULL; + } + return new FJPEGTexture (BigShort(first4bytes.w[1]), BigShort(first4bytes.w[0])); +} + +//========================================================================== +// +// +// +//========================================================================== + +FJPEGTexture::FJPEGTexture (int width, int height) +{ + bMasked = false; + + Width = width; + Height = height; +} + +//=========================================================================== +// +// FJPEGTexture::CopyPixels +// +// Preserves the full color information (unlike software mode) +// +//=========================================================================== + +int FJPEGTexture::CopyPixels(FBitmap *bmp, int conversion) +{ + PalEntry pe[256]; + + auto lump = kopenFileReader(Name, 0); + if (!lump.isOpen()) return -1; // Just leave the texture blank. + + jpeg_decompress_struct cinfo; + jpeg_error_mgr jerr; + + cinfo.err = jpeg_std_error(&jerr); + cinfo.err->output_message = JPEG_OutputMessage; + cinfo.err->error_exit = JPEG_ErrorExit; + jpeg_create_decompress(&cinfo); + + FLumpSourceMgr sourcemgr(&lump, &cinfo); + try + { + jpeg_read_header(&cinfo, TRUE); + + if (!((cinfo.out_color_space == JCS_RGB && cinfo.num_components == 3) || + (cinfo.out_color_space == JCS_CMYK && cinfo.num_components == 4) || + (cinfo.out_color_space == JCS_YCbCr && cinfo.num_components == 3) || + (cinfo.out_color_space == JCS_GRAYSCALE && cinfo.num_components == 1))) + { + Printf(TEXTCOLOR_ORANGE "Unsupported color format in %s\n", Name.GetChars()); + } + else + { + jpeg_start_decompress(&cinfo); + + int yc = 0; + TArray buff(cinfo.output_height * cinfo.output_width * cinfo.output_components, true); + + while (cinfo.output_scanline < cinfo.output_height) + { + uint8_t * ptr = buff.Data() + cinfo.output_width * cinfo.output_components * yc; + jpeg_read_scanlines(&cinfo, &ptr, 1); + yc++; + } + + switch (cinfo.out_color_space) + { + case JCS_RGB: + bmp->CopyPixelDataRGB(0, 0, buff.Data(), cinfo.output_width, cinfo.output_height, + 3, cinfo.output_width * cinfo.output_components, 0, CF_RGB); + break; + + case JCS_GRAYSCALE: + for (int i = 0; i < 256; i++) pe[i] = PalEntry(255, i, i, i); // default to a gray map + bmp->CopyPixelData(0, 0, buff.Data(), cinfo.output_width, cinfo.output_height, + 1, cinfo.output_width, 0, pe); + break; + + case JCS_CMYK: + bmp->CopyPixelDataRGB(0, 0, buff.Data(), cinfo.output_width, cinfo.output_height, + 4, cinfo.output_width * cinfo.output_components, 0, CF_CMYK); + break; + + case JCS_YCbCr: + bmp->CopyPixelDataRGB(0, 0, buff.Data(), cinfo.output_width, cinfo.output_height, + 4, cinfo.output_width * cinfo.output_components, 0, CF_YCbCr); + break; + + default: + assert(0); + break; + } + jpeg_finish_decompress(&cinfo); + } + } + catch (int) + { + Printf(TEXTCOLOR_ORANGE "JPEG error in %s\n", Name.GetChars()); + } + jpeg_destroy_decompress(&cinfo); + return 0; +} + diff --git a/source/common/textures/formats/pcxtexture.cpp b/source/common/textures/formats/pcxtexture.cpp new file mode 100644 index 000000000..9a15192e2 --- /dev/null +++ b/source/common/textures/formats/pcxtexture.cpp @@ -0,0 +1,412 @@ +/* +** pcxtexture.cpp +** Texture class for PCX images +** +**--------------------------------------------------------------------------- +** Copyright 2005 David HENRY +** Copyright 2006 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +** +*/ + +#include "basics.h" +#include "files.h" +#include "bitmap.h" +#include "image.h" +#include "cache1d.h" + +//========================================================================== +// +// PCX file header +// +//========================================================================== + +#pragma pack(1) + +struct PCXHeader +{ + uint8_t manufacturer; + uint8_t version; + uint8_t encoding; + uint8_t bitsPerPixel; + + uint16_t xmin, ymin; + uint16_t xmax, ymax; + uint16_t horzRes, vertRes; + + uint8_t palette[48]; + uint8_t reserved; + uint8_t numColorPlanes; + + uint16_t bytesPerScanLine; + uint16_t paletteType; + uint16_t horzSize, vertSize; + + uint8_t padding[54]; + +} FORCE_PACKED; +#pragma pack() + +//========================================================================== +// +// a PCX texture +// +//========================================================================== + +class FPCXTexture : public FImageSource +{ +public: + FPCXTexture (PCXHeader &); + + int CopyPixels(FBitmap *bmp, int conversion) override; + +protected: + void ReadPCX1bit (uint8_t *dst, FileReader & lump, PCXHeader *hdr); + void ReadPCX4bits (uint8_t *dst, FileReader & lump, PCXHeader *hdr); + void ReadPCX8bits (uint8_t *dst, FileReader & lump, PCXHeader *hdr); + void ReadPCX24bits (uint8_t *dst, FileReader & lump, PCXHeader *hdr, int planes); +}; + + +//========================================================================== +// +// +// +//========================================================================== + +FImageSource * PCXImage_TryCreate(FileReader & file) +{ + PCXHeader hdr; + + + file.Seek(0, FileReader::SeekSet); + if (file.Read(&hdr, sizeof(hdr)) != sizeof(hdr)) + { + return NULL; + } + + hdr.xmin = LittleShort(hdr.xmin); + hdr.xmax = LittleShort(hdr.xmax); + hdr.bytesPerScanLine = LittleShort(hdr.bytesPerScanLine); + + if (hdr.manufacturer != 10 || hdr.encoding != 1) return NULL; + if (hdr.version != 0 && hdr.version != 2 && hdr.version != 3 && hdr.version != 4 && hdr.version != 5) return NULL; + if (hdr.bitsPerPixel != 1 && hdr.bitsPerPixel != 8 && hdr.bitsPerPixel != 4) return NULL; + if (hdr.bitsPerPixel == 1 && hdr.numColorPlanes !=1 && hdr.numColorPlanes != 4) return NULL; + if (hdr.bitsPerPixel == 8 && hdr.bytesPerScanLine != ((hdr.xmax - hdr.xmin + 2)&~1)) return NULL; + + for (int i = 0; i < 54; i++) + { + if (hdr.padding[i] != 0) return NULL; + } + + file.Seek(0, FileReader::SeekSet); + file.Read(&hdr, sizeof(hdr)); + + return new FPCXTexture(hdr); +} + +//========================================================================== +// +// +// +//========================================================================== + +FPCXTexture::FPCXTexture(PCXHeader & hdr) +{ + bMasked = false; + Width = LittleShort(hdr.xmax) - LittleShort(hdr.xmin) + 1; + Height = LittleShort(hdr.ymax) - LittleShort(hdr.ymin) + 1; +} + +//========================================================================== +// +// +// +//========================================================================== + +void FPCXTexture::ReadPCX1bit (uint8_t *dst, FileReader & lump, PCXHeader *hdr) +{ + int y, i, bytes; + int rle_count = 0; + uint8_t rle_value = 0; + + TArray srcp = lump.Read(lump.GetLength() - sizeof(PCXHeader)); + uint8_t * src = srcp.Data(); + + for (y = 0; y < Height; ++y) + { + uint8_t * ptr = &dst[y * Width]; + + bytes = hdr->bytesPerScanLine; + + while (bytes--) + { + if (rle_count == 0) + { + if ( (rle_value = *src++) < 0xc0) + { + rle_count = 1; + } + else + { + rle_count = rle_value - 0xc0; + rle_value = *src++; + } + } + + rle_count--; + + for (i = 7; i >= 0; --i, ptr ++) + { + // This can overflow for the last byte if not checked. + if (ptr < dst+Width*Height) + *ptr = ((rle_value & (1 << i)) > 0); + } + } + } +} + +//========================================================================== +// +// +// +//========================================================================== + +void FPCXTexture::ReadPCX4bits (uint8_t *dst, FileReader & lump, PCXHeader *hdr) +{ + int rle_count = 0, rle_value = 0; + int x, y, c; + int bytes; + TArray line(hdr->bytesPerScanLine, true); + TArray colorIndex(Width, true); + + TArray srcp = lump.Read(lump.GetLength() - sizeof(PCXHeader)); + uint8_t * src = srcp.Data(); + + for (y = 0; y < Height; ++y) + { + uint8_t * ptr = &dst[y * Width]; + memset (ptr, 0, Width * sizeof (uint8_t)); + + for (c = 0; c < 4; ++c) + { + uint8_t * pLine = line.Data(); + + bytes = hdr->bytesPerScanLine; + + while (bytes--) + { + if (rle_count == 0) + { + if ( (rle_value = *src++) < 0xc0) + { + rle_count = 1; + } + else + { + rle_count = rle_value - 0xc0; + rle_value = *src++; + } + } + + rle_count--; + *(pLine++) = rle_value; + } + } + + /* compute line's color indexes */ + for (x = 0; x < Width; ++x) + { + if (line[x / 8] & (128 >> (x % 8))) + ptr[x] += (1 << c); + } + } +} + +//========================================================================== +// +// +// +//========================================================================== + +void FPCXTexture::ReadPCX8bits (uint8_t *dst, FileReader & lump, PCXHeader *hdr) +{ + int rle_count = 0, rle_value = 0; + int y, bytes; + + auto srcp = lump.Read(lump.GetLength() - sizeof(PCXHeader)); + uint8_t * src = srcp.Data(); + + for (y = 0; y < Height; ++y) + { + uint8_t * ptr = &dst[y * Width]; + + bytes = hdr->bytesPerScanLine; + while (bytes--) + { + if (rle_count == 0) + { + if( (rle_value = *src++) < 0xc0) + { + rle_count = 1; + } + else + { + rle_count = rle_value - 0xc0; + rle_value = *src++; + } + } + + rle_count--; + *ptr++ = rle_value; + } + } +} + +//========================================================================== +// +// +// +//========================================================================== + +void FPCXTexture::ReadPCX24bits (uint8_t *dst, FileReader & lump, PCXHeader *hdr, int planes) +{ + int rle_count = 0, rle_value = 0; + int y, c; + int bytes; + + auto srcp = lump.Read(lump.GetLength() - sizeof(PCXHeader)); + uint8_t * src = srcp.Data(); + + for (y = 0; y < Height; ++y) + { + /* for each color plane */ + for (c = 0; c < planes; ++c) + { + uint8_t * ptr = &dst[y * Width * planes]; + bytes = hdr->bytesPerScanLine; + + while (bytes--) + { + if (rle_count == 0) + { + if( (rle_value = *src++) < 0xc0) + { + rle_count = 1; + } + else + { + rle_count = rle_value - 0xc0; + rle_value = *src++; + } + } + + rle_count--; + ptr[c] = (uint8_t)rle_value; + ptr += planes; + } + } + } +} + +//=========================================================================== +// +// FPCXTexture::CopyPixels +// +// Preserves the full color information (unlike software mode) +// +//=========================================================================== + +int FPCXTexture::CopyPixels(FBitmap *bmp, int conversion) +{ + PalEntry pe[256]; + PCXHeader header; + int bitcount; + TArray Pixels; + + auto lump = kopenFileReader(Name, 0); + if (!lump.isOpen()) return -1; // Just leave the texture blank. + + lump.Read(&header, sizeof(header)); + + bitcount = header.bitsPerPixel * header.numColorPlanes; + + if (bitcount < 24) + { + Pixels.Resize(Width*Height); + if (bitcount < 8) + { + switch (bitcount) + { + default: + case 1: + pe[0] = PalEntry(255, 0, 0, 0); + pe[1] = PalEntry(255, 255, 255, 255); + ReadPCX1bit (Pixels.Data(), lump, &header); + break; + + case 4: + for (int i = 0; i<16; i++) + { + pe[i] = PalEntry(255, header.palette[i * 3], header.palette[i * 3 + 1], header.palette[i * 3 + 2]); + } + ReadPCX4bits (Pixels.Data(), lump, &header); + break; + } + } + else if (bitcount == 8) + { + lump.Seek(-769, FileReader::SeekEnd); + uint8_t c = lump.ReadUInt8(); + c=0x0c; // Apparently there's many non-compliant PCXs out there... + if (c !=0x0c) + { + for(int i=0;i<256;i++) pe[i]=PalEntry(255,i,i,i); // default to a gray map + } + else for(int i=0;i<256;i++) + { + uint8_t r = lump.ReadUInt8(); + uint8_t g = lump.ReadUInt8(); + uint8_t b = lump.ReadUInt8(); + pe[i] = PalEntry(255, r,g,b); + } + lump.Seek(sizeof(header), FileReader::SeekSet); + ReadPCX8bits (Pixels.Data(), lump, &header); + } + bmp->CopyPixelData(0, 0, Pixels.Data(), Width, Height, 1, Width, 0, pe); + } + else + { + Pixels.Resize(Width*Height*4); + ReadPCX24bits (Pixels.Data(), lump, &header, 3); + bmp->CopyPixelDataRGB(0, 0, Pixels.Data(), Width, Height, 3, Width*3, 0, CF_RGB); + } + return 0; +} + diff --git a/source/common/textures/formats/pngtexture.cpp b/source/common/textures/formats/pngtexture.cpp new file mode 100644 index 000000000..9b4cc127c --- /dev/null +++ b/source/common/textures/formats/pngtexture.cpp @@ -0,0 +1,364 @@ +/* +** pngtexture.cpp +** Texture class for PNG images +** +**--------------------------------------------------------------------------- +** Copyright 2004-2007 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +** +*/ + +#include "files.h" +#include "templates.h" +#include "m_png.h" +#include "bitmap.h" +#include "image.h" +#include "printf.h" +#include "cache1d.h" + +//========================================================================== +// +// A PNG texture +// +//========================================================================== + +class FPNGTexture : public FImageSource +{ +public: + FPNGTexture (FileReader &lump, int width, int height, uint8_t bitdepth, uint8_t colortype, uint8_t interlace); + + int CopyPixels(FBitmap *bmp, int conversion) override; + +protected: + uint8_t BitDepth; + uint8_t ColorType; + uint8_t Interlace; + bool HaveTrans; + uint16_t NonPaletteTrans[3]; + + int PaletteSize = 0; + uint32_t StartOfIDAT = 0; + uint32_t StartOfPalette = 0; +}; + + +//========================================================================== +// +// +// +//========================================================================== + +FImageSource *PNGImage_TryCreate(FileReader & data) +{ + union + { + uint32_t dw; + uint16_t w[2]; + uint8_t b[4]; + } first4bytes; + + + // This is most likely a PNG, but make sure. (Note that if the + // first 4 bytes match, but later bytes don't, we assume it's + // a corrupt PNG.) + + data.Seek(0, FileReader::SeekSet); + if (data.Read (first4bytes.b, 4) != 4) return NULL; + if (first4bytes.dw != MAKE_ID(137,'P','N','G')) return NULL; + if (data.Read (first4bytes.b, 4) != 4) return NULL; + if (first4bytes.dw != MAKE_ID(13,10,26,10)) return NULL; + if (data.Read (first4bytes.b, 4) != 4) return NULL; + if (first4bytes.dw != MAKE_ID(0,0,0,13)) return NULL; + if (data.Read (first4bytes.b, 4) != 4) return NULL; + if (first4bytes.dw != MAKE_ID('I','H','D','R')) return NULL; + + // The PNG looks valid so far. Check the IHDR to make sure it's a + // type of PNG we support. + int width = data.ReadInt32BE(); + int height = data.ReadInt32BE(); + uint8_t bitdepth = data.ReadUInt8(); + uint8_t colortype = data.ReadUInt8(); + uint8_t compression = data.ReadUInt8(); + uint8_t filter = data.ReadUInt8(); + uint8_t interlace = data.ReadUInt8(); + + if (compression != 0 || filter != 0 || interlace > 1) + { + return NULL; + } + if (!((1 << colortype) & 0x5D)) + { + return NULL; + } + if (!((1 << bitdepth) & 0x116)) + { + return NULL; + } + + // Just for completeness, make sure the PNG has something more than an IHDR. + data.Seek (4, FileReader::SeekSet); + data.Read (first4bytes.b, 4); + if (first4bytes.dw == 0) + { + data.Read (first4bytes.b, 4); + if (first4bytes.dw == MAKE_ID('I','E','N','D')) + { + return NULL; + } + } + + return new FPNGTexture (data, width, height, bitdepth, colortype, interlace); +} + +//========================================================================== +// +// +// +//========================================================================== + +FPNGTexture::FPNGTexture (FileReader &lump, int width, int height, + uint8_t depth, uint8_t colortype, uint8_t interlace) + : BitDepth(depth), ColorType(colortype), Interlace(interlace), HaveTrans(false) +{ + uint8_t trans[256]; + uint32_t len, id; + + bMasked = false; + + Width = width; + Height = height; + + memset(trans, 255, 256); + + // Parse pre-IDAT chunks. I skip the CRCs. Is that bad? + lump.Seek(33, FileReader::SeekSet); + + lump.Read(&len, 4); + lump.Read(&id, 4); + while (id != MAKE_ID('I','D','A','T') && id != MAKE_ID('I','E','N','D')) + { + len = BigLong((unsigned int)len); + switch (id) + { + default: + lump.Seek (len, FileReader::SeekCur); + break; + + case MAKE_ID('g','r','A','b'): + // This is like GRAB found in an ILBM, except coordinates use 4 bytes + { + uint32_t hotx, hoty; + int ihotx, ihoty; + + lump.Read(&hotx, 4); + lump.Read(&hoty, 4); + ihotx = BigLong((int)hotx); + ihoty = BigLong((int)hoty); + if (ihotx < -32768 || ihotx > 32767) + { + Printf ("X-Offset for PNG texture %s is bad: %d (0x%08x)\n", Name.GetChars(), ihotx, ihotx); + ihotx = 0; + } + if (ihoty < -32768 || ihoty > 32767) + { + Printf ("Y-Offset for PNG texture %s is bad: %d (0x%08x)\n", Name.GetChars(), ihoty, ihoty); + ihoty = 0; + } + LeftOffset = ihotx; + TopOffset = ihoty; + } + break; + + case MAKE_ID('P','L','T','E'): + PaletteSize = std::min (len / 3, 256); + StartOfPalette = (uint32_t)lump.Tell(); + break; + + case MAKE_ID('t','R','N','S'): + lump.Read (trans, len); + HaveTrans = true; + // Save for colortype 2 + NonPaletteTrans[0] = uint16_t(trans[0] * 256 + trans[1]); + NonPaletteTrans[1] = uint16_t(trans[2] * 256 + trans[3]); + NonPaletteTrans[2] = uint16_t(trans[4] * 256 + trans[5]); + break; + } + lump.Seek(4, FileReader::SeekCur); // Skip CRC + lump.Read(&len, 4); + id = MAKE_ID('I','E','N','D'); + lump.Read(&id, 4); + } + StartOfIDAT = (uint32_t)lump.Tell() - 8; + + switch (colortype) + { + case 4: // Grayscale + Alpha + bMasked = true; + break; + + case 0: // Grayscale + if (colortype == 0 && HaveTrans && NonPaletteTrans[0] < 256) + { + bMasked = true; + } + break; + + case 3: // Paletted + break; + + case 6: // RGB + Alpha + bMasked = true; + break; + + case 2: // RGB + bMasked = HaveTrans; + break; + } +} + +//=========================================================================== +// +// FPNGTexture::CopyPixels +// +//=========================================================================== + +int FPNGTexture::CopyPixels(FBitmap *bmp, int conversion) +{ + // Parse pre-IDAT chunks. I skip the CRCs. Is that bad? + PalEntry pe[256]; + uint32_t len, id; + static char bpp[] = {1, 0, 3, 1, 2, 0, 4}; + int pixwidth = Width * bpp[ColorType]; + int transpal = false; + + FileReader *lump; + FileReader lfr; + + lfr = kopenFileReader(Name, 0); + if (!lfr.isOpen()) return -1; // Just leave the texture blank. + + lump = 𝔩 + + lump->Seek(33, FileReader::SeekSet); + for(int i = 0; i < 256; i++) // default to a gray map + pe[i] = PalEntry(255,i,i,i); + + lump->Read(&len, 4); + lump->Read(&id, 4); + while (id != MAKE_ID('I','D','A','T') && id != MAKE_ID('I','E','N','D')) + { + len = BigLong((unsigned int)len); + switch (id) + { + default: + lump->Seek (len, FileReader::SeekCur); + break; + + case MAKE_ID('P','L','T','E'): + for(int i = 0; i < PaletteSize; i++) + { + pe[i].r = lump->ReadUInt8(); + pe[i].g = lump->ReadUInt8(); + pe[i].b = lump->ReadUInt8(); + } + break; + + case MAKE_ID('t','R','N','S'): + if (ColorType == 3) + { + for(uint32_t i = 0; i < len; i++) + { + pe[i].a = lump->ReadUInt8(); + if (pe[i].a != 0 && pe[i].a != 255) + transpal = true; + } + } + else + { + lump->Seek(len, FileReader::SeekCur); + } + break; + } + lump->Seek(4, FileReader::SeekCur); // Skip CRC + lump->Read(&len, 4); + id = MAKE_ID('I','E','N','D'); + lump->Read(&id, 4); + } + + if (ColorType == 0 && HaveTrans && NonPaletteTrans[0] < 256) + { + pe[NonPaletteTrans[0]].a = 0; + transpal = true; + } + + uint8_t * Pixels = new uint8_t[pixwidth * Height]; + + lump->Seek (StartOfIDAT, FileReader::SeekSet); + lump->Read(&len, 4); + lump->Read(&id, 4); + M_ReadIDAT (*lump, Pixels, Width, Height, pixwidth, BitDepth, ColorType, Interlace, BigLong((unsigned int)len)); + + switch (ColorType) + { + case 0: + case 3: + bmp->CopyPixelData(0, 0, Pixels, Width, Height, 1, Width, 0, pe); + break; + + case 2: + if (!HaveTrans) + { + bmp->CopyPixelDataRGB(0, 0, Pixels, Width, Height, 3, pixwidth, 0, CF_RGB); + } + else + { + bmp->CopyPixelDataRGB(0, 0, Pixels, Width, Height, 3, pixwidth, 0, CF_RGBT, + NonPaletteTrans[0], NonPaletteTrans[1], NonPaletteTrans[2]); + transpal = true; + } + break; + + case 4: + bmp->CopyPixelDataRGB(0, 0, Pixels, Width, Height, 2, pixwidth, 0, CF_IA); + transpal = -1; + break; + + case 6: + bmp->CopyPixelDataRGB(0, 0, Pixels, Width, Height, 4, pixwidth, 0, CF_RGBA); + transpal = -1; + break; + + default: + break; + + } + delete[] Pixels; + return transpal; +} + + diff --git a/source/common/textures/formats/tgatexture.cpp b/source/common/textures/formats/tgatexture.cpp new file mode 100644 index 000000000..b8df67dcb --- /dev/null +++ b/source/common/textures/formats/tgatexture.cpp @@ -0,0 +1,319 @@ +/* +** tgatexture.cpp +** Texture class for TGA images +** +**--------------------------------------------------------------------------- +** Copyright 2006 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +** +*/ + +#include "files.h" +#include "templates.h" +#include "bitmap.h" +#include "image.h" +#include "cache1d.h" + + +//========================================================================== +// +// TGA file header +// +//========================================================================== + +#pragma pack(1) + +struct TGAHeader +{ + uint8_t id_len; + uint8_t has_cm; + uint8_t img_type; + int16_t cm_first; + int16_t cm_length; + uint8_t cm_size; + + int16_t x_origin; + int16_t y_origin; + int16_t width; + int16_t height; + uint8_t bpp; + uint8_t img_desc; +}; + +#pragma pack() + +//========================================================================== +// +// a TGA texture +// +//========================================================================== + +class FTGATexture : public FImageSource +{ +public: + FTGATexture (TGAHeader *); + + int CopyPixels(FBitmap *bmp, int conversion) override; + +protected: + void ReadCompressed(FileReader &lump, uint8_t * buffer, int bytesperpixel); +}; + +//========================================================================== +// +// +// +//========================================================================== + +FImageSource *TGAImage_TryCreate(FileReader & file) +{ + TGAHeader hdr; + + if (file.GetLength() < (long)sizeof(hdr)) return NULL; + + file.Seek(0, FileReader::SeekSet); + file.Read(&hdr, sizeof(hdr)); + hdr.width = LittleShort(hdr.width); + hdr.height = LittleShort(hdr.height); + + // Not much that can be done here because TGA does not have a proper + // header to be identified with. + if (hdr.has_cm != 0 && hdr.has_cm != 1) return NULL; + if (hdr.width <=0 || hdr.height <=0 || hdr.width > 2048 || hdr.height > 2048) return NULL; + if (hdr.bpp != 8 && hdr.bpp != 15 && hdr.bpp != 16 && hdr.bpp !=24 && hdr.bpp !=32) return NULL; + if (hdr.img_type <= 0 || hdr.img_type > 11) return NULL; + if (hdr.img_type >=4 && hdr.img_type <= 8) return NULL; + if ((hdr.img_desc & 16) != 0) return NULL; + + file.Seek(0, FileReader::SeekSet); + file.Read(&hdr, sizeof(hdr)); + hdr.width = LittleShort(hdr.width); + hdr.height = LittleShort(hdr.height); + + return new FTGATexture(&hdr); +} + +//========================================================================== +// +// +// +//========================================================================== + +FTGATexture::FTGATexture(TGAHeader * hdr) +{ + Width = hdr->width; + Height = hdr->height; + // Alpha channel is used only for 32 bit RGBA and paletted images with RGBA palettes. + bMasked = (hdr->img_desc&15)==8 && (hdr->bpp==32 || (hdr->img_type==1 && hdr->cm_size==32)); +} + +//========================================================================== +// +// +// +//========================================================================== + +void FTGATexture::ReadCompressed(FileReader &lump, uint8_t * buffer, int bytesperpixel) +{ + uint8_t data[4]; + int Size = Width * Height; + + while (Size > 0) + { + uint8_t b = lump.ReadUInt8(); + if (b & 128) + { + b&=~128; + lump.Read(data, bytesperpixel); + for (int i=std::min(Size, (b+1)); i>0; i--) + { + buffer[0] = data[0]; + if (bytesperpixel>=2) buffer[1] = data[1]; + if (bytesperpixel>=3) buffer[2] = data[2]; + if (bytesperpixel==4) buffer[3] = data[3]; + buffer+=bytesperpixel; + } + } + else + { + lump.Read(buffer, std::min(Size, (b+1))*bytesperpixel); + buffer += (b+1)*bytesperpixel; + } + Size -= b+1; + } +} + +//=========================================================================== +// +// FTGATexture::CopyPixels +// +//=========================================================================== + +int FTGATexture::CopyPixels(FBitmap *bmp, int conversion) +{ + PalEntry pe[256]; + auto lump = kopenFileReader(Name, 0); + TGAHeader hdr; + uint16_t w; + uint8_t r,g,b,a; + int transval = 0; + + lump.Read(&hdr, sizeof(hdr)); + lump.Seek(hdr.id_len, FileReader::SeekCur); + + hdr.width = LittleShort(hdr.width); + hdr.height = LittleShort(hdr.height); + hdr.cm_first = LittleShort(hdr.cm_first); + hdr.cm_length = LittleShort(hdr.cm_length); + + if (hdr.has_cm) + { + memset(pe, 0, 256*sizeof(PalEntry)); + for (int i = hdr.cm_first; i < hdr.cm_first + hdr.cm_length && i < 256; i++) + { + switch (hdr.cm_size) + { + case 15: + case 16: + w = lump.ReadUInt16(); + r = (w & 0x001F) << 3; + g = (w & 0x03E0) >> 2; + b = (w & 0x7C00) >> 7; + a = 255; + break; + + case 24: + b = lump.ReadUInt8(); + g = lump.ReadUInt8(); + r = lump.ReadUInt8(); + a = 255; + break; + + case 32: + b = lump.ReadUInt8(); + g = lump.ReadUInt8(); + r = lump.ReadUInt8(); + a = lump.ReadUInt8(); + if ((hdr.img_desc & 15) != 8) a = 255; + else if (a != 0 && a != 255) transval = true; + break; + + default: // should never happen + r=g=b=a=0; + break; + } + pe[i] = PalEntry(a, r, g, b); + } + } + + int Size = Width * Height * (hdr.bpp>>3); + TArray sbuffer(Size); + + if (hdr.img_type < 4) // uncompressed + { + lump.Read(sbuffer.Data(), Size); + } + else // compressed + { + ReadCompressed(lump, sbuffer.Data(), hdr.bpp>>3); + } + + uint8_t * ptr = sbuffer.Data(); + int step_x = (hdr.bpp>>3); + int Pitch = Width * step_x; + + /* + if (hdr.img_desc&32) + { + ptr += (Width-1) * step_x; + step_x =- step_x; + } + */ + if (!(hdr.img_desc&32)) + { + ptr += (Height-1) * Pitch; + Pitch = -Pitch; + } + + switch (hdr.img_type & 7) + { + case 1: // paletted + bmp->CopyPixelData(0, 0, ptr, Width, Height, step_x, Pitch, 0, pe); + break; + + case 2: // RGB + switch (hdr.bpp) + { + case 15: + case 16: + bmp->CopyPixelDataRGB(0, 0, ptr, Width, Height, step_x, Pitch, 0, CF_RGB555); + break; + + case 24: + bmp->CopyPixelDataRGB(0, 0, ptr, Width, Height, step_x, Pitch, 0, CF_BGR); + break; + + case 32: + if ((hdr.img_desc&15)!=8) // 32 bits without a valid alpha channel + { + bmp->CopyPixelDataRGB(0, 0, ptr, Width, Height, step_x, Pitch, 0, CF_BGR); + } + else + { + bmp->CopyPixelDataRGB(0, 0, ptr, Width, Height, step_x, Pitch, 0, CF_BGRA); + transval = -1; + } + break; + + default: + break; + } + break; + + case 3: // Grayscale + switch (hdr.bpp) + { + case 8: + for(int i=0;i<256;i++) pe[i]=PalEntry(255,i,i,i); // gray map + bmp->CopyPixelData(0, 0, ptr, Width, Height, step_x, Pitch, 0, pe); + break; + + case 16: + bmp->CopyPixelDataRGB(0, 0, ptr, Width, Height, step_x, Pitch, 0, CF_I16); + break; + + default: + break; + } + break; + + default: + break; + } + return transval; +} diff --git a/source/common/textures/image.cpp b/source/common/textures/image.cpp new file mode 100644 index 000000000..6ea28820c --- /dev/null +++ b/source/common/textures/image.cpp @@ -0,0 +1,130 @@ +/* +** texture.cpp +** The base texture class +** +**--------------------------------------------------------------------------- +** Copyright 2004-2007 Randy Heit +** Copyright 2006-2018 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +** +*/ + +#include "memarena.h" +#include "bitmap.h" +#include "image.h" +#include "files.h" +#include "cache1d.h" + +int FImageSource::NextID; + +//=========================================================================== +// +// the default just returns an empty texture. +// +//=========================================================================== + +TArray FImageSource::CreatePalettedPixels(int conversion) +{ + TArray Pixels(Width * Height, true); + memset(Pixels.Data(), 0, Width * Height); + return Pixels; +} + + +//=========================================================================== +// +// FImageSource::CopyPixels +// +// this is the generic case that can handle +// any properly implemented texture for software rendering. +// Its drawback is that it is limited to the base palette which is +// why all classes that handle different palettes should subclass this +// method +// +//=========================================================================== + +int FImageSource::CopyTranslatedPixels(FBitmap *bmp, PalEntry *remap) +{ + auto ppix = CreatePalettedPixels(false); + bmp->CopyPixelData(0, 0, ppix.Data(), Width, Height, Height, 1, 0, remap); + return 0; +} + +int FImageSource::CopyPixels(FBitmap* bmp, int conversion) +{ + return CopyTranslatedPixels(bmp, nullptr); // This should never get called for ART tiles. +} + + +//========================================================================== +// +// +// +//========================================================================== + +typedef FImageSource * (*CreateFunc)(FileReader & file); + +struct TexCreateInfo +{ + CreateFunc TryCreate; +}; + +FImageSource *PNGImage_TryCreate(FileReader &); +FImageSource *JPEGImage_TryCreate(FileReader &); +FImageSource *DDSImage_TryCreate(FileReader &); +FImageSource *PCXImage_TryCreate(FileReader &); +FImageSource *TGAImage_TryCreate(FileReader &); + + +// Examines the lump contents to decide what type of texture to create, +// and creates the texture. +FImageSource * FImageSource::GetImage(const char *name) +{ + static TexCreateInfo CreateInfo[] = { + { PNGImage_TryCreate }, + { JPEGImage_TryCreate }, + { DDSImage_TryCreate }, + { PCXImage_TryCreate }, + { TGAImage_TryCreate }, + { nullptr } + }; + + auto data = kopenFileReader(name, 0); + if (!data.isOpen()) return nullptr; + + for (size_t i = 0; CreateInfo[i].TryCreate; i++) + { + auto image = CreateInfo[i].TryCreate(data); + if (image != nullptr) + { + image->Name = name; + return image; + } + } + return nullptr; +} diff --git a/source/common/textures/image.h b/source/common/textures/image.h new file mode 100644 index 000000000..3aa27bb99 --- /dev/null +++ b/source/common/textures/image.h @@ -0,0 +1,138 @@ +#pragma once + +#include +#include "zstring.h" +#include "tarray.h" +#include "textures.h" +#include "bitmap.h" +#include "memarena.h" + +class FImageSource; +using PrecacheInfo = TMap>; + +struct PalettedPixels +{ + friend class FImageSource; + TArrayView Pixels; +private: + TArray PixelStore; + + bool ownsPixels() const + { + return Pixels.Data() == PixelStore.Data(); + } +}; + +// This represents a naked image. It has no high level logic attached to it. +// All it can do is provide raw image data to its users. +class FImageSource +{ +protected: + + static int NextID; + + int SourceLump; + int Width = 0, Height = 0; + int LeftOffset = 0, TopOffset = 0; // Offsets stored in the image. + bool bUseGamePalette = false; // true if this is an image without its own color set. + int ImageID = -1; + FString Name; + + // Internal image creation functions. All external access should go through the cache interface, + // so that all code can benefit from future improvements to that. + +public: + + virtual ~FImageSource() = default; + void CopySize(FImageSource &other) + { + Width = other.Width; + Height = other.Height; + LeftOffset = other.LeftOffset; + TopOffset = other.TopOffset; + SourceLump = other.SourceLump; + } + + bool bMasked = true; // Image (might) have holes (Assume true unless proven otherwise!) + int8_t bTranslucent = -1; // Image has pixels with a non-0/1 value. (-1 means the user needs to do a real check) + + int GetId() const { return ImageID; } + + // 'noremap0' will only be looked at by FPatchTexture and forwarded by FMultipatchTexture. + static FImageSource * GetImage(const char *name); + + virtual TArray CreatePalettedPixels(int conversion); + virtual int CopyPixels(FBitmap* bmp, int conversion); // This will always ignore 'luminance'. + int CopyTranslatedPixels(FBitmap* bmp, PalEntry* remap); + + + // Conversion option + enum EType + { + normal = 0, + luminance = 1, + noremap0 = 2 + }; + + FImageSource(int sourcelump = -1) : SourceLump(sourcelump) { ImageID = ++NextID; } + + int GetWidth() const + { + return Width; + } + + int GetHeight() const + { + return Height; + } + + std::pair GetSize() const + { + return std::make_pair(Width, Height); + } + + std::pair GetOffsets() const + { + return std::make_pair(LeftOffset, TopOffset); + } + + void SetOffsets(int x, int y) + { + LeftOffset = x; + TopOffset = y; + } + + int LumpNum() const + { + return SourceLump; + } + + bool UseGamePalette() const + { + return bUseGamePalette; + } +}; + +//========================================================================== +// +// a TGA texture +// +//========================================================================== + +class FImageTexture : public FTexture +{ + FImageSource *mImage; +public: + FImageTexture (FImageSource *image, const char *name = nullptr); + virtual TArray Get8BitPixels(bool alphatex); + + void SetImage(FImageSource *img) // This is only for the multipatch texture builder! + { + mImage = img; + } + + FImageSource *GetImage() const override { return mImage; } + FBitmap GetBgraBitmap(PalEntry *p, int *trans) override; + +}; + diff --git a/source/common/textures/imagetexture.cpp b/source/common/textures/imagetexture.cpp new file mode 100644 index 000000000..6ee8596ad --- /dev/null +++ b/source/common/textures/imagetexture.cpp @@ -0,0 +1,89 @@ +/* +** imagetexture.cpp +** Texture class based on FImageSource +** +**--------------------------------------------------------------------------- +** Copyright 2018 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +** +*/ + +#include "files.h" +#include "templates.h" +#include "bitmap.h" +#include "image.h" + + +//========================================================================== +// +// +// +//========================================================================== + +FImageTexture::FImageTexture(FImageSource *img, const char *name) +: FTexture(name) +{ + mImage = img; + if (img != nullptr) + { + Width = img->GetWidth(); + Height = img->GetHeight(); + + auto offsets = img->GetOffsets(); + LeftOffset = offsets.first; + TopOffset = offsets.second; + + bMasked = img->bMasked; + bTranslucent = img->bTranslucent; + } +} + +//=========================================================================== +// +// +// +//=========================================================================== + +FBitmap FImageTexture::GetBgraBitmap(PalEntry *p, int *trans) +{ + FBitmap bmp; + mImage->CopyPixels(&bmp, 0); // Todo: Handle translations. + return bmp; +} + +//=========================================================================== +// +// +// +//=========================================================================== + +TArray FImageTexture::Get8BitPixels(bool alpha) +{ + return mImage->CreatePalettedPixels(0); +} + diff --git a/source/common/textures/skyboxtexture.cpp b/source/common/textures/skyboxtexture.cpp new file mode 100644 index 000000000..5fe1f7707 --- /dev/null +++ b/source/common/textures/skyboxtexture.cpp @@ -0,0 +1,63 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2004-2016 Christoph Oelckers +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// + +#include "filesystem.h" +#include "textures.h" +#include "skyboxtexture.h" +#include "bitmap.h" + + + +//----------------------------------------------------------------------------- +// +// +// +//----------------------------------------------------------------------------- + +FSkyBox::FSkyBox(const char *name) +: FTexture(name) +{ + bSkybox = true; + fliptop = false; +} + +//----------------------------------------------------------------------------- +// +// +// +//----------------------------------------------------------------------------- + +FBitmap FSkyBox::GetBgraBitmap(PalEntry *p, int *trans) +{ + return faces[0]->GetBgraBitmap(p, trans); +} + +//----------------------------------------------------------------------------- +// +// +// +//----------------------------------------------------------------------------- + +FImageSource *FSkyBox::GetImage() const +{ + return faces[0]->GetImage(); +} diff --git a/source/common/textures/skyboxtexture.h b/source/common/textures/skyboxtexture.h new file mode 100644 index 000000000..51350a067 --- /dev/null +++ b/source/common/textures/skyboxtexture.h @@ -0,0 +1,37 @@ +#pragma once + +#include "textures.h" +//----------------------------------------------------------------------------- +// +// This is not a real texture but will be added to the texture manager +// so that it can be handled like any other sky. +// +//----------------------------------------------------------------------------- + +class FSkyBox : public FTexture +{ +public: + + FTexture* faces[6] = {}; + bool fliptop; + + FSkyBox(const char *name); + FBitmap GetBgraBitmap(PalEntry *, int *trans) override; + FImageSource *GetImage() const override; + + + void SetSize() + { + CopySize(faces[0]); + } + + bool Is3Face() const + { + return faces[5] == nullptr; + } + + bool IsFlipped() const + { + return fliptop; + } +}; diff --git a/source/common/textures/texture.cpp b/source/common/textures/texture.cpp new file mode 100644 index 000000000..36aaddd58 --- /dev/null +++ b/source/common/textures/texture.cpp @@ -0,0 +1,419 @@ +/* +** texture.cpp +** The base texture class +** +**--------------------------------------------------------------------------- +** Copyright 2004-2007 Randy Heit +** Copyright 2006-2018 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +** +*/ + +#include "files.h" +#include "templates.h" + +#include "bitmap.h" +#include "image.h" + +FTexture *CreateBrightmapTexture(FImageSource*); + +//========================================================================== +// +// +// +//========================================================================== + + +// Examines the lump contents to decide what type of texture to create, +// and creates the texture. +FTexture * FTexture::CreateTexture(const char *name) +{ + auto image = FImageSource::GetImage(name); + if (image != nullptr) + { + FTexture *tex = new FImageTexture(image); + if (tex != nullptr) + { + tex->Name = name; + return tex; + } + } + return nullptr; +} + +//========================================================================== +// +// +// +//========================================================================== + +FTexture::FTexture (const char *name) +{ + Name = name; +} + +FTexture::~FTexture () +{ +} + +//=========================================================================== +// +// FTexture::GetBgraBitmap +// +// Default returns just an empty bitmap. This needs to be overridden by +// any subclass that actually does return a software pixel buffer. +// +//=========================================================================== + +FBitmap FTexture::GetBgraBitmap(PalEntry *remap, int *ptrans) +{ + FBitmap bmp; + bmp.Create(Width, Height); + return bmp; +} + +//=========================================================================== +// +// Gets the average color of a texture for use as a sky cap color +// +//=========================================================================== + +#define APART(c) (((c)>>24)&0xff) +#define RPART(c) (((c)>>16)&0xff) +#define GPART(c) (((c)>>8)&0xff) +#define BPART(c) ((c)&0xff) + +PalEntry FTexture::averageColor(const uint32_t *data, int size, int maxout) +{ + int i; + unsigned int r, g, b; + + // First clear them. + r = g = b = 0; + if (size == 0) + { + return PalEntry(255, 255, 255); + } + for (i = 0; i < size; i++) + { + b += BPART(data[i]); + g += GPART(data[i]); + r += RPART(data[i]); + } + + r = r / size; + g = g / size; + b = b / size; + + int maxv = std::max(std::max(r, g), b); + + if (maxv && maxout) + { + r = uint64_t(r) * maxout / maxv; + g = uint64_t(g) * maxout / maxv; + b = uint64_t(b) * maxout / maxv; + } + return PalEntry(255, r, g, b); +} + +PalEntry FTexture::GetSkyCapColor(bool bottom) +{ + if (!skyColorDone) + { + skyColorDone = true; + + FBitmap bitmap = GetBgraBitmap(nullptr); + int w = bitmap.GetWidth(); + int h = bitmap.GetHeight(); + + const uint32_t *buffer = (const uint32_t *)bitmap.GetPixels(); + if (buffer) + { + CeilingSkyColor = averageColor((uint32_t *)buffer, w * std::min(30, h), 0); + if (h>30) + { + FloorSkyColor = averageColor(((uint32_t *)buffer) + (h - 30)*w, w * 30, 0); + } + else FloorSkyColor = CeilingSkyColor; + } + } + return bottom ? FloorSkyColor : CeilingSkyColor; +} + + +//---------------------------------------------------------------------------- +// +// +// +//---------------------------------------------------------------------------- + +void FTexture::CheckTrans(unsigned char * buffer, int size, int trans) +{ + if (bTranslucent == -1) + { + bTranslucent = trans; + if (trans == -1) + { + uint32_t * dwbuf = (uint32_t*)buffer; + for (int i = 0; i> 24; + + if (alpha != 0xff && alpha != 0) + { + bTranslucent = 1; + return; + } + } + bTranslucent = 0; + } + } +} + + +//=========================================================================== +// +// smooth the edges of transparent fields in the texture +// +//=========================================================================== + +#ifdef WORDS_BIGENDIAN +#define MSB 0 +#define SOME_MASK 0xffffff00 +#else +#define MSB 3 +#define SOME_MASK 0x00ffffff +#endif + +#define CHKPIX(ofs) (l1[(ofs)*4+MSB]==255 ? (( ((uint32_t*)l1)[0] = ((uint32_t*)l1)[ofs]&SOME_MASK), trans=true ) : false) + +int FTexture::SmoothEdges(unsigned char * buffer, int w, int h) +{ + int x, y; + int trans = buffer[MSB] == 0; // If I set this to false here the code won't detect textures + // that only contain transparent pixels. + int semitrans = false; + unsigned char * l1; + + if (h <= 1 || w <= 1) return false; // makes (a) no sense and (b) doesn't work with this code! + + l1 = buffer; + + + if (l1[MSB] == 0 && !CHKPIX(1)) CHKPIX(w); + else if (l1[MSB]<255) semitrans = true; + l1 += 4; + for (x = 1; xGetId(); + builder.translation = std::max(0, translation); + builder.expand = exx; + result.mContentId = builder.id; + } + else result.mContentId = 0; // for non-image backed textures this has no meaning so leave it at 0. + + result.mBuffer = buffer; + result.mWidth = W; + result.mHeight = H; + + // Only do postprocessing for image-backed textures. (i.e. not for the burn texture which can also pass through here.) + if (GetImage() && flags & CTF_ProcessData) + { +#if 0 + CreateUpsampledTextureBuffer(result, !!isTransparent, checkonly); +#endif + if (!checkonly) ProcessData(result.mBuffer, result.mWidth, result.mHeight, false); + } + + return result; +} + +//=========================================================================== +// +// Dummy texture for the 0-entry. +// +//=========================================================================== + +bool FTexture::GetTranslucency() +{ + if (bTranslucent == -1) + { + if (true)//!bHasCanvas) + { + // This will calculate all we need, so just discard the result. + CreateTexBuffer(0); + } + /* + else + { + bTranslucent = 0; + }*/ + } + return !!bTranslucent; +} + +//=========================================================================== +// +// the default just returns an empty texture. +// +//=========================================================================== + +TArray FTexture::Get8BitPixels(bool alphatex) +{ + TArray Pixels(Width * Height, true); + memset(Pixels.Data(), 0, Width * Height); + return Pixels; +} + +#if 0 +//========================================================================== +// +// +// +//========================================================================== + +FWrapperTexture::FWrapperTexture(int w, int h, int bits) +{ + Width = w; + Height = h; + Format = bits; + UseType = ETextureType::SWCanvas; + bNoCompress = true; + auto hwtex = screen->CreateHardwareTexture(); + // todo: Initialize here. + SystemTextures.AddHardwareTexture(0, false, hwtex); +} + +#endif \ No newline at end of file diff --git a/source/common/textures/textureid.h b/source/common/textures/textureid.h new file mode 100644 index 000000000..769099ff1 --- /dev/null +++ b/source/common/textures/textureid.h @@ -0,0 +1,36 @@ +#pragma once + +class FTextureID +{ + friend class FTextureManager; + +public: + FTextureID() = default; + bool isNull() const { return texnum == 0; } + bool isValid() const { return texnum > 0; } + bool Exists() const { return texnum >= 0; } + void SetInvalid() { texnum = -1; } + void SetNull() { texnum = 0; } + bool operator ==(const FTextureID &other) const { return texnum == other.texnum; } + bool operator !=(const FTextureID &other) const { return texnum != other.texnum; } + FTextureID operator +(int offset) throw(); + int GetIndex() const { return texnum; } // Use this only if you absolutely need the index! + + // The switch list needs these to sort the switches by texture index + int operator -(FTextureID other) const { return texnum - other.texnum; } + bool operator < (FTextureID other) const { return texnum < other.texnum; } + bool operator > (FTextureID other) const { return texnum > other.texnum; } + +protected: + FTextureID(int num) { texnum = num; } +private: + int texnum; +}; + +// This is for the script interface which needs to do casts from int to texture. +class FSetTextureID : public FTextureID +{ +public: + FSetTextureID(int v) : FTextureID(v) {} +}; + \ No newline at end of file diff --git a/source/common/textures/textures.h b/source/common/textures/textures.h new file mode 100644 index 000000000..40d89d02d --- /dev/null +++ b/source/common/textures/textures.h @@ -0,0 +1,198 @@ +/* +** textures.h +** +**--------------------------------------------------------------------------- +** Copyright 2005-2016 Randy Heit +** Copyright 2005-2016 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#ifndef __TEXTURES_H +#define __TEXTURES_H + +#include "textureid.h" +#include "palentry.h" +/* +#include "v_palette.h" +#include "r_data/v_colortables.h" +#include "colormatcher.h" +#include "r_data/renderstyle.h" +#include "r_data/r_translate.h" +#include "hwrenderer/textures/hw_texcontainer.h" +*/ +#include + +class FImageSource; + +enum ECreateTexBufferFlags +{ + CTF_CheckHires = 1, // use external hires replacement if found + CTF_Expand = 2, // create buffer with a one-pixel wide border + CTF_ProcessData = 4, // run postprocessing on the generated buffer. This is only needed when using the data for a hardware texture. + CTF_CheckOnly = 8, // Only runs the code to get a content ID but does not create a texture. Can be used to access a caching system for the hardware textures. +}; + + + +class FBitmap; +struct FRemapTable; +struct FCopyInfo; +class FScanner; + +// Texture IDs +class FTextureManager; +class FTerrainTypeArray; +class IHardwareTexture; +class FMaterial; +class FMultipatchTextureBuilder; + +extern int r_spriteadjustSW, r_spriteadjustHW; + +class FNullTextureID : public FTextureID +{ +public: + FNullTextureID() : FTextureID(0) {} +}; + + + +class FGLRenderState; + +class FSerializer; +namespace OpenGLRenderer +{ + class FGLRenderState; + class FHardwareTexture; +} + +union FContentIdBuilder +{ + uint64_t id; + struct + { + unsigned imageID : 24; + unsigned translation : 16; + unsigned expand : 1; + unsigned scaler : 4; + unsigned scalefactor : 4; + }; +}; + +struct FTextureBuffer +{ + uint8_t *mBuffer = nullptr; + int mWidth = 0; + int mHeight = 0; + uint64_t mContentId = 0; // unique content identifier. (Two images created from the same image source with the same settings will return the same value.) + + FTextureBuffer() = default; + + ~FTextureBuffer() + { + if (mBuffer) delete[] mBuffer; + } + + FTextureBuffer(const FTextureBuffer &other) = delete; + FTextureBuffer(FTextureBuffer &&other) + { + mBuffer = other.mBuffer; + mWidth = other.mWidth; + mHeight = other.mHeight; + mContentId = other.mContentId; + other.mBuffer = nullptr; + } + + FTextureBuffer& operator=(FTextureBuffer &&other) + { + mBuffer = other.mBuffer; + mWidth = other.mWidth; + mHeight = other.mHeight; + mContentId = other.mContentId; + other.mBuffer = nullptr; + return *this; + } + +}; + +// Base texture class +class FTexture +{ + +public: + static FTexture *CreateTexture(const char *name); + virtual ~FTexture (); + virtual FImageSource *GetImage() const { return nullptr; } + + const FString &GetName() const { return Name; } + bool isMasked() const { return bMasked; } + bool isTranslucent() const { return bMasked; } + PalEntry GetSkyCapColor(bool bottom); + + // Returns the whole texture, stored in column-major order + virtual TArray Get8BitPixels(bool alphatex); + virtual FBitmap GetBgraBitmap(PalEntry *remap, int *trans = nullptr); + + static int SmoothEdges(unsigned char * buffer,int w, int h); + static PalEntry averageColor(const uint32_t *data, int size, int maxout); + + int GetWidth() { return Width; } + int GetHeight() { return Height; } + int GetLeftOffset() { return LeftOffset; } + int GetTopOffset() { return TopOffset; } + FTextureBuffer CreateTexBuffer(int translation, int flags = 0); + bool GetTranslucency(); + void CheckTrans(unsigned char * buffer, int size, int trans); + bool ProcessData(unsigned char * buffer, int w, int h, bool ispatch); + +protected: + + void CopySize(FTexture *BaseTexture) + { + Width = BaseTexture->GetWidth(); + Height = BaseTexture->GetHeight(); + TopOffset = BaseTexture->TopOffset; + TopOffset = BaseTexture->TopOffset; + } + + int Width = 0, Height = 0; + int LeftOffset = 0, TopOffset = 0; + FString Name; + uint8_t bMasked = true; // Texture (might) have holes + int8_t bTranslucent = -1; // Does this texture have an active alpha channel? + bool skyColorDone = false; + PalEntry FloorSkyColor; + PalEntry CeilingSkyColor; + + FTexture (const char *name = NULL); +}; + +extern TMap textures; + +#endif + + diff --git a/source/common/utility/m_crc32.h b/source/common/utility/m_crc32.h new file mode 100644 index 000000000..b727d7a6b --- /dev/null +++ b/source/common/utility/m_crc32.h @@ -0,0 +1,52 @@ +/* +** m_crc32.h +** Simple interface to zlib's CRC table +** +**--------------------------------------------------------------------------- +** Copyright 1998-2006 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include +#include + +// zlib includes some CRC32 stuff, so just use that + +inline const uint32_t *GetCRCTable () { return (const uint32_t *)get_crc_table(); } +inline uint32_t CalcCRC32 (const uint8_t *buf, unsigned int len) +{ + return crc32 (0, buf, len); +} +inline uint32_t AddCRC32 (uint32_t crc, const uint8_t *buf, unsigned int len) +{ + return crc32 (crc, buf, len); +} +inline uint32_t CRC1 (uint32_t crc, const uint8_t c, const uint32_t *crcTable) +{ + return crcTable[(crc & 0xff) ^ c] ^ (crc >> 8); +} diff --git a/source/common/utility/m_png.cpp b/source/common/utility/m_png.cpp new file mode 100644 index 000000000..245546d65 --- /dev/null +++ b/source/common/utility/m_png.cpp @@ -0,0 +1,1282 @@ +/* +** m_png.cpp +** Routines for manipulating PNG files. +** +**--------------------------------------------------------------------------- +** Copyright 2002-2006 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +// HEADER FILES ------------------------------------------------------------ + +#include +#include +#include +#include +#ifdef _MSC_VER +#include // for alloca() +#endif + +#include "basics.h" +#include "m_crc32.h" +#include "m_swap.h" +#include "m_png.h" + + +// MACROS ------------------------------------------------------------------ + +// The maximum size of an IDAT chunk ZDoom will write. This is also the +// size of the compression buffer it allocates on the stack. +#define PNG_WRITE_SIZE 32768 + +// Set this to 1 to use a simple heuristic to select the filter to apply +// for each row of RGB image saves. As it turns out, it seems no filtering +// is the best for Doom screenshots, no matter what the heuristic might +// determine, so that's why this is 0 here. +#define USE_FILTER_HEURISTIC 0 + +// TYPES ------------------------------------------------------------------- + +struct IHDR +{ + uint32_t Width; + uint32_t Height; + uint8_t BitDepth; + uint8_t ColorType; + uint8_t Compression; + uint8_t Filter; + uint8_t Interlace; +}; + +PNGHandle::PNGHandle (FileReader &file) : bDeleteFilePtr(true), ChunkPt(0) +{ + File = std::move(file); +} + +PNGHandle::~PNGHandle () +{ + for (unsigned int i = 0; i < TextChunks.Size(); ++i) + { + delete[] TextChunks[i]; + } +} + +// EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- + +// PUBLIC FUNCTION PROTOTYPES ---------------------------------------------- + +// PRIVATE FUNCTION PROTOTYPES --------------------------------------------- + +static inline void MakeChunk (void *where, uint32_t type, size_t len); +static inline void StuffPalette (const PalEntry *from, uint8_t *to); +static bool WriteIDAT (FileWriter *file, const uint8_t *data, int len); +static void UnfilterRow (int width, uint8_t *dest, uint8_t *stream, uint8_t *prev, int bpp); +static void UnpackPixels (int width, int bytesPerRow, int bitdepth, const uint8_t *rowin, uint8_t *rowout, bool grayscale); + +// EXTERNAL DATA DECLARATIONS ---------------------------------------------- + +// PUBLIC DATA DEFINITIONS ------------------------------------------------- + +int png_level = 5; +int png_gamma = 0; +/* +CUSTOM_CVAR(Int, png_level, 5, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +{ + if (self < 0) + self = 0; + else if (self > 9) + self = 9; +} +CVAR(Float, png_gamma, 0.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +*/ +// PRIVATE DATA DEFINITIONS ------------------------------------------------ + +// CODE -------------------------------------------------------------------- + +//========================================================================== +// +// M_CreatePNG +// +// Passed a newly-created file, writes the PNG signature and IHDR, gAMA, and +// PLTE chunks. Returns true if everything went as expected. +// +//========================================================================== + +bool M_CreatePNG (FileWriter *file, const uint8_t *buffer, const PalEntry *palette, + ESSType color_type, int width, int height, int pitch, float gamma) +{ + uint8_t work[8 + // signature + 12+2*4+5 + // IHDR + 12+4 + // gAMA + 12+256*3]; // PLTE + uint32_t *const sig = (uint32_t *)&work[0]; + IHDR *const ihdr = (IHDR *)&work[8 + 8]; + uint32_t *const gama = (uint32_t *)((uint8_t *)ihdr + 2*4+5 + 12); + uint8_t *const plte = (uint8_t *)gama + 4 + 12; + size_t work_len; + + sig[0] = MAKE_ID(137,'P','N','G'); + sig[1] = MAKE_ID(13,10,26,10); + + ihdr->Width = BigLong(width); + ihdr->Height = BigLong(height); + ihdr->BitDepth = 8; + ihdr->ColorType = color_type == SS_PAL ? 3 : 2; + ihdr->Compression = 0; + ihdr->Filter = 0; + ihdr->Interlace = 0; + MakeChunk (ihdr, MAKE_ID('I','H','D','R'), 2*4+5); + + // Assume a display exponent of 2.2 (100000/2.2 ~= 45454.5) + *gama = BigLong (int (45454.5f * (png_gamma == 0.f ? gamma : png_gamma))); + MakeChunk (gama, MAKE_ID('g','A','M','A'), 4); + + if (color_type == SS_PAL) + { + StuffPalette (palette, plte); + MakeChunk (plte, MAKE_ID('P','L','T','E'), 256*3); + work_len = sizeof(work); + } + else + { + work_len = sizeof(work) - (12+256*3); + } + + if (file->Write (work, work_len) != work_len) + return false; + + return M_SaveBitmap (buffer, color_type, width, height, pitch, file); +} + +//========================================================================== +// +// M_CreateDummyPNG +// +// Like M_CreatePNG, but the image is always a grayscale 1x1 black square. +// +//========================================================================== + +bool M_CreateDummyPNG (FileWriter *file) +{ + static const uint8_t dummyPNG[] = + { + 137,'P','N','G',13,10,26,10, + 0,0,0,13,'I','H','D','R', + 0,0,0,1,0,0,0,1,8,0,0,0,0,0x3a,0x7e,0x9b,0x55, + 0,0,0,10,'I','D','A','T', + 104,222,99,96,0,0,0,2,0,1,0x9f,0x65,0x0e,0x18 + }; + return file->Write (dummyPNG, sizeof(dummyPNG)) == sizeof(dummyPNG); +} + + +//========================================================================== +// +// M_FinishPNG +// +// Writes an IEND chunk to a PNG file. The file is left opened. +// +//========================================================================== + +bool M_FinishPNG (FileWriter *file) +{ + static const uint8_t iend[12] = { 0,0,0,0,73,69,78,68,174,66,96,130 }; + return file->Write (iend, 12) == 12; +} + +//========================================================================== +// +// M_AppendPNGChunk +// +// Writes a PNG-compliant chunk to the file. +// +//========================================================================== + +bool M_AppendPNGChunk (FileWriter *file, uint32_t chunkID, const uint8_t *chunkData, uint32_t len) +{ + uint32_t head[2] = { BigLong((unsigned int)len), chunkID }; + uint32_t crc; + + if (file->Write (head, 8) == 8 && + (len == 0 || file->Write (chunkData, len) == len)) + { + crc = CalcCRC32 ((uint8_t *)&head[1], 4); + if (len != 0) + { + crc = AddCRC32 (crc, chunkData, len); + } + crc = BigLong((unsigned int)crc); + return file->Write (&crc, 4) == 4; + } + return false; +} + +//========================================================================== +// +// M_AppendPNGText +// +// Appends a PNG tEXt chunk to the file +// +//========================================================================== + +bool M_AppendPNGText (FileWriter *file, const char *keyword, const char *text) +{ + struct { uint32_t len, id; char key[80]; } head; + int len = (int)strlen (text); + int keylen = std::min ((int)strlen (keyword), 79); + uint32_t crc; + + head.len = BigLong(len + keylen + 1); + head.id = MAKE_ID('t','E','X','t'); + memset (&head.key, 0, sizeof(head.key)); + strncpy (head.key, keyword, keylen); + head.key[keylen] = 0; + + if ((int)file->Write (&head, keylen + 9) == keylen + 9 && + (int)file->Write (text, len) == len) + { + crc = CalcCRC32 ((uint8_t *)&head+4, keylen + 5); + if (len != 0) + { + crc = AddCRC32 (crc, (uint8_t *)text, len); + } + crc = BigLong(crc); + return file->Write (&crc, 4) == 4; + } + return false; +} + +//========================================================================== +// +// M_FindPNGChunk +// +// Finds a chunk in a PNG file. The file pointer will be positioned at the +// beginning of the chunk data, and its length will be returned. A return +// value of 0 indicates the chunk was either not present or had 0 length. +// This means there is no way to conclusively determine if a chunk is not +// present in a PNG file with this function, but since we're only +// interested in chunks with content, that's okay. The file pointer will +// be left sitting at the start of the chunk's data if it was found. +// +//========================================================================== + +unsigned int M_FindPNGChunk (PNGHandle *png, uint32_t id) +{ + png->ChunkPt = 0; + return M_NextPNGChunk (png, id); +} + +//========================================================================== +// +// M_NextPNGChunk +// +// Like M_FindPNGChunk, but it starts it search at the current chunk. +// +//========================================================================== + +unsigned int M_NextPNGChunk (PNGHandle *png, uint32_t id) +{ + for ( ; png->ChunkPt < png->Chunks.Size(); ++png->ChunkPt) + { + if (png->Chunks[png->ChunkPt].ID == id) + { // Found the chunk + png->File.Seek (png->Chunks[png->ChunkPt++].Offset, FileReader::SeekSet); + return png->Chunks[png->ChunkPt - 1].Size; + } + } + return 0; +} + +//========================================================================== +// +// M_GetPNGText +// +// Finds a PNG text chunk with the given signature and returns a pointer +// to a NULL-terminated string if present. Returns NULL on failure. +// +//========================================================================== + +char *M_GetPNGText (PNGHandle *png, const char *keyword) +{ + unsigned int i; + size_t keylen, textlen; + + for (i = 0; i < png->TextChunks.Size(); ++i) + { + if (strncmp (keyword, png->TextChunks[i], 80) == 0) + { + // Woo! A match was found! + keylen = std::min (80, strlen (keyword) + 1); + textlen = strlen (png->TextChunks[i] + keylen) + 1; + char *str = new char[textlen]; + strcpy (str, png->TextChunks[i] + keylen); + return str; + } + } + return NULL; +} + +// This version copies it to a supplied buffer instead of allocating a new one. + +bool M_GetPNGText (PNGHandle *png, const char *keyword, char *buffer, size_t buffsize) +{ + unsigned int i; + size_t keylen; + + for (i = 0; i < png->TextChunks.Size(); ++i) + { + if (strncmp (keyword, png->TextChunks[i], 80) == 0) + { + // Woo! A match was found! + keylen = std::min (80, strlen (keyword) + 1); + strncpy (buffer, png->TextChunks[i] + keylen, buffsize); + return true; + } + } + return false; +} + +//========================================================================== +// +// M_VerifyPNG +// +// Returns a PNGHandle if the file is a PNG or NULL if not. CRC checking of +// chunks is not done in order to save time. +// +//========================================================================== + +PNGHandle *M_VerifyPNG (FileReader &filer) +{ + PNGHandle::Chunk chunk; + PNGHandle *png; + uint32_t data[2]; + bool sawIDAT = false; + + if (filer.Read(&data, 8) != 8) + { + return NULL; + } + if (data[0] != MAKE_ID(137,'P','N','G') || data[1] != MAKE_ID(13,10,26,10)) + { // Does not have PNG signature + return NULL; + } + if (filer.Read (&data, 8) != 8) + { + return NULL; + } + if (data[1] != MAKE_ID('I','H','D','R')) + { // IHDR must be the first chunk + return NULL; + } + + // It looks like a PNG so far, so start creating a PNGHandle for it + png = new PNGHandle (filer); + // filer is no longer valid after the above line! + chunk.ID = data[1]; + chunk.Offset = 16; + chunk.Size = BigLong((unsigned int)data[0]); + png->Chunks.Push (chunk); + png->File.Seek (16, FileReader::SeekSet); + + while (png->File.Seek (chunk.Size + 4, FileReader::SeekCur) == 0) + { + // If the file ended before an IEND was encountered, it's not a PNG. + if (png->File.Read (&data, 8) != 8) + { + break; + } + // An IEND chunk terminates the PNG and must be empty + if (data[1] == MAKE_ID('I','E','N','D')) + { + if (data[0] == 0 && sawIDAT) + { + return png; + } + break; + } + // A PNG must include an IDAT chunk + if (data[1] == MAKE_ID('I','D','A','T')) + { + sawIDAT = true; + } + chunk.ID = data[1]; + chunk.Offset = (uint32_t)png->File.Tell(); + chunk.Size = BigLong((unsigned int)data[0]); + png->Chunks.Push (chunk); + + // If this is a text chunk, also record its contents. + if (data[1] == MAKE_ID('t','E','X','t')) + { + char *str = new char[chunk.Size + 1]; + + if (png->File.Read (str, chunk.Size) != chunk.Size) + { + delete[] str; + break; + } + str[chunk.Size] = 0; + png->TextChunks.Push (str); + chunk.Size = 0; // Don't try to seek past its contents again. + } + } + + filer = std::move(png->File); // need to get the reader back if this function failed. + delete png; + return NULL; +} + +//========================================================================== +// +// M_FreePNG +// +// Just deletes the PNGHandle. The file is not closed. +// +//========================================================================== + +void M_FreePNG (PNGHandle *png) +{ + delete png; +} + +//========================================================================== +// +// ReadIDAT +// +// Reads image data out of a PNG +// +//========================================================================== + +bool M_ReadIDAT (FileReader &file, uint8_t *buffer, int width, int height, int pitch, + uint8_t bitdepth, uint8_t colortype, uint8_t interlace, unsigned int chunklen) +{ + // Uninterlaced images are treated as a conceptual eighth pass by these tables. + static const uint8_t passwidthshift[8] = { 3, 3, 2, 2, 1, 1, 0, 0 }; + static const uint8_t passheightshift[8] = { 3, 3, 3, 2, 2, 1, 1, 0 }; + static const uint8_t passrowoffset[8] = { 0, 0, 4, 0, 2, 0, 1, 0 }; + static const uint8_t passcoloffset[8] = { 0, 4, 0, 2, 0, 1, 0, 0 }; + + Byte *inputLine, *prev, *curr, *adam7buff[3], *bufferend; + Byte chunkbuffer[4096]; + z_stream stream; + int err; + int i, pass, passbuff, passpitch, passwidth; + bool lastIDAT; + int bytesPerRowIn, bytesPerRowOut; + int bytesPerPixel; + bool initpass; + + switch (colortype) + { + case 2: bytesPerPixel = 3; break; // RGB + case 4: bytesPerPixel = 2; break; // LA + case 6: bytesPerPixel = 4; break; // RGBA + default: bytesPerPixel = 1; break; + } + + bytesPerRowOut = width * bytesPerPixel; + i = 4 + bytesPerRowOut * 2; + if (interlace) + { + i += bytesPerRowOut * 2; + } + inputLine = (Byte *)alloca (i); + adam7buff[0] = inputLine + 4 + bytesPerRowOut; + adam7buff[1] = adam7buff[0] + bytesPerRowOut; + adam7buff[2] = adam7buff[1] + bytesPerRowOut; + bufferend = buffer + pitch * height; + + stream.next_in = Z_NULL; + stream.avail_in = 0; + stream.zalloc = Z_NULL; + stream.zfree = Z_NULL; + err = inflateInit (&stream); + if (err != Z_OK) + { + return false; + } + lastIDAT = false; + initpass = true; + pass = interlace ? 0 : 7; + + // Silence GCC warnings. Due to initpass being true, these will be set + // before they're used, but it doesn't know that. + curr = prev = 0; + passwidth = passpitch = bytesPerRowIn = 0; + passbuff = 0; + + while (err != Z_STREAM_END && pass < 8 - interlace) + { + if (initpass) + { + int rowoffset, coloffset; + + initpass = false; + pass--; + do + { + pass++; + rowoffset = passrowoffset[pass]; + coloffset = passcoloffset[pass]; + } + while ((rowoffset >= height || coloffset >= width) && pass < 7); + if (pass == 7 && interlace) + { + break; + } + passwidth = (width + (1 << passwidthshift[pass]) - 1 - coloffset) >> passwidthshift[pass]; + prev = adam7buff[0]; + passbuff = 1; + memset (prev, 0, passwidth * bytesPerPixel); + switch (bitdepth) + { + case 8: bytesPerRowIn = passwidth * bytesPerPixel; break; + case 4: bytesPerRowIn = (passwidth+1)/2; break; + case 2: bytesPerRowIn = (passwidth+3)/4; break; + case 1: bytesPerRowIn = (passwidth+7)/8; break; + default: return false; + } + curr = buffer + rowoffset*pitch + coloffset*bytesPerPixel; + passpitch = pitch << passheightshift[pass]; + stream.next_out = inputLine; + stream.avail_out = bytesPerRowIn + 1; + } + if (stream.avail_in == 0 && chunklen > 0) + { + stream.next_in = chunkbuffer; + stream.avail_in = (uInt)file.Read (chunkbuffer, std::min(chunklen,sizeof(chunkbuffer))); + chunklen -= stream.avail_in; + } + + err = inflate (&stream, Z_SYNC_FLUSH); + if (err != Z_OK && err != Z_STREAM_END) + { // something unexpected happened + inflateEnd (&stream); + return false; + } + + if (stream.avail_out == 0) + { + if (pass >= 6) + { + // Store pixels directly into the output buffer + UnfilterRow (bytesPerRowIn, curr, inputLine, prev, bytesPerPixel); + prev = curr; + } + else + { + const uint8_t *in; + uint8_t *out; + int colstep, x; + + // Store pixels into a temporary buffer + UnfilterRow (bytesPerRowIn, adam7buff[passbuff], inputLine, prev, bytesPerPixel); + prev = adam7buff[passbuff]; + passbuff ^= 1; + in = prev; + if (bitdepth < 8) + { + UnpackPixels (passwidth, bytesPerRowIn, bitdepth, in, adam7buff[2], colortype == 0); + in = adam7buff[2]; + } + // Distribute pixels into the output buffer + out = curr; + colstep = bytesPerPixel << passwidthshift[pass]; + switch (bytesPerPixel) + { + case 1: + for (x = passwidth; x > 0; --x) + { + *out = *in; + out += colstep; + in += 1; + } + break; + + case 2: + for (x = passwidth; x > 0; --x) + { + *(uint16_t *)out = *(uint16_t *)in; + out += colstep; + in += 2; + } + break; + + case 3: + for (x = passwidth; x > 0; --x) + { + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; + out += colstep; + in += 3; + } + break; + + case 4: + for (x = passwidth; x > 0; --x) + { + *(uint32_t *)out = *(uint32_t *)in; + out += colstep; + in += 4; + } + break; + } + } + if ((curr += passpitch) >= bufferend) + { + ++pass; + initpass = true; + } + stream.next_out = inputLine; + stream.avail_out = bytesPerRowIn + 1; + } + + if (chunklen == 0 && !lastIDAT) + { + uint32_t x[3]; + + if (file.Read (x, 12) != 12) + { + lastIDAT = true; + } + else if (x[2] != MAKE_ID('I','D','A','T')) + { + lastIDAT = true; + } + else + { + chunklen = BigLong((unsigned int)x[1]); + } + } + } + + inflateEnd (&stream); + + if (bitdepth < 8) + { + // Noninterlaced images must be unpacked completely. + // Interlaced images only need their final pass unpacked. + passpitch = pitch << interlace; + for (curr = buffer + pitch * interlace; curr <= prev; curr += passpitch) + { + UnpackPixels (width, bytesPerRowIn, bitdepth, curr, curr, colortype == 0); + } + } + return true; +} + +// PRIVATE CODE ------------------------------------------------------------ + + +//========================================================================== +// +// MakeChunk +// +// Prepends the chunk length and type and appends the chunk's CRC32. +// There must be 8 bytes available before the chunk passed and 4 bytes +// after the chunk. +// +//========================================================================== + +static inline void MakeChunk (void *where, uint32_t type, size_t len) +{ + uint8_t *const data = (uint8_t *)where; + *(uint32_t *)(data - 8) = BigLong ((unsigned int)len); + *(uint32_t *)(data - 4) = type; + *(uint32_t *)(data + len) = BigLong ((unsigned int)CalcCRC32 (data-4, (unsigned int)(len+4))); +} + +//========================================================================== +// +// StuffPalette +// +// Converts 256 4-byte palette entries to 3 bytes each. +// +//========================================================================== + +static void StuffPalette (const PalEntry *from, uint8_t *to) +{ + for (int i = 256; i > 0; --i) + { + to[0] = from->r; + to[1] = from->g; + to[2] = from->b; + from += 1; + to += 3; + } +} + +//========================================================================== +// +// CalcSum +// +// +//========================================================================== + +uint32_t CalcSum(Byte *row, int len) +{ + uint32_t sum = 0; + + while (len-- != 0) + { + sum += (char)*row++; + } + return sum; +} + +//========================================================================== +// +// SelectFilter +// +// Performs the heuristic recommended by the PNG spec to decide the +// (hopefully) best filter to use for this row. To quate: +// +// Select the filter that gives the smallest sum of absolute values of +// outputs. (Consider the output bytes as signed differences for this +// test.) +// +//========================================================================== + +#if USE_FILTER_HEURISTIC +static int SelectFilter(Byte row[5][1 + MAXWIDTH*3], Byte prior[MAXWIDTH*3], int width) +{ + // As it turns out, it seems no filtering is the best for Doom screenshots, + // no matter what the heuristic might determine. + return 0; + uint32_t sum; + uint32_t bestsum; + int bestfilter; + int x; + + width *= 3; + + // The first byte of each row holds the filter type, filled in by the caller. + // However, the prior row does not contain a filter type, since it's always 0. + + bestsum = 0; + bestfilter = 0; + + // None + for (x = 1; x <= width; ++x) + { + bestsum += abs((char)row[0][x]); + } + + // Sub + row[1][1] = row[0][1]; + row[1][2] = row[0][2]; + row[1][3] = row[0][3]; + sum = abs((char)row[0][1]) + abs((char)row[0][2]) + abs((char)row[0][3]); + for (x = 4; x <= width; ++x) + { + row[1][x] = row[0][x] - row[0][x - 3]; + sum += abs((char)row[1][x]); + if (sum >= bestsum) + { // This isn't going to be any better. + break; + } + } + if (sum < bestsum) + { + bestsum = sum; + bestfilter = 1; + } + + // Up + sum = 0; + for (x = 1; x <= width; ++x) + { + row[2][x] = row[0][x] - prior[x - 1]; + sum += abs((char)row[2][x]); + if (sum >= bestsum) + { // This isn't going to be any better. + break; + } + } + if (sum < bestsum) + { + bestsum = sum; + bestfilter = 2; + } + + // Average + row[3][1] = row[0][1] - prior[0] / 2; + row[3][2] = row[0][2] - prior[1] / 2; + row[3][3] = row[0][3] - prior[2] / 2; + sum = abs((char)row[3][1]) + abs((char)row[3][2]) + abs((char)row[3][3]); + for (x = 4; x <= width; ++x) + { + row[3][x] = row[0][x] - (row[0][x - 3] + prior[x - 1]) / 2; + sum += (char)row[3][x]; + if (sum >= bestsum) + { // This isn't going to be any better. + break; + } + } + if (sum < bestsum) + { + bestsum = sum; + bestfilter = 3; + } + + // Paeth + row[4][1] = row[0][1] - prior[0]; + row[4][2] = row[0][2] - prior[1]; + row[4][3] = row[0][3] - prior[2]; + sum = abs((char)row[4][1]) + abs((char)row[4][2]) + abs((char)row[4][3]); + for (x = 4; x <= width; ++x) + { + Byte a = row[0][x - 3]; + Byte b = prior[x - 1]; + Byte c = prior[x - 4]; + int p = a + b - c; + int pa = abs(p - a); + int pb = abs(p - b); + int pc = abs(p - c); + if (pa <= pb && pa <= pc) + { + row[4][x] = row[0][x] - a; + } + else if (pb <= pc) + { + row[4][x] = row[0][x] - b; + } + else + { + row[4][x] = row[0][x] - c; + } + sum += (char)row[4][x]; + if (sum >= bestsum) + { // This isn't going to be any better. + break; + } + } + if (sum < bestsum) + { + bestfilter = 4; + } + + return bestfilter; +} +#else +#define SelectFilter(x,y,z) 0 +#endif + +//========================================================================== +// +// M_SaveBitmap +// +// Given a bitmap, creates one or more IDAT chunks in the given file. +// Returns true on success. +// +//========================================================================== + +bool M_SaveBitmap(const uint8_t *from, ESSType color_type, int width, int height, int pitch, FileWriter *file) +{ +#define MAXWIDTH 2048 +#if USE_FILTER_HEURISTIC + Byte prior[MAXWIDTH*3]; + Byte temprow[5][1 + MAXWIDTH*3]; +#else + Byte temprow[1][1 + MAXWIDTH*3]; +#endif + Byte buffer[PNG_WRITE_SIZE]; + z_stream stream; + int err; + int y; + + stream.next_in = Z_NULL; + stream.avail_in = 0; + stream.zalloc = Z_NULL; + stream.zfree = Z_NULL; + err = deflateInit (&stream, png_level); + + if (err != Z_OK) + { + return false; + } + + y = height; + stream.next_out = buffer; + stream.avail_out = sizeof(buffer); + + temprow[0][0] = 0; +#if USE_FILTER_HEURISTIC + temprow[1][0] = 1; + temprow[2][0] = 2; + temprow[3][0] = 3; + temprow[4][0] = 4; + + // Fill the prior row with 0 for RGB images. Paletted is always filter 0, + // so it doesn't need this. + if (color_type != SS_PAL) + { + memset(prior, 0, width * 3); + } +#endif + + while (y-- > 0 && err == Z_OK) + { + switch (color_type) + { + case SS_PAL: + memcpy(&temprow[0][1], from, width); + // always use filter type 0 for paletted images + stream.next_in = temprow[0]; + stream.avail_in = width + 1; + break; + + case SS_RGB: + memcpy(&temprow[0][1], from, width*3); + stream.next_in = temprow[SelectFilter(temprow, prior, width)]; + stream.avail_in = width * 3 + 1; + break; + + case SS_BGRA: + for (int x = 0; x < width; ++x) + { + temprow[0][x*3 + 1] = from[x*4 + 2]; + temprow[0][x*3 + 2] = from[x*4 + 1]; + temprow[0][x*3 + 3] = from[x*4]; + } + stream.next_in = temprow[SelectFilter(temprow, prior, width)]; + stream.avail_in = width * 3 + 1; + break; + } +#if USE_FILTER_HEURISTIC + if (color_type != SS_PAL) + { + // Save this row for filter calculations on the next row. + memcpy (prior, &temprow[0][1], stream.avail_in - 1); + } +#endif + + from += pitch; + + err = deflate (&stream, (y == 0) ? Z_FINISH : 0); + if (err != Z_OK) + { + break; + } + while (stream.avail_out == 0) + { + if (!WriteIDAT (file, buffer, sizeof(buffer))) + { + return false; + } + stream.next_out = buffer; + stream.avail_out = sizeof(buffer); + if (stream.avail_in != 0) + { + err = deflate (&stream, (y == 0) ? Z_FINISH : 0); + if (err != Z_OK) + { + break; + } + } + } + } + + while (err == Z_OK) + { + err = deflate (&stream, Z_FINISH); + if (err != Z_OK) + { + break; + } + if (stream.avail_out == 0) + { + if (!WriteIDAT (file, buffer, sizeof(buffer))) + { + return false; + } + stream.next_out = buffer; + stream.avail_out = sizeof(buffer); + } + } + + deflateEnd (&stream); + + if (err != Z_STREAM_END) + { + return false; + } + return WriteIDAT (file, buffer, sizeof(buffer)-stream.avail_out); +} + +//========================================================================== +// +// WriteIDAT +// +// Writes a single IDAT chunk to the file. Returns true on success. +// +//========================================================================== + +static bool WriteIDAT (FileWriter *file, const uint8_t *data, int len) +{ + uint32_t foo[2], crc; + + foo[0] = BigLong (len); + foo[1] = MAKE_ID('I','D','A','T'); + crc = CalcCRC32 ((uint8_t *)&foo[1], 4); + crc = BigLong ((unsigned int)AddCRC32 (crc, data, len)); + + if (file->Write (foo, 8) != 8 || + file->Write (data, len) != (size_t)len || + file->Write (&crc, 4) != 4) + { + return false; + } + return true; +} + +//========================================================================== +// +// UnfilterRow +// +// Unfilters the given row. Unknown filter types are silently ignored. +// bpp is bytes per pixel, not bits per pixel. +// width is in bytes, not pixels. +// +//========================================================================== + +void UnfilterRow (int width, uint8_t *dest, uint8_t *row, uint8_t *prev, int bpp) +{ + int x; + + switch (*row++) + { + case 1: // Sub + x = bpp; + do + { + *dest++ = *row++; + } + while (--x); + for (x = width - bpp; x > 0; --x) + { + *dest = *row++ + *(dest - bpp); + dest++; + } + break; + + case 2: // Up + x = width; + do + { + *dest++ = *row++ + *prev++; + } + while (--x); + break; + + case 3: // Average + x = bpp; + do + { + *dest++ = *row++ + (*prev++)/2; + } + while (--x); + for (x = width - bpp; x > 0; --x) + { + *dest = *row++ + (uint8_t)((unsigned(*(dest - bpp)) + unsigned(*prev++)) >> 1); + dest++; + } + break; + + case 4: // Paeth + x = bpp; + do + { + *dest++ = *row++ + *prev++; + } + while (--x); + for (x = width - bpp; x > 0; --x) + { + int a, b, c, pa, pb, pc; + + a = *(dest - bpp); + b = *(prev); + c = *(prev - bpp); + pa = b - c; + pb = a - c; + pc = abs (pa + pb); + pa = abs (pa); + pb = abs (pb); + *dest = *row + (uint8_t)((pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c); + dest++; + row++; + prev++; + } + break; + + default: // Treat everything else as filter type 0 (none) + memcpy (dest, row, width); + break; + } +} + +//========================================================================== +// +// UnpackPixels +// +// Unpacks a row of pixels whose depth is less than 8 so that each pixel +// occupies a single byte. The outrow must be "width" bytes long. +// "bytesPerRow" is the number of bytes for the packed row. The in and out +// rows may overlap, but only if rowin == rowout. +// +//========================================================================== + +static void UnpackPixels (int width, int bytesPerRow, int bitdepth, const uint8_t *rowin, uint8_t *rowout, bool grayscale) +{ + const uint8_t *in; + uint8_t *out; + uint8_t pack; + int lastbyte; + + assert(bitdepth == 1 || bitdepth == 2 || bitdepth == 4); + + out = rowout + width; + in = rowin + bytesPerRow; + + switch (bitdepth) + { + case 1: + + lastbyte = width & 7; + if (lastbyte != 0) + { + in--; + pack = *in; + out -= lastbyte; + out[0] = (pack >> 7) & 1; + if (lastbyte >= 2) out[1] = (pack >> 6) & 1; + if (lastbyte >= 3) out[2] = (pack >> 5) & 1; + if (lastbyte >= 4) out[3] = (pack >> 4) & 1; + if (lastbyte >= 5) out[4] = (pack >> 3) & 1; + if (lastbyte >= 6) out[5] = (pack >> 2) & 1; + if (lastbyte == 7) out[6] = (pack >> 1) & 1; + } + + while (in-- > rowin) + { + pack = *in; + out -= 8; + out[0] = (pack >> 7) & 1; + out[1] = (pack >> 6) & 1; + out[2] = (pack >> 5) & 1; + out[3] = (pack >> 4) & 1; + out[4] = (pack >> 3) & 1; + out[5] = (pack >> 2) & 1; + out[6] = (pack >> 1) & 1; + out[7] = pack & 1; + } + break; + + case 2: + + lastbyte = width & 3; + if (lastbyte != 0) + { + in--; + pack = *in; + out -= lastbyte; + out[0] = pack >> 6; + if (lastbyte >= 2) out[1] = (pack >> 4) & 3; + if (lastbyte == 3) out[2] = (pack >> 2) & 3; + } + + while (in-- > rowin) + { + pack = *in; + out -= 4; + out[0] = pack >> 6; + out[1] = (pack >> 4) & 3; + out[2] = (pack >> 2) & 3; + out[3] = pack & 3; + } + break; + + case 4: + lastbyte = width & 1; + if (lastbyte != 0) + { + in--; + pack = *in; + out -= lastbyte; + out[0] = pack >> 4; + } + + while (in-- > rowin) + { + pack = *in; + out -= 2; + out[0] = pack >> 4; + out[1] = pack & 15; + } + break; + } + + // Expand grayscale to 8bpp + if (grayscale) + { + // Put the 2-bit lookup table on the stack, since it's probably already + // in a cache line. + union + { + uint32_t bits2l; + uint8_t bits2[4]; + }; + + out = rowout + width; + switch (bitdepth) + { + case 1: + while (--out >= rowout) + { + // 1 becomes -1 (0xFF), and 0 remains untouched. + *out = 0 - *out; + } + break; + + case 2: + bits2l = MAKE_ID(0x00,0x55,0xAA,0xFF); + while (--out >= rowout) + { + *out = bits2[*out]; + } + break; + + case 4: + while (--out >= rowout) + { + *out |= (*out << 4); + } + break; + } + } +} diff --git a/source/common/utility/m_png.h b/source/common/utility/m_png.h new file mode 100644 index 000000000..85fd1c64f --- /dev/null +++ b/source/common/utility/m_png.h @@ -0,0 +1,133 @@ +#ifndef __M_PNG_H +#define __M_PNG_H +/* +** m_png.h +** +**--------------------------------------------------------------------------- +** Copyright 2002-2005 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include +#include "zstring.h" +#include "files.h" +#include "palentry.h" + +// Screenshot buffer image data types +enum ESSType +{ + SS_PAL, + SS_RGB, + SS_BGRA +}; + +class FileWriter; +// PNG Writing -------------------------------------------------------------- + +// Start writing an 8-bit palettized PNG file. +// The passed file should be a newly created file. +// This function writes the PNG signature and the IHDR, gAMA, PLTE, and IDAT +// chunks. +bool M_CreatePNG (FileWriter *file, const uint8_t *buffer, const PalEntry *pal, + ESSType color_type, int width, int height, int pitch, float gamma); + +// Creates a grayscale 1x1 PNG file. Used for savegames without savepics. +bool M_CreateDummyPNG (FileWriter *file); + +// Appends any chunk to a PNG file started with M_CreatePNG. +bool M_AppendPNGChunk (FileWriter *file, uint32_t chunkID, const uint8_t *chunkData, uint32_t len); + +// Adds a tEXt chunk to a PNG file started with M_CreatePNG. +bool M_AppendPNGText (FileWriter *file, const char *keyword, const char *text); + +// Appends the IEND chunk to a PNG file. +bool M_FinishPNG (FileWriter *file); + +bool M_SaveBitmap(const uint8_t *from, ESSType color_type, int width, int height, int pitch, FileWriter *file); + +// PNG Reading -------------------------------------------------------------- + +struct PNGHandle +{ + struct Chunk + { + uint32_t ID; + uint32_t Offset; + uint32_t Size; + }; + + FileReader File; + bool bDeleteFilePtr; + TArray Chunks; + TArray TextChunks; + unsigned int ChunkPt; + + PNGHandle(FileReader &file); + ~PNGHandle(); +}; + +// Verify that a file really is a PNG file. This includes not only checking +// the signature, but also checking for the IEND chunk. CRC checking of +// each chunk is not done. If it is valid, you get a PNGHandle to pass to +// the following functions. +PNGHandle *M_VerifyPNG (FileReader &file); + +// Finds a chunk in a PNG file. The file pointer will be positioned at the +// beginning of the chunk data, and its length will be returned. A return +// value of 0 indicates the chunk was either not present or had 0 length. +unsigned int M_FindPNGChunk (PNGHandle *png, uint32_t chunkID); + +// Finds a chunk in the PNG file, starting its search at whatever chunk +// the file pointer is currently positioned at. +unsigned int M_NextPNGChunk (PNGHandle *png, uint32_t chunkID); + +// Finds a PNG text chunk with the given signature and returns a pointer +// to a NULL-terminated string if present. Returns NULL on failure. +// (Note: tEXt, not zTXt.) +char *M_GetPNGText (PNGHandle *png, const char *keyword); +bool M_GetPNGText (PNGHandle *png, const char *keyword, char *buffer, size_t buffsize); + +// The file must be positioned at the start of the first IDAT. It reads +// image data into the provided buffer. Returns true on success. +bool M_ReadIDAT (FileReader &file, uint8_t *buffer, int width, int height, int pitch, + uint8_t bitdepth, uint8_t colortype, uint8_t interlace, unsigned int idatlen); + + +class FTexture; + +FTexture *PNGTexture_CreateFromFile(PNGHandle *png, const FString &filename); + +#ifndef MAKE_ID +#ifndef __BIG_ENDIAN__ +#define MAKE_ID(a,b,c,d) ((uint32_t)((a)|((b)<<8)|((c)<<16)|((d)<<24))) +#else +#define MAKE_ID(a,b,c,d) ((uint32_t)((d)|((c)<<8)|((b)<<16)|((a)<<24))) +#endif +#endif + +#endif diff --git a/source/common/utility/memarena.cpp b/source/common/utility/memarena.cpp new file mode 100644 index 000000000..f23f8d350 --- /dev/null +++ b/source/common/utility/memarena.cpp @@ -0,0 +1,417 @@ +/* +** memarena.cpp +** Implements memory arenas. +** +**--------------------------------------------------------------------------- +** Copyright 2010 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +** A memory arena is used for efficient allocation of many small objects that +** will all be freed at once. Note that since individual destructors are not +** called, you must not use an arena to allocate any objects that use a +** destructor, either explicitly or implicitly (because they have members +** with destructors). +*/ + +#include "basics.h" +#include "memarena.h" +#include "printf.h" + +struct FMemArena::Block +{ + Block *NextBlock; + void *Limit; // End of this block + void *Avail; // Start of free space in this block + void *alignme; // align to 16 bytes. + + void Reset(); + void *Alloc(size_t size); +}; + +//========================================================================== +// +// RoundPointer +// +// Rounds a pointer up to a pointer-sized boundary. +// +//========================================================================== + +static inline void *RoundPointer(void *ptr) +{ + return (void *)(((size_t)ptr + sizeof(void*) - 1) & ~(sizeof(void*) - 1)); +} + +//========================================================================== +// +// FMemArena Constructor +// +//========================================================================== + +FMemArena::FMemArena(size_t blocksize) +{ + TopBlock = NULL; + FreeBlocks = NULL; + BlockSize = blocksize; +} + +//========================================================================== +// +// FMemArena Destructor +// +//========================================================================== + +FMemArena::~FMemArena() +{ + FreeAllBlocks(); +} + +//========================================================================== +// +// FMemArena :: Alloc +// +//========================================================================== + +void *FMemArena::iAlloc(size_t size) +{ + Block *block; + + for (block = TopBlock; block != NULL; block = block->NextBlock) + { + void *res = block->Alloc(size); + if (res != NULL) + { + return res; + } + } + block = AddBlock(size); + return block->Alloc(size); +} + +void *FMemArena::Alloc(size_t size) +{ + return iAlloc((size + 15) & ~15); +} + +//========================================================================== +// +// FMemArena :: FreeAll +// +// Moves all blocks to the free list. No system-level deallocation occurs. +// +//========================================================================== + +void FMemArena::FreeAll() +{ + for (Block *next, *block = TopBlock; block != NULL; block = next) + { + next = block->NextBlock; + block->Reset(); + block->NextBlock = FreeBlocks; + FreeBlocks = block; + } + TopBlock = NULL; +} + +//========================================================================== +// +// FMemArena :: FreeAllBlocks +// +// Frees all blocks used by this arena. +// +//========================================================================== + +void FMemArena::FreeAllBlocks() +{ + FreeBlockChain(TopBlock); + FreeBlockChain(FreeBlocks); +} + +//========================================================================== +// +// FMemArena :: DumpInfo +// +// Prints some info about this arena +// +//========================================================================== + +void FMemArena::DumpInfo() +{ + size_t allocated = 0; + size_t used = 0; + for (auto block = TopBlock; block != NULL; block = block->NextBlock) + { + allocated += BlockSize; + used += BlockSize - ((char*)block->Limit - (char*)block->Avail); + } + Printf("%zu bytes allocated, %zu bytes in use\n", allocated, used); +} + +//========================================================================== +// +// FMemArena :: DumpInfo +// +// Dumps the arena to a file (for debugging) +// +//========================================================================== + +void FMemArena::DumpData(FILE *f) +{ + for (auto block = TopBlock; block != NULL; block = block->NextBlock) + { + auto used = BlockSize - ((char*)block->Limit - (char*)block->Avail); + fwrite(block, 1, used, f); + } +} + +//========================================================================== +// +// FMemArena :: FreeBlockChain +// +// Frees a chain of blocks. +// +//========================================================================== + +void FMemArena::FreeBlockChain(Block *&top) +{ + for (Block *next, *block = top; block != NULL; block = next) + { + next = block->NextBlock; + free(block); + } + top = NULL; +} + +//========================================================================== +// +// FMemArena :: AddBlock +// +// Allocates a block large enough to hold at least bytes and adds it +// to the TopBlock chain. +// +//========================================================================== + +FMemArena::Block *FMemArena::AddBlock(size_t size) +{ + Block *mem, **last; + size += sizeof(Block); // Account for header size + + // Search for a free block to use + for (last = &FreeBlocks, mem = FreeBlocks; mem != NULL; last = &mem->NextBlock, mem = mem->NextBlock) + { + if ((uint8_t *)mem->Limit - (uint8_t *)mem >= (ptrdiff_t)size) + { + *last = mem->NextBlock; + break; + } + } + if (mem == NULL) + { + // Allocate a new block + if (size < BlockSize) + { + size = BlockSize; + } + else + { // Stick some free space at the end so we can use this block for + // other things. + size += BlockSize/2; + } + mem = (Block *)malloc(size); + mem->Limit = (uint8_t *)mem + size; + } + mem->Reset(); + mem->NextBlock = TopBlock; + TopBlock = mem; + return mem; +} + +//========================================================================== +// +// FMemArena :: Block :: Reset +// +// Resets this block's Avail pointer. +// +//========================================================================== + +void FMemArena::Block::Reset() +{ + Avail = RoundPointer(reinterpret_cast(this) + sizeof(*this)); +} + +//========================================================================== +// +// FMemArena :: Block :: Alloc +// +// Allocates memory from the block if it has space. Returns NULL if not. +// +//========================================================================== + +void *FMemArena::Block::Alloc(size_t size) +{ + if ((char *)Avail + size > Limit) + { + return NULL; + } + void *res = Avail; + Avail = RoundPointer((char *)Avail + size); + return res; +} + +//========================================================================== +// +// FSharedStringArena Constructor +// +//========================================================================== + +FSharedStringArena::FSharedStringArena() +{ + memset(Buckets, 0, sizeof(Buckets)); +} + +//========================================================================== +// +// FSharedStringArena Destructor +// +//========================================================================== + +FSharedStringArena::~FSharedStringArena() +{ + FreeAll(); + // FMemArena destructor will free the blocks. +} + +//========================================================================== +// +// FSharedStringArena :: Alloc +// +// Allocates a new string and initializes it with the passed string. This +// version takes an FString as a parameter, so it won't need to allocate any +// memory for the string text if it already exists in the arena. +// +//========================================================================== + +FString *FSharedStringArena::Alloc(const FString &source) +{ + unsigned int hash; + Node *strnode; + + strnode = FindString(source, source.Len(), hash); + if (strnode == NULL) + { + strnode = (Node *)iAlloc(sizeof(Node)); + ::new(&strnode->String) FString(source); + strnode->Hash = hash; + hash %= Bucket_Count; + strnode->Next = Buckets[hash]; + Buckets[hash] = strnode; + } + return &strnode->String; +} + +//========================================================================== +// +// FSharedStringArena :: Alloc +// +//========================================================================== + +FString *FSharedStringArena::Alloc(const char *source) +{ + return Alloc(source, strlen(source)); +} + +//========================================================================== +// +// FSharedStringArena :: Alloc +// +//========================================================================== + +FString *FSharedStringArena::Alloc(const char *source, size_t strlen) +{ + unsigned int hash; + Node *strnode; + + strnode = FindString(source, strlen, hash); + if (strnode == NULL) + { + strnode = (Node *)iAlloc(sizeof(Node)); + ::new(&strnode->String) FString(source, strlen); + strnode->Hash = hash; + hash %= Bucket_Count; + strnode->Next = Buckets[hash]; + Buckets[hash] = strnode; + } + return &strnode->String; +} + +//========================================================================== +// +// FSharedStringArena :: FindString +// +// Finds the string if it's already in the arena. Returns NULL if not. +// +//========================================================================== + +FSharedStringArena::Node *FSharedStringArena::FindString(const char *str, size_t strlen, unsigned int &hash) +{ + hash = SuperFastHash(str, strlen); + + for (Node *node = Buckets[hash % Bucket_Count]; node != NULL; node = node->Next) + { + if (node->Hash == hash && node->String.Len() == strlen && memcmp(&node->String[0], str, strlen) == 0) + { + return node; + } + } + return NULL; +} + +//========================================================================== +// +// FSharedStringArena :: FreeAll +// +// In addition to moving all used blocks onto the free list, all FStrings +// they contain will have their destructors called. +// +//========================================================================== + +void FSharedStringArena::FreeAll() +{ + for (Block *next, *block = TopBlock; block != NULL; block = next) + { + next = block->NextBlock; + void *limit = block->Avail; + block->Reset(); + for (Node *string = (Node *)block->Avail; string < limit; ++string) + { + string->~Node(); + } + block->NextBlock = FreeBlocks; + FreeBlocks = block; + } + memset(Buckets, 0, sizeof(Buckets)); + TopBlock = NULL; +} diff --git a/source/common/utility/memarena.h b/source/common/utility/memarena.h new file mode 100644 index 000000000..9d8f4bfb2 --- /dev/null +++ b/source/common/utility/memarena.h @@ -0,0 +1,97 @@ +/* +** memarena.h +** +**--------------------------------------------------------------------------- +** Copyright 2010 Randy Heit +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#ifndef __MEMARENA_H +#define __MEMARENA_H + +#include "zstring.h" + +// A general purpose arena. +class FMemArena +{ +public: + FMemArena(size_t blocksize = 10*1024); + ~FMemArena(); + + void *Alloc(size_t size); + void FreeAll(); + void FreeAllBlocks(); + void DumpInfo(); + void DumpData(FILE *f); + +protected: + struct Block; + + Block *AddBlock(size_t size); + void FreeBlockChain(Block *&top); + void *iAlloc(size_t size); + + Block *TopBlock; + Block *FreeBlocks; + size_t BlockSize; +}; + +// An arena specializing in storage of FStrings. It knows how to free them, +// but this means it also should never be used for allocating anything else. +// Identical strings all return the same pointer. +class FSharedStringArena : public FMemArena +{ +public: + FSharedStringArena(); + ~FSharedStringArena(); + void FreeAll(); + + class FString *Alloc(const FString &source); + class FString *Alloc(const char *source); + class FString *Alloc(const char *source, size_t strlen); + +protected: + struct Node + { + Node *Next; + FString String; + unsigned int Hash; + }; + enum + { + Bucket_Count = 256 + }; + Node *Buckets[Bucket_Count]; + + Node *FindString(const char *str, size_t strlen, unsigned int &hash); +private: + void *Alloc(size_t size) { return NULL; } // No access to FMemArena::Alloc for outsiders. +}; + + +#endif diff --git a/source/common/utility/palentry.h b/source/common/utility/palentry.h new file mode 100644 index 000000000..8f4bfa6d8 --- /dev/null +++ b/source/common/utility/palentry.h @@ -0,0 +1,82 @@ +#pragma once + +#include + +struct PalEntry +{ + PalEntry() = default; + PalEntry (uint32_t argb) { d = argb; } + operator uint32_t () const { return d; } + void SetRGB(PalEntry other) + { + d = other.d & 0xffffff; + } + PalEntry Modulate(PalEntry other) const + { + if (isWhite()) + { + return other; + } + else if (other.isWhite()) + { + return *this; + } + else + { + other.r = (r * other.r) / 255; + other.g = (g * other.g) / 255; + other.b = (b * other.b) / 255; + return other; + } + } + int Luminance() const + { + return (r * 77 + g * 143 + b * 37) >> 8; + } + + void Decolorize() // this for 'nocoloredspritelighting' and not the same as desaturation. The normal formula results in a value that's too dark. + { + int v = (r + g + b); + r = g = b = ((255*3) + v + v) / 9; + } + bool isBlack() const + { + return (d & 0xffffff) == 0; + } + bool isWhite() const + { + return (d & 0xffffff) == 0xffffff; + } + PalEntry &operator= (const PalEntry &other) = default; + PalEntry &operator= (uint32_t other) { d = other; return *this; } + PalEntry InverseColor() const { PalEntry nc; nc.a = a; nc.r = 255 - r; nc.g = 255 - g; nc.b = 255 - b; return nc; } +#ifdef __BIG_ENDIAN__ + PalEntry (uint8_t ir, uint8_t ig, uint8_t ib) : a(0), r(ir), g(ig), b(ib) {} + PalEntry (uint8_t ia, uint8_t ir, uint8_t ig, uint8_t ib) : a(ia), r(ir), g(ig), b(ib) {} + union + { + struct + { + uint8_t a,r,g,b; + }; + uint32_t d; + }; +#else + PalEntry (uint8_t ir, uint8_t ig, uint8_t ib) : b(ib), g(ig), r(ir), a(0) {} + PalEntry (uint8_t ia, uint8_t ir, uint8_t ig, uint8_t ib) : b(ib), g(ig), r(ir), a(ia) {} + union + { + struct + { + uint8_t b,g,r,a; + }; + uint32_t d; + }; +#endif +}; + +inline int Luminance(int r, int g, int b) +{ + return (r * 77 + g * 143 + b * 37) >> 8; +} + diff --git a/source/platform/win32/winbits.cpp b/source/platform/win32/winbits.cpp index 540572974..9ac920118 100644 --- a/source/platform/win32/winbits.cpp +++ b/source/platform/win32/winbits.cpp @@ -1,5 +1,6 @@ // Windows layer-independent code +#include #include "compat.h" #include "build.h" #include "baselayer.h"