/* ** 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; }