aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--engines/sword25/gfx/image/pngloader.cpp37
-rw-r--r--graphics/module.mk1
-rw-r--r--graphics/png.cpp449
-rw-r--r--graphics/png.h162
4 files changed, 647 insertions, 2 deletions
diff --git a/engines/sword25/gfx/image/pngloader.cpp b/engines/sword25/gfx/image/pngloader.cpp
index cdff0012c2..f54b45254b 100644
--- a/engines/sword25/gfx/image/pngloader.cpp
+++ b/engines/sword25/gfx/image/pngloader.cpp
@@ -32,13 +32,23 @@
*
*/
+// Define to use ScummVM's PNG decoder, instead of libpng
+#define USE_INTERNAL_PNG_DECODER
+
+#ifndef USE_INTERNAL_PNG_DECODER
// Disable symbol overrides so that we can use png.h
#define FORBIDDEN_SYMBOL_ALLOW_ALL
+#endif
#include "common/memstream.h"
#include "sword25/gfx/image/image.h"
#include "sword25/gfx/image/pngloader.h"
+#ifndef USE_INTERNAL_PNG_DECODER
#include <png.h>
+#else
+#include "graphics/pixelformat.h"
+#include "graphics/png.h"
+#endif
namespace Sword25 {
@@ -87,6 +97,7 @@ static uint findEmbeddedPNG(const byte *fileDataPtr, uint fileSize) {
return static_cast<uint>(stream.pos() + compressedGamedataSize);
}
+#ifndef USE_INTERNAL_PNG_DECODER
static void png_user_read_data(png_structp png_ptr, png_bytep data, png_size_t length) {
const byte **ref = (const byte **)png_get_io_ptr(png_ptr);
memcpy(data, *ref, length);
@@ -96,9 +107,10 @@ static void png_user_read_data(png_structp png_ptr, png_bytep data, png_size_t l
static bool doIsCorrectImageFormat(const byte *fileDataPtr, uint fileSize) {
return (fileSize > 8) && png_check_sig(const_cast<byte *>(fileDataPtr), 8);
}
-
+#endif
bool PNGLoader::doDecodeImage(const byte *fileDataPtr, uint fileSize, byte *&uncompressedDataPtr, int &width, int &height, int &pitch) {
+#ifndef USE_INTERNAL_PNG_DECODER
png_structp png_ptr = NULL;
png_infop info_ptr = NULL;
@@ -202,7 +214,25 @@ bool PNGLoader::doDecodeImage(const byte *fileDataPtr, uint fileSize, byte *&unc
// Destroy libpng structures
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
+#else
+ Common::MemoryReadStream *fileStr = new Common::MemoryReadStream(fileDataPtr, fileSize, DisposeAfterUse::NO);
+ Graphics::PNG *png = new Graphics::PNG();
+ if (!png->read(fileStr)) // the fileStr pointer, and thus pFileData will be deleted after this is done
+ error("Error while reading PNG image");
+
+ Graphics::PixelFormat format = Graphics::PixelFormat(4, 8, 8, 8, 8, 16, 8, 0, 24);
+ Graphics::Surface *pngSurface = png->getSurface(format);
+ width = pngSurface->w;
+ height = pngSurface->h;
+ uncompressedDataPtr = new byte[pngSurface->pitch * pngSurface->h];
+ memcpy(uncompressedDataPtr, (byte *)pngSurface->pixels, pngSurface->pitch * pngSurface->h);
+ pngSurface->free();
+
+ delete pngSurface;
+ delete png;
+
+#endif
// Signal success
return true;
}
@@ -213,6 +243,7 @@ bool PNGLoader::decodeImage(const byte *fileDataPtr, uint fileSize, byte *&uncom
}
bool PNGLoader::doImageProperties(const byte *fileDataPtr, uint fileSize, int &width, int &height) {
+#ifndef USE_INTERNAL_PNG_DECODER
// Check for valid PNG signature
if (!doIsCorrectImageFormat(fileDataPtr, fileSize))
return false;
@@ -249,7 +280,9 @@ bool PNGLoader::doImageProperties(const byte *fileDataPtr, uint fileSize, int &w
// Die Strukturen freigeben
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
-
+#else
+ // We don't need to read the image properties here...
+#endif
return true;
}
diff --git a/graphics/module.mk b/graphics/module.mk
index 827d2886f7..c962f0617d 100644
--- a/graphics/module.mk
+++ b/graphics/module.mk
@@ -15,6 +15,7 @@ MODULE_OBJS := \
imagedec.o \
jpeg.o \
pict.o \
+ png.o \
primitives.o \
scaler.o \
scaler/thumbnail_intern.o \
diff --git a/graphics/png.cpp b/graphics/png.cpp
new file mode 100644
index 0000000000..cf19969b46
--- /dev/null
+++ b/graphics/png.cpp
@@ -0,0 +1,449 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "graphics/conversion.h"
+#include "graphics/png.h"
+#include "graphics/pixelformat.h"
+
+#include "common/endian.h"
+#include "common/memstream.h"
+#include "common/stream.h"
+#include "common/util.h"
+#include "common/zlib.h"
+
+namespace Graphics {
+
+enum PNGChunks {
+ // == Critical chunks =====================================================
+ kChunkIHDR = MKID_BE('IHDR'), // Image header
+ kChunkIDAT = MKID_BE('IDAT'), // Image data
+ kChunkPLTE = MKID_BE('PLTE'), // Palette
+ kChunkIEND = MKID_BE('IEND'), // Image trailer
+ // == Ancillary chunks ====================================================
+ kChunktRNS = MKID_BE('tRNS') // Transparency
+ // All of the other ancillary chunks are ignored. They're added here for
+ // reference only.
+ // cHRM - Primary chromacities and white point
+ // gAMA - Image gamma
+ // iCCP - Embedded ICC profile
+ // sBIT - Significant bits
+ // sRGB - Standard RGB color space
+ // tEXT - Textual data
+ // sTXt - Compressed textual data
+ // iTXt - International textual data
+ // bKGD - Background color
+ // hIST - Image histogram
+ // pHYs - Physical pixel dimensions
+ // sPLT - Suggested palette
+ // tIME - Image last-modification time
+};
+
+// Refer to http://www.w3.org/TR/PNG/#9Filters
+enum PNGFilters {
+ kFilterNone = 0,
+ kFilterSub = 1,
+ kFilterUp = 2,
+ kFilterAverage = 3,
+ kFilterPaeth = 4
+};
+
+#define PNG_HEADER(a, b, c, d) CONSTANT_LE_32(d | (c << 8) | (b << 16) | (a << 24))
+
+PNG::PNG() : _compressedBuffer(0), _compressedBufferSize(0),
+ _unfilteredSurface(0), _transparentColorSpecified(false) {
+}
+
+PNG::~PNG() {
+ if (_unfilteredSurface) {
+ _unfilteredSurface->free();
+ delete _unfilteredSurface;
+ }
+}
+
+Graphics::Surface *PNG::getSurface(const PixelFormat &format) {
+ Graphics::Surface *output = new Graphics::Surface();
+ output->create(_unfilteredSurface->w, _unfilteredSurface->h, format.bytesPerPixel);
+ byte *src = (byte *)_unfilteredSurface->pixels;
+ byte a = 0xFF;
+
+ if (_header.colorType != kIndexed) {
+ if (_header.colorType == kTrueColor || _header.colorType == kTrueColorWithAlpha) {
+ if (_unfilteredSurface->bytesPerPixel != 3 && _unfilteredSurface->bytesPerPixel != 4)
+ error("Unsupported truecolor PNG format");
+ } else if (_header.colorType == kGrayScale || _header.colorType == kGrayScaleWithAlpha) {
+ if (_unfilteredSurface->bytesPerPixel != 1 && _unfilteredSurface->bytesPerPixel != 2)
+ error("Unsupported grayscale PNG format");
+ }
+
+ for (uint16 i = 0; i < output->h; i++) {
+ for (uint16 j = 0; j < output->w; j++) {
+ if (format.bytesPerPixel == 2) {
+ if (_unfilteredSurface->bytesPerPixel == 1) { // Grayscale
+ if (_transparentColorSpecified)
+ a = (src[0] == _transparentColor[0]) ? 0 : 0xFF;
+ *((uint16 *)output->getBasePtr(j, i)) = format.ARGBToColor( a, src[0], src[0], src[0]);
+ } else if (_unfilteredSurface->bytesPerPixel == 2) { // Grayscale + alpha
+ *((uint16 *)output->getBasePtr(j, i)) = format.ARGBToColor(src[1], src[0], src[0], src[0]);
+ } else if (_unfilteredSurface->bytesPerPixel == 3) { // RGB
+ if (_transparentColorSpecified) {
+ bool isTransparentColor = (src[0] == _transparentColor[0] &&
+ src[1] == _transparentColor[1] &&
+ src[2] == _transparentColor[2]);
+ a = isTransparentColor ? 0 : 0xFF;
+ }
+ *((uint16 *)output->getBasePtr(j, i)) = format.ARGBToColor( a, src[0], src[1], src[2]);
+ } else if (_unfilteredSurface->bytesPerPixel == 4) { // RGBA
+ *((uint16 *)output->getBasePtr(j, i)) = format.ARGBToColor(src[3], src[0], src[1], src[2]);
+ }
+ } else {
+ if (_unfilteredSurface->bytesPerPixel == 1) { // Grayscale
+ if (_transparentColorSpecified)
+ a = (src[0] == _transparentColor[0]) ? 0 : 0xFF;
+ *((uint32 *)output->getBasePtr(j, i)) = format.ARGBToColor( a, src[0], src[0], src[0]);
+ } else if (_unfilteredSurface->bytesPerPixel == 2) { // Grayscale + alpha
+ *((uint32 *)output->getBasePtr(j, i)) = format.ARGBToColor(src[1], src[0], src[0], src[0]);
+ } else if (_unfilteredSurface->bytesPerPixel == 3) { // RGB
+ if (_transparentColorSpecified) {
+ bool isTransparentColor = (src[0] == _transparentColor[0] &&
+ src[1] == _transparentColor[1] &&
+ src[2] == _transparentColor[2]);
+ a = isTransparentColor ? 0 : 0xFF;
+ }
+ *((uint32 *)output->getBasePtr(j, i)) = format.ARGBToColor( a, src[0], src[1], src[2]);
+ } else if (_unfilteredSurface->bytesPerPixel == 4) { // RGBA
+ *((uint32 *)output->getBasePtr(j, i)) = format.ARGBToColor(src[3], src[0], src[1], src[2]);
+ }
+ }
+
+ src += _unfilteredSurface->bytesPerPixel;
+ }
+ }
+ } else {
+ byte index, r, g, b;
+
+ // Convert the indexed surface to the target pixel format
+ for (uint16 i = 0; i < output->h; i++) {
+ for (uint16 j = 0; j < output->w; j++) {
+ index = *src;
+ r = _palette[index * 4 + 0];
+ g = _palette[index * 4 + 1];
+ b = _palette[index * 4 + 2];
+ a = _palette[index * 4 + 3];
+
+ if (format.bytesPerPixel == 2)
+ *((uint16 *)output->getBasePtr(j, i)) = format.ARGBToColor(a, r, g, b);
+ else
+ *((uint32 *)output->getBasePtr(j, i)) = format.ARGBToColor(a, r, g, b);
+
+ src++;
+ }
+ }
+ }
+
+ return output;
+}
+
+bool PNG::read(Common::SeekableReadStream *str) {
+ uint32 chunkLength = 0, chunkType = 0;
+ _stream = str;
+
+ // First, check the PNG signature
+ if (_stream->readUint32BE() != PNG_HEADER(0x89, 0x50, 0x4e, 0x47)) {
+ delete _stream;
+ return false;
+ }
+ if (_stream->readUint32BE() != PNG_HEADER(0x0d, 0x0a, 0x1a, 0x0a)) {
+ delete _stream;
+ return false;
+ }
+
+ // Start reading chunks till we reach an IEND chunk
+ while (chunkType != kChunkIEND) {
+ // The chunk length does not include the type or CRC bytes
+ chunkLength = _stream->readUint32BE();
+ chunkType = _stream->readUint32BE();
+
+ switch (chunkType) {
+ case kChunkIHDR:
+ readHeaderChunk();
+ break;
+ case kChunkIDAT:
+ if (_compressedBufferSize == 0) {
+ _compressedBufferSize += chunkLength;
+ _compressedBuffer = new byte[_compressedBufferSize];
+ _stream->read(_compressedBuffer, chunkLength);
+ } else {
+ // Expand the buffer
+ uint32 prevSize = _compressedBufferSize;
+ _compressedBufferSize += chunkLength;
+ byte *tmp = new byte[prevSize];
+ memcpy(tmp, _compressedBuffer, prevSize);
+ delete[] _compressedBuffer;
+ _compressedBuffer = new byte[_compressedBufferSize];
+ memcpy(_compressedBuffer, tmp, prevSize);
+ delete[] tmp;
+ _stream->read(_compressedBuffer + prevSize, chunkLength);
+ }
+ break;
+ case kChunkPLTE: // only available in indexed PNGs
+ if (_header.colorType != kIndexed)
+ error("A palette chunk has been found in a non-indexed PNG file");
+ if (chunkLength % 3 != 0)
+ error("Palette chunk not divisible by 3");
+ _paletteEntries = chunkLength / 3;
+ readPaletteChunk();
+ break;
+ case kChunkIEND:
+ // End of stream
+ break;
+ case kChunktRNS:
+ readTransparencyChunk(chunkLength);
+ break;
+ default:
+ // Skip the chunk content
+ _stream->skip(chunkLength);
+ break;
+ }
+
+ if (chunkType != kChunkIEND)
+ _stream->skip(4); // skip the chunk CRC checksum
+ }
+
+ // We no longer need the file stream, thus close it here
+ delete _stream;
+ _stream = 0;
+
+ // Unpack the compressed buffer
+ Common::MemoryReadStream *compData = new Common::MemoryReadStream(_compressedBuffer, _compressedBufferSize, DisposeAfterUse::YES);
+ _imageData = Common::wrapCompressedReadStream(compData);
+
+ // Construct the final image
+ constructImage();
+
+ // Close the uncompressed stream, which will also delete the memory stream,
+ // and thus the original compressed buffer
+ delete _imageData;
+
+ return true;
+}
+
+/**
+ * Paeth predictor, used by PNG filter type 4
+ * The parameters are of signed 16-bit integers, but should come
+ * from unsigned chars. The integers are only needed to make
+ * the paeth calculation correct.
+ *
+ * Taken from lodePNG, with a slight patch:
+ * http://www.atalasoft.com/cs/blogs/stevehawley/archive/2010/02/23/libpng-you-re-doing-it-wrong.aspx
+ */
+byte PNG::paethPredictor(int16 a, int16 b, int16 c) {
+ int16 pa = ABS<int16>(b - c);
+ int16 pb = ABS<int16>(a - c);
+ int16 pc = ABS<int16>(a + b - c - c);
+
+ if (pa <= MIN<int16>(pb, pc))
+ return (byte)a;
+ else if (pb <= pc)
+ return (byte)b;
+ else
+ return (byte)c;
+}
+
+/**
+ * Unfilters a filtered PNG scan line.
+ * PNG filters are defined in: http://www.w3.org/TR/PNG/#9Filters
+ * Note that filters are always applied to bytes
+ *
+ * Taken from lodePNG
+ */
+void PNG::unfilterScanLine(byte *dest, const byte *scanLine, const byte *prevLine, uint16 byteWidth, byte filterType, uint16 length) {
+ uint16 i;
+
+ switch (filterType) {
+ case kFilterNone: // no change
+ for (i = 0; i < length; i++)
+ dest[i] = scanLine[i];
+ break;
+ case kFilterSub: // add the bytes to the left
+ for (i = 0; i < byteWidth; i++)
+ dest[i] = scanLine[i];
+ for (i = byteWidth; i < length; i++)
+ dest[i] = scanLine[i] + dest[i - byteWidth];
+ break;
+ case kFilterUp: // add the bytes of the above scanline
+ if (prevLine) {
+ for (i = 0; i < length; i++)
+ dest[i] = scanLine[i] + prevLine[i];
+ } else {
+ for (i = 0; i < length; i++)
+ dest[i] = scanLine[i];
+ }
+ break;
+ case kFilterAverage: // average value of the left and top left
+ if (prevLine) {
+ for (i = 0; i < byteWidth; i++)
+ dest[i] = scanLine[i] + prevLine[i] / 2;
+ for (i = byteWidth; i < length; i++)
+ dest[i] = scanLine[i] + ((dest[i - byteWidth] + prevLine[i]) / 2);
+ } else {
+ for (i = 0; i < byteWidth; i++)
+ dest[i] = scanLine[i];
+ for (i = byteWidth; i < length; i++)
+ dest[i] = scanLine[i] + dest[i - byteWidth] / 2;
+ }
+ break;
+ case kFilterPaeth: // Paeth filter: http://www.w3.org/TR/PNG/#9Filter-type-4-Paeth
+ if (prevLine) {
+ for(i = 0; i < byteWidth; i++)
+ dest[i] = (scanLine[i] + prevLine[i]); // paethPredictor(0, prevLine[i], 0) is always prevLine[i]
+ for(i = byteWidth; i < length; i++)
+ dest[i] = (scanLine[i] + paethPredictor(dest[i - byteWidth], prevLine[i], prevLine[i - byteWidth]));
+ } else {
+ for(i = 0; i < byteWidth; i++)
+ dest[i] = scanLine[i];
+ for(i = byteWidth; i < length; i++)
+ dest[i] = (scanLine[i] + dest[i - byteWidth]); // paethPredictor(dest[i - byteWidth], 0, 0) is always dest[i - byteWidth]
+ }
+ break;
+ default:
+ error("Unknown line filter");
+ }
+
+}
+
+void PNG::constructImage() {
+ assert (_header.bitDepth != 0);
+
+ byte *dest;
+ byte *scanLine;
+ byte *prevLine = 0;
+ byte filterType;
+ uint16 scanLineWidth = (_header.width * getNumColorChannels() * _header.bitDepth + 7) / 8;
+
+ if (_unfilteredSurface) {
+ _unfilteredSurface->free();
+ delete _unfilteredSurface;
+ }
+ _unfilteredSurface = new Graphics::Surface();
+ _unfilteredSurface->create(_header.width, _header.height, (getNumColorChannels() * _header.bitDepth + 7) / 8);
+ scanLine = new byte[_unfilteredSurface->pitch];
+ dest = (byte *)_unfilteredSurface->getBasePtr(0, 0);
+
+ switch(_header.interlaceType) {
+ case kNonInterlaced:
+ for (uint16 y = 0; y < _unfilteredSurface->h; y++) {
+ filterType = _imageData->readByte();
+ _imageData->read(scanLine, scanLineWidth);
+ unfilterScanLine(dest, scanLine, prevLine, _unfilteredSurface->bytesPerPixel, filterType, scanLineWidth);
+ prevLine = dest;
+ dest += _unfilteredSurface->pitch;
+ }
+ break;
+ case kInterlaced:
+ // Theoretically, this shouldn't be needed, as interlacing is only
+ // useful for web images. Interlaced PNG images require more complex
+ // handling, so unless having support for such images is needed, there
+ // is no reason to add support for them.
+ error("TODO: Support for interlaced PNG images");
+ break;
+ }
+
+ delete[] scanLine;
+}
+
+void PNG::readHeaderChunk() {
+ _header.width = _stream->readUint32BE();
+ _header.height = _stream->readUint32BE();
+ _header.bitDepth = _stream->readByte();
+ if (_header.bitDepth > 8)
+ error("Only PNGs with a bit depth of 1-8 bits are supported (i.e. PNG24)");
+ _header.colorType = (PNGColorType)_stream->readByte();
+ _header.compressionMethod = _stream->readByte();
+ // Compression methods: http://www.w3.org/TR/PNG/#10Compression
+ // Only compression method 0 (deflate) is documented and supported
+ if (_header.compressionMethod != 0)
+ error("Unknown PNG compression method: %d", _header.compressionMethod);
+ _header.filterMethod = _stream->readByte();
+ // Filter methods: http://www.w3.org/TR/PNG/#9Filters
+ // Only filter method 0 is documented and supported
+ if (_header.filterMethod != 0)
+ error("Unknown PNG filter method: %d", _header.filterMethod);
+ _header.interlaceType = (PNGInterlaceType)_stream->readByte();
+}
+
+byte PNG::getNumColorChannels() {
+ switch (_header.colorType) {
+ case kGrayScale:
+ return 1; // Gray
+ case kTrueColor:
+ return 3; // RGB
+ case kIndexed:
+ return 1; // Indexed
+ case kGrayScaleWithAlpha:
+ return 2; // Gray + Alpha
+ case kTrueColorWithAlpha:
+ return 4; // RGBA
+ default:
+ error("Unknown color type");
+ }
+}
+
+void PNG::readPaletteChunk() {
+ for (byte i = 0; i < _paletteEntries; i++) {
+ _palette[i * 4 + 0] = _stream->readByte(); // R
+ _palette[i * 4 + 1] = _stream->readByte(); // G
+ _palette[i * 4 + 2] = _stream->readByte(); // B
+ _palette[i * 4 + 3] = 0xFF; // Alpha, set in the tRNS chunk
+ }
+}
+
+void PNG::readTransparencyChunk(uint32 chunkLength) {
+ _transparentColorSpecified = true;
+
+ switch(_header.colorType) {
+ case kGrayScale:
+ _transparentColor[0] = _stream->readUint16BE();
+ _transparentColor[1] = _transparentColor[0];
+ _transparentColor[2] = _transparentColor[0];
+ break;
+ case kTrueColor:
+ _transparentColor[0] = _stream->readUint16BE();
+ _transparentColor[1] = _stream->readUint16BE();
+ _transparentColor[2] = _stream->readUint16BE();
+ break;
+ case kIndexed:
+ for (uint32 i = 0; i < chunkLength; i++)
+ _palette[i * 4 + 3] = _stream->readByte();
+ // A transparency chunk may have less entries
+ // than the palette entries. The remaining ones
+ // are unmodified (set to 255). Check here:
+ // http://www.w3.org/TR/PNG/#11tRNS
+ break;
+ default:
+ error("Transparency chunk found in a PNG that has a separate transparency channel");
+ }
+}
+
+} // End of Graphics namespace
diff --git a/graphics/png.h b/graphics/png.h
new file mode 100644
index 0000000000..4de55d869b
--- /dev/null
+++ b/graphics/png.h
@@ -0,0 +1,162 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+/*
+ * PNG decoder used in engines:
+ * - sword25
+ */
+
+#ifndef GRAPHICS_PNG_H
+#define GRAPHICS_PNG_H
+
+#include "graphics/surface.h"
+
+// PNG decoder, based on the W3C specs:
+// http://www.w3.org/TR/PNG/
+// and lodePNG:
+// http://members.gamedev.net/lode/projects/LodePNG/
+// and the ysflight PNG decoder:
+// http://homepage3.nifty.com/ysflight/pngdecoder/pngdecodere.html
+
+// All the numbers are BE: http://www.w3.org/TR/PNG/#7Integers-and-byte-order
+
+// Note: At the moment, this decoder only supports non-interlaced images, and
+// does not support truecolor/grayscale images with 16bit depth.
+//
+// Theoretically, interlaced images shouldn't be needed for games, as
+// interlacing is only useful for images in websites.
+//
+// PNG images with 16bit depth (i.e. 48bit images) are quite rare, and can
+// theoretically contain more than 16.7 millions of colors (the so-called "deep
+// color" representation). In essence, each of the R, G, B and A components in
+// them is specified with 2 bytes, instead of 1. However, the PNG specification
+// always refers to color components with 1 byte each, so this part of the spec
+// is a bit unclear. For now, these won't be supported, until a suitable sample
+// is found.
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Graphics {
+
+enum PNGColorType {
+ kGrayScale = 0, // bit depths: 1, 2, 4, 8, 16
+ kTrueColor = 2, // bit depths: 8, 16
+ kIndexed = 3, // bit depths: 1, 2, 4, 8
+ kGrayScaleWithAlpha = 4, // bit depths: 8, 16
+ kTrueColorWithAlpha = 6 // bit depths: 8, 16
+};
+
+enum PNGInterlaceType {
+ kNonInterlaced = 0,
+ kInterlaced = 1
+};
+
+struct PNGHeader {
+ uint32 width;
+ uint32 height;
+ byte bitDepth;
+ PNGColorType colorType;
+ byte compressionMethod;
+ byte filterMethod;
+ PNGInterlaceType interlaceType;
+};
+
+struct PixelFormat;
+
+class PNG {
+public:
+ PNG();
+ ~PNG();
+
+ /**
+ * Reads a PNG image from the specified stream
+ */
+ bool read(Common::SeekableReadStream *str);
+
+ /**
+ * Returns the information obtained from the PNG header.
+ */
+ PNGHeader getHeader() const { return _header; }
+
+ /**
+ * Returns the PNG image, formatted for the specified pixel format.
+ */
+ Graphics::Surface *getSurface(const PixelFormat &format);
+
+ /**
+ * Returns the indexed PNG8 image. Used for PNGs with an indexed 256 color
+ * palette, when they're shown on an 8-bit color screen, as no translation
+ * is taking place.
+ */
+ Graphics::Surface *getIndexedSurface() {
+ if (_header.colorType != kIndexed)
+ error("Indexed surface requested for a non-indexed PNG");
+ return _unfilteredSurface;
+ }
+
+ /**
+ * Returns the palette of the specified PNG8 image
+ */
+ void getPalette(byte *palette, byte &entries) {
+ if (_header.colorType != kIndexed)
+ error("Palette requested for a non-indexed PNG");
+ palette = _palette;
+ entries = _paletteEntries;
+ }
+
+private:
+ void readHeaderChunk();
+ byte getNumColorChannels();
+
+ void readPaletteChunk();
+ void readTransparencyChunk(uint32 chunkLength);
+
+ void constructImage();
+ void unfilterScanLine(byte *dest, const byte *scanLine, const byte *prevLine, uint16 byteWidth, byte filterType, uint16 length);
+ byte paethPredictor(int16 a, int16 b, int16 c);
+
+ // The original file stream
+ Common::SeekableReadStream *_stream;
+ // The unzipped image data stream
+ Common::SeekableReadStream *_imageData;
+
+ PNGHeader _header;
+
+ byte _palette[256 * 4]; // RGBA
+ byte _paletteEntries;
+ uint16 _transparentColor[3];
+ bool _transparentColorSpecified;
+
+ byte *_compressedBuffer;
+ uint32 _compressedBufferSize;
+
+ Graphics::Surface *_unfilteredSurface;
+};
+
+} // End of Graphics namespace
+
+#endif // GRAPHICS_PNG_H