aboutsummaryrefslogtreecommitdiff
path: root/graphics/decoders
diff options
context:
space:
mode:
Diffstat (limited to 'graphics/decoders')
-rw-r--r--graphics/decoders/bmp.cpp159
-rw-r--r--graphics/decoders/bmp.h56
-rw-r--r--graphics/decoders/image_decoder.h85
-rw-r--r--graphics/decoders/jpeg.cpp781
-rw-r--r--graphics/decoders/jpeg.h133
-rw-r--r--graphics/decoders/pict.cpp567
-rw-r--r--graphics/decoders/pict.h130
-rw-r--r--graphics/decoders/png.cpp503
-rw-r--r--graphics/decoders/png.h135
9 files changed, 2549 insertions, 0 deletions
diff --git a/graphics/decoders/bmp.cpp b/graphics/decoders/bmp.cpp
new file mode 100644
index 0000000000..0d44881d7c
--- /dev/null
+++ b/graphics/decoders/bmp.cpp
@@ -0,0 +1,159 @@
+/* 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.
+ */
+
+#include "common/stream.h"
+#include "common/textconsole.h"
+
+#include "graphics/pixelformat.h"
+#include "graphics/surface.h"
+#include "graphics/decoders/bmp.h"
+
+namespace Graphics {
+
+BitmapDecoder::BitmapDecoder() {
+ _surface = 0;
+ _palette = 0;
+}
+
+BitmapDecoder::~BitmapDecoder() {
+ destroy();
+}
+
+void BitmapDecoder::destroy() {
+ if (_surface) {
+ _surface->free();
+ delete _surface; _surface = 0;
+ }
+
+ delete[] _palette; _palette = 0;
+}
+
+bool BitmapDecoder::loadStream(Common::SeekableReadStream &stream) {
+ destroy();
+
+ if (stream.readByte() != 'B')
+ return false;
+
+ if (stream.readByte() != 'M')
+ return false;
+
+ /* uint32 fileSize = */ stream.readUint32LE();
+ /* uint16 res1 = */ stream.readUint16LE();
+ /* uint16 res2 = */ stream.readUint16LE();
+ uint32 imageOffset = stream.readUint32LE();
+
+ uint32 infoSize = stream.readUint32LE();
+ if (infoSize != 40) {
+ warning("Only Windows v3 bitmaps are supported");
+ return false;
+ }
+
+ uint32 width = stream.readUint32LE();
+ int32 height = stream.readSint32LE();
+
+ if (width == 0 || height == 0)
+ return false;
+
+ if (height < 0) {
+ warning("Right-side up bitmaps not supported");
+ return false;
+ }
+
+ /* uint16 planes = */ stream.readUint16LE();
+ uint16 bitsPerPixel = stream.readUint16LE();
+
+ if (bitsPerPixel != 8 && bitsPerPixel != 24) {
+ warning("%dbpp bitmaps not supported", bitsPerPixel);
+ return false;
+ }
+
+ uint32 compression = stream.readUint32LE();
+
+ if (compression != 0) {
+ warning("Compressed bitmaps not supported");
+ return false;
+ }
+
+ /* uint32 imageSize = */ stream.readUint32LE();
+ /* uint32 pixelsPerMeterX = */ stream.readUint32LE();
+ /* uint32 pixelsPerMeterY = */ stream.readUint32LE();
+ uint32 colorsUsed = stream.readUint32LE();
+ /* uint32 colorsImportant = */ stream.readUint32LE();
+
+ if (colorsUsed == 0)
+ colorsUsed = 256;
+
+ if (bitsPerPixel == 8) {
+ // Read the palette
+ _palette = new byte[colorsUsed * 3];
+ for (uint16 i = 0; i < colorsUsed; i++) {
+ _palette[i * 3 + 2] = stream.readByte();
+ _palette[i * 3 + 1] = stream.readByte();
+ _palette[i * 3 + 0] = stream.readByte();
+ stream.readByte();
+ }
+ }
+
+ // Start us at the beginning of the image
+ stream.seek(imageOffset);
+
+ Graphics::PixelFormat format = Graphics::PixelFormat::createFormatCLUT8();
+
+ // BGRA for 24bpp
+ if (bitsPerPixel == 24)
+ format = Graphics::PixelFormat(4, 8, 8, 8, 8, 8, 16, 24, 0);
+
+ _surface = new Graphics::Surface();
+ _surface->create(width, height, format);
+
+ int srcPitch = width * (bitsPerPixel >> 3);
+ const int extraDataLength = (srcPitch % 4) ? 4 - (srcPitch % 4) : 0;
+
+ if (bitsPerPixel == 8) {
+ byte *dst = (byte *)_surface->pixels;
+
+ for (int32 i = 0; i < height; i++) {
+ stream.read(dst + (height - i - 1) * width, width);
+ stream.skip(extraDataLength);
+ }
+ } else {
+ byte *dst = (byte *)_surface->pixels + (height - 1) * _surface->pitch;
+
+ for (int32 i = 0; i < height; i++) {
+ for (uint32 j = 0; j < width; j++) {
+ byte b = stream.readByte();
+ byte g = stream.readByte();
+ byte r = stream.readByte();
+ uint32 color = format.RGBToColor(r, g, b);
+
+ *((uint32 *)dst) = color;
+ dst += format.bytesPerPixel;
+ }
+
+ stream.skip(extraDataLength);
+ dst -= _surface->pitch * 2;
+ }
+ }
+
+ return true;
+}
+
+} // End of namespace Graphics
diff --git a/graphics/decoders/bmp.h b/graphics/decoders/bmp.h
new file mode 100644
index 0000000000..e11b12fad6
--- /dev/null
+++ b/graphics/decoders/bmp.h
@@ -0,0 +1,56 @@
+/* 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.
+ */
+
+#ifndef GRAPHICS_DECODERS_BMP_H
+#define GRAPHICS_DECODERS_BMP_H
+
+#include "common/scummsys.h"
+#include "common/str.h"
+#include "graphics/decoders/image_decoder.h"
+
+namespace Common{
+class SeekableReadStream;
+}
+
+namespace Graphics {
+
+struct PixelFormat;
+struct Surface;
+
+class BitmapDecoder : public ImageDecoder {
+public:
+ BitmapDecoder();
+ virtual ~BitmapDecoder();
+
+ // ImageDecoder API
+ void destroy();
+ virtual bool loadStream(Common::SeekableReadStream &stream);
+ virtual const Surface *getSurface() const { return _surface; }
+ virtual const byte *getPalette() { return _palette; }
+
+private:
+ Surface *_surface;
+ byte *_palette;
+};
+
+} // End of namespace Graphics
+
+#endif
diff --git a/graphics/decoders/image_decoder.h b/graphics/decoders/image_decoder.h
new file mode 100644
index 0000000000..e768f7f9a2
--- /dev/null
+++ b/graphics/decoders/image_decoder.h
@@ -0,0 +1,85 @@
+/* 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.
+ */
+
+#ifndef GRAPHICS_DECODERS_IMAGEDECODER_H
+#define GRAPHICS_DECODERS_IMAGEDECODER_H
+
+#include "common/scummsys.h"
+#include "common/str.h"
+
+namespace Common{
+class SeekableReadStream;
+}
+
+namespace Graphics {
+
+struct PixelFormat;
+struct Surface;
+
+/**
+ * A representation of an image decoder that maintains ownership of the surface
+ * and palette it decodes to.
+ */
+class ImageDecoder {
+public:
+ virtual ~ImageDecoder() {}
+
+ /**
+ * Load an image from the specified stream
+ *
+ * @param stream the input stream
+ * @return whether loading the file succeeded
+ * @see getSurface
+ * @see getPalette
+ */
+ virtual bool loadStream(Common::SeekableReadStream &stream) = 0;
+
+ /**
+ * Destroy this decoder's surface and palette
+ */
+ virtual void destroy() = 0;
+
+ /**
+ * Get the decoded surface
+ *
+ * This surface is owned by this ImageDecoder and will remain valid
+ * until destroy() or loadStream() is called, or until this ImageDecoder's
+ * destructor is called.
+ *
+ * @return the decoded surface, or 0 if no surface is present
+ */
+ virtual const Surface *getSurface() const = 0;
+
+ /**
+ * Get the decoded palette
+ *
+ * This palette is owned by this ImageDecoder and will remain valid
+ * until destroy() or loadStream() is called, or until this ImageDecoder's
+ * destructor is called.
+ *
+ * @return the decoded palette, or 0 if no palette is present
+ */
+ virtual const byte *getPalette() const { return 0; }
+};
+
+} // End of namespace Graphics
+
+#endif
diff --git a/graphics/decoders/jpeg.cpp b/graphics/decoders/jpeg.cpp
new file mode 100644
index 0000000000..a871377ca1
--- /dev/null
+++ b/graphics/decoders/jpeg.cpp
@@ -0,0 +1,781 @@
+/* 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.
+ *
+ */
+
+#include "graphics/pixelformat.h"
+#include "graphics/yuv_to_rgb.h"
+#include "graphics/decoders/jpeg.h"
+
+#include "common/debug.h"
+#include "common/endian.h"
+#include "common/stream.h"
+#include "common/textconsole.h"
+
+namespace Graphics {
+
+// Order used to traverse the quantization tables
+static const uint8 _zigZagOrder[64] = {
+ 0, 1, 8, 16, 9, 2, 3, 10,
+ 17, 24, 32, 25, 18, 11, 4, 5,
+ 12, 19, 26, 33, 40, 48, 41, 34,
+ 27, 20, 13, 6, 7, 14, 21, 28,
+ 35, 42, 49, 56, 57, 50, 43, 36,
+ 29, 22, 15, 23, 30, 37, 44, 51,
+ 58, 59, 52, 45, 38, 31, 39, 46,
+ 53, 60, 61, 54, 47, 55, 62, 63
+};
+
+JPEGDecoder::JPEGDecoder() : ImageDecoder(),
+ _stream(NULL), _w(0), _h(0), _numComp(0), _components(NULL), _numScanComp(0),
+ _scanComp(NULL), _currentComp(NULL), _rgbSurface(0) {
+
+ // Initialize the quantization tables
+ for (int i = 0; i < JPEG_MAX_QUANT_TABLES; i++)
+ _quant[i] = NULL;
+
+ // Initialize the Huffman tables
+ for (int i = 0; i < 2 * JPEG_MAX_HUFF_TABLES; i++) {
+ _huff[i].count = 0;
+ _huff[i].values = NULL;
+ _huff[i].sizes = NULL;
+ _huff[i].codes = NULL;
+ }
+}
+
+JPEGDecoder::~JPEGDecoder() {
+ destroy();
+}
+
+const Surface *JPEGDecoder::getSurface() const {
+ // Make sure we have loaded data
+ if (!isLoaded())
+ return 0;
+
+ if (_rgbSurface)
+ return _rgbSurface;
+
+ // Create an RGBA8888 surface
+ _rgbSurface = new Graphics::Surface();
+ _rgbSurface->create(_w, _h, Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0));
+
+ // Get our component surfaces
+ const Graphics::Surface *yComponent = getComponent(1);
+ const Graphics::Surface *uComponent = getComponent(2);
+ const Graphics::Surface *vComponent = getComponent(3);
+
+ convertYUV444ToRGB(_rgbSurface, (byte *)yComponent->pixels, (byte *)uComponent->pixels, (byte *)vComponent->pixels, yComponent->w, yComponent->h, yComponent->pitch, uComponent->pitch);
+
+ return _rgbSurface;
+}
+
+void JPEGDecoder::destroy() {
+ // Reset member variables
+ _stream = NULL;
+ _w = _h = 0;
+ _restartInterval = 0;
+
+ // Free the components
+ for (int c = 0; c < _numComp; c++)
+ _components[c].surface.free();
+ delete[] _components; _components = NULL;
+ _numComp = 0;
+
+ // Free the scan components
+ delete[] _scanComp; _scanComp = NULL;
+ _numScanComp = 0;
+ _currentComp = NULL;
+
+ // Free the quantization tables
+ for (int i = 0; i < JPEG_MAX_QUANT_TABLES; i++) {
+ delete[] _quant[i];
+ _quant[i] = NULL;
+ }
+
+ // Free the Huffman tables
+ for (int i = 0; i < 2 * JPEG_MAX_HUFF_TABLES; i++) {
+ _huff[i].count = 0;
+ delete[] _huff[i].values; _huff[i].values = NULL;
+ delete[] _huff[i].sizes; _huff[i].sizes = NULL;
+ delete[] _huff[i].codes; _huff[i].codes = NULL;
+ }
+
+ if (_rgbSurface) {
+ _rgbSurface->free();
+ delete _rgbSurface;
+ }
+}
+
+bool JPEGDecoder::loadStream(Common::SeekableReadStream &stream) {
+ // Reset member variables and tables from previous reads
+ destroy();
+
+ // Save the input stream
+ _stream = &stream;
+
+ bool ok = true;
+ bool done = false;
+ while (!_stream->eos() && ok && !done) {
+ // Read the marker
+
+ // WORKAROUND: While each and every JPEG file should end with
+ // an EOI (end of image) tag, in reality this may not be the
+ // case. For instance, at least one image in the Masterpiece
+ // edition of Myst doesn't, yet other programs are able to read
+ // the image without complaining.
+ //
+ // Apparently, the customary workaround is to insert a fake
+ // EOI tag.
+
+ uint16 marker = _stream->readByte();
+ bool fakeEOI = false;
+
+ if (_stream->eos()) {
+ fakeEOI = true;
+ marker = 0xFF;
+ }
+
+ if (marker != 0xFF) {
+ error("JPEG: Invalid marker[0]: 0x%02X", marker);
+ ok = false;
+ break;
+ }
+
+ while (marker == 0xFF && !_stream->eos())
+ marker = _stream->readByte();
+
+ if (_stream->eos()) {
+ fakeEOI = true;
+ marker = 0xD9;
+ }
+
+ if (fakeEOI)
+ warning("JPEG: Inserted fake EOI");
+
+ // Process the marker data
+ switch (marker) {
+ case 0xC0: // Start Of Frame
+ ok = readSOF0();
+ break;
+ case 0xC4: // Define Huffman Tables
+ ok = readDHT();
+ break;
+ case 0xD8: // Start Of Image
+ break;
+ case 0xD9: // End Of Image
+ done = true;
+ break;
+ case 0xDA: // Start Of Scan
+ ok = readSOS();
+ break;
+ case 0xDB: // Define Quantization Tables
+ ok = readDQT();
+ break;
+ case 0xE0: // JFIF/JFXX segment
+ ok = readJFIF();
+ break;
+ case 0xDD: // Define Restart Interval
+ ok = readDRI();
+ break;
+ case 0xFE: // Comment
+ _stream->seek(_stream->readUint16BE() - 2, SEEK_CUR);
+ break;
+ default: { // Unknown marker
+ uint16 size = _stream->readUint16BE();
+
+ if ((marker & 0xE0) != 0xE0)
+ warning("JPEG: Unknown marker %02X, skipping %d bytes", marker, size - 2);
+
+ _stream->seek(size - 2, SEEK_CUR);
+ }
+ }
+ }
+
+ _stream = 0;
+ return ok;
+}
+
+bool JPEGDecoder::readJFIF() {
+ uint16 length = _stream->readUint16BE();
+ uint32 tag = _stream->readUint32BE();
+ if (tag != MKTAG('J', 'F', 'I', 'F')) {
+ warning("JPEGDecoder::readJFIF() tag mismatch");
+ return false;
+ }
+ if (_stream->readByte() != 0) { // NULL
+ warning("JPEGDecoder::readJFIF() NULL mismatch");
+ return false;
+ }
+ byte majorVersion = _stream->readByte();
+ byte minorVersion = _stream->readByte();
+ if (majorVersion != 1 || minorVersion != 1)
+ warning("JPEGDecoder::readJFIF() Non-v1.1 JPEGs may not be handled correctly");
+ /* byte densityUnits = */_stream->readByte();
+ /* uint16 xDensity = */_stream->readUint16BE();
+ /* uint16 yDensity = */_stream->readUint16BE();
+ byte thumbW = _stream->readByte();
+ byte thumbH = _stream->readByte();
+ _stream->seek(thumbW * thumbH * 3, SEEK_CUR); // Ignore thumbnail
+ if (length != (thumbW * thumbH * 3) + 16) {
+ warning("JPEGDecoder::readJFIF() length mismatch");
+ return false;
+ }
+ return true;
+}
+
+// Marker 0xC0 (Start Of Frame, Baseline DCT)
+bool JPEGDecoder::readSOF0() {
+ debug(5, "JPEG: readSOF0");
+ uint16 size = _stream->readUint16BE();
+
+ // Read the sample precision
+ uint8 precision = _stream->readByte();
+ if (precision != 8) {
+ warning("JPEG: Just 8 bit precision supported at the moment");
+ return false;
+ }
+
+ // Image size
+ _h = _stream->readUint16BE();
+ _w = _stream->readUint16BE();
+
+ // Number of components
+ _numComp = _stream->readByte();
+ if (size != 8 + 3 * _numComp) {
+ warning("JPEG: Invalid number of components");
+ return false;
+ }
+
+ // Allocate the new components
+ delete[] _components;
+ _components = new Component[_numComp];
+
+ // Read the components details
+ for (int c = 0; c < _numComp; c++) {
+ _components[c].id = _stream->readByte();
+ _components[c].factorH = _stream->readByte();
+ _components[c].factorV = _components[c].factorH & 0xF;
+ _components[c].factorH >>= 4;
+ _components[c].quantTableSelector = _stream->readByte();
+ }
+
+ return true;
+}
+
+// Marker 0xC4 (Define Huffman Tables)
+bool JPEGDecoder::readDHT() {
+ debug(5, "JPEG: readDHT");
+ uint16 size = _stream->readUint16BE() - 2;
+ uint32 pos = _stream->pos();
+
+ while ((uint32)_stream->pos() < (size + pos)) {
+ // Read the table type and id
+ uint8 tableId = _stream->readByte();
+ uint8 tableType = tableId >> 4; // type 0: DC, 1: AC
+ tableId &= 0xF;
+ uint8 tableNum = (tableId << 1) + tableType;
+
+ // Free the Huffman table
+ delete[] _huff[tableNum].values; _huff[tableNum].values = NULL;
+ delete[] _huff[tableNum].sizes; _huff[tableNum].sizes = NULL;
+ delete[] _huff[tableNum].codes; _huff[tableNum].codes = NULL;
+
+ // Read the number of values for each length
+ uint8 numValues[16];
+ _huff[tableNum].count = 0;
+ for (int len = 0; len < 16; len++) {
+ numValues[len] = _stream->readByte();
+ _huff[tableNum].count += numValues[len];
+ }
+
+ // Allocate memory for the current table
+ _huff[tableNum].values = new uint8[_huff[tableNum].count];
+ _huff[tableNum].sizes = new uint8[_huff[tableNum].count];
+ _huff[tableNum].codes = new uint16[_huff[tableNum].count];
+
+ // Read the table contents
+ int cur = 0;
+ for (int len = 0; len < 16; len++) {
+ for (int i = 0; i < numValues[len]; i++) {
+ _huff[tableNum].values[cur] = _stream->readByte();
+ _huff[tableNum].sizes[cur] = len + 1;
+ cur++;
+ }
+ }
+
+ // Fill the table of Huffman codes
+ cur = 0;
+ uint16 curCode = 0;
+ uint8 curCodeSize = _huff[tableNum].sizes[0];
+ while (cur < _huff[tableNum].count) {
+ // Increase the code size to fit the request
+ while (_huff[tableNum].sizes[cur] != curCodeSize) {
+ curCode <<= 1;
+ curCodeSize++;
+ }
+
+ // Assign the current code
+ _huff[tableNum].codes[cur] = curCode;
+ curCode++;
+ cur++;
+ }
+ }
+
+ return true;
+}
+
+// Marker 0xDA (Start Of Scan)
+bool JPEGDecoder::readSOS() {
+ debug(5, "JPEG: readSOS");
+ uint16 size = _stream->readUint16BE();
+
+ // Number of scan components
+ _numScanComp = _stream->readByte();
+ if (size != 6 + 2 * _numScanComp) {
+ warning("JPEG: Invalid number of components");
+ return false;
+ }
+
+ // Allocate the new scan components
+ delete[] _scanComp;
+ _scanComp = new Component *[_numScanComp];
+
+ // Reset the maximum sampling factors
+ _maxFactorV = 0;
+ _maxFactorH = 0;
+
+ // Component-specification parameters
+ for (int c = 0; c < _numScanComp; c++) {
+ // Read the desired component id
+ uint8 id = _stream->readByte();
+
+ // Search the component with the specified id
+ bool found = false;
+ for (int i = 0; !found && i < _numComp; i++) {
+ if (_components[i].id == id) {
+ // We found the desired component
+ found = true;
+
+ // Assign the found component to the c'th scan component
+ _scanComp[c] = &_components[i];
+ }
+ }
+
+ if (!found) {
+ warning("JPEG: Invalid component");
+ return false;
+ }
+
+ // Read the entropy table selectors
+ _scanComp[c]->DCentropyTableSelector = _stream->readByte();
+ _scanComp[c]->ACentropyTableSelector = _scanComp[c]->DCentropyTableSelector & 0xF;
+ _scanComp[c]->DCentropyTableSelector >>= 4;
+
+ // Calculate the maximum sampling factors
+ if (_scanComp[c]->factorV > _maxFactorV)
+ _maxFactorV = _scanComp[c]->factorV;
+
+ if (_scanComp[c]->factorH > _maxFactorH)
+ _maxFactorH = _scanComp[c]->factorH;
+
+ // Initialize the DC predictor
+ _scanComp[c]->DCpredictor = 0;
+ }
+
+ // Start of spectral selection
+ if (_stream->readByte() != 0) {
+ warning("JPEG: Progressive scanning not supported");
+ return false;
+ }
+
+ // End of spectral selection
+ if (_stream->readByte() != 63) {
+ warning("JPEG: Progressive scanning not supported");
+ return false;
+ }
+
+ // Successive approximation parameters
+ if (_stream->readByte() != 0) {
+ warning("JPEG: Progressive scanning not supported");
+ return false;
+ }
+
+ // Entropy coded sequence starts, initialize Huffman decoder
+ _bitsNumber = 0;
+
+ // Read all the scan MCUs
+ uint16 xMCU = _w / (_maxFactorH * 8);
+ uint16 yMCU = _h / (_maxFactorV * 8);
+
+ // Check for non- multiple-of-8 dimensions
+ if (_w % (_maxFactorH * 8) != 0)
+ xMCU++;
+ if (_h % (_maxFactorV * 8) != 0)
+ yMCU++;
+
+ // Initialize the scan surfaces
+ for (uint16 c = 0; c < _numScanComp; c++) {
+ _scanComp[c]->surface.create(xMCU * _maxFactorH * 8, yMCU * _maxFactorV * 8, PixelFormat::createFormatCLUT8());
+ }
+
+ bool ok = true;
+ uint16 interval = _restartInterval;
+
+ for (int y = 0; ok && (y < yMCU); y++) {
+ for (int x = 0; ok && (x < xMCU); x++) {
+ ok = readMCU(x, y);
+
+ // If we have a restart interval, we'll need to reset a couple
+ // variables
+ if (_restartInterval != 0) {
+ interval--;
+
+ if (interval == 0) {
+ interval = _restartInterval;
+ _bitsNumber = 0;
+
+ for (byte i = 0; i < _numScanComp; i++)
+ _scanComp[i]->DCpredictor = 0;
+ }
+ }
+ }
+ }
+
+ // Trim Component surfaces back to image height and width
+ // Note: Code using jpeg must use surface.pitch correctly...
+ for (uint16 c = 0; c < _numScanComp; c++) {
+ _scanComp[c]->surface.w = _w;
+ _scanComp[c]->surface.h = _h;
+ }
+
+ return ok;
+}
+
+// Marker 0xDB (Define Quantization Tables)
+bool JPEGDecoder::readDQT() {
+ debug(5, "JPEG: readDQT");
+ uint16 size = _stream->readUint16BE() - 2;
+ uint32 pos = _stream->pos();
+
+ while ((uint32)_stream->pos() < (pos + size)) {
+ // Read the table precision and id
+ uint8 tableId = _stream->readByte();
+ bool highPrecision = (tableId & 0xF0) != 0;
+
+ // Validate the table id
+ tableId &= 0xF;
+ if (tableId > JPEG_MAX_QUANT_TABLES) {
+ warning("JPEG: Invalid number of components");
+ return false;
+ }
+
+ // Create the new table if necessary
+ if (!_quant[tableId])
+ _quant[tableId] = new uint16[64];
+
+ // Read the table (stored in Zig-Zag order)
+ for (int i = 0; i < 64; i++)
+ _quant[tableId][i] = highPrecision ? _stream->readUint16BE() : _stream->readByte();
+ }
+
+ return true;
+}
+
+// Marker 0xDD (Define Restart Interval)
+bool JPEGDecoder::readDRI() {
+ debug(5, "JPEG: readDRI");
+ uint16 size = _stream->readUint16BE() - 2;
+
+ if (size != 2) {
+ warning("JPEG: Invalid DRI size %d", size);
+ return false;
+ }
+
+ _restartInterval = _stream->readUint16BE();
+ debug(5, "Restart interval: %d", _restartInterval);
+ return true;
+}
+
+bool JPEGDecoder::readMCU(uint16 xMCU, uint16 yMCU) {
+ bool ok = true;
+ for (int c = 0; ok && (c < _numComp); c++) {
+ // Set the current component
+ _currentComp = _scanComp[c];
+
+ // Read the data units of the current component
+ for (int y = 0; ok && (y < _scanComp[c]->factorV); y++)
+ for (int x = 0; ok && (x < _scanComp[c]->factorH); x++)
+ ok = readDataUnit(xMCU * _scanComp[c]->factorH + x, yMCU * _scanComp[c]->factorV + y);
+ }
+
+ return ok;
+}
+
+// triple-butterfly-add (and possible rounding)
+#define xadd3(xa, xb, xc, xd, h) \
+ p = xa + xb; \
+ n = xa - xb; \
+ xa = p + xc + h; \
+ xb = n + xd + h; \
+ xc = p - xc + h; \
+ xd = n - xd + h;
+
+// butterfly-mul
+#define xmul(xa, xb, k1, k2, sh) \
+ n = k1 * (xa + xb); \
+ p = xa; \
+ xa = (n + (k2 - k1) * xb) >> sh; \
+ xb = (n - (k2 + k1) * p) >> sh;
+
+// IDCT based on public domain code from http://halicery.com/jpeg/idct.html
+void JPEGDecoder::idct1D8x8(int32 src[8], int32 dest[64], int32 ps, int32 half) {
+ int p, n;
+
+ src[0] <<= 9;
+ src[1] <<= 7;
+ src[3] *= 181;
+ src[4] <<= 9;
+ src[5] *= 181;
+ src[7] <<= 7;
+
+ // Even part
+ xmul(src[6], src[2], 277, 669, 0)
+ xadd3(src[0], src[4], src[6], src[2], half)
+
+ // Odd part
+ xadd3(src[1], src[7], src[3], src[5], 0)
+ xmul(src[5], src[3], 251, 50, 6)
+ xmul(src[1], src[7], 213, 142, 6)
+
+ dest[0 * 8] = (src[0] + src[1]) >> ps;
+ dest[1 * 8] = (src[4] + src[5]) >> ps;
+ dest[2 * 8] = (src[2] + src[3]) >> ps;
+ dest[3 * 8] = (src[6] + src[7]) >> ps;
+ dest[4 * 8] = (src[6] - src[7]) >> ps;
+ dest[5 * 8] = (src[2] - src[3]) >> ps;
+ dest[6 * 8] = (src[4] - src[5]) >> ps;
+ dest[7 * 8] = (src[0] - src[1]) >> ps;
+}
+
+void JPEGDecoder::idct2D8x8(int32 block[64]) {
+ int32 tmp[64];
+
+ // Apply 1D IDCT to rows
+ for (int i = 0; i < 8; i++)
+ idct1D8x8(&block[i * 8], &tmp[i], 9, 1 << 8);
+
+ // Apply 1D IDCT to columns
+ for (int i = 0; i < 8; i++)
+ idct1D8x8(&tmp[i * 8], &block[i], 12, 1 << 11);
+ }
+
+bool JPEGDecoder::readDataUnit(uint16 x, uint16 y) {
+ // Prepare an empty data array
+ int16 readData[64];
+ for (int i = 1; i < 64; i++)
+ readData[i] = 0;
+
+ // Read the DC component
+ readData[0] = _currentComp->DCpredictor + readDC();
+ _currentComp->DCpredictor = readData[0];
+
+ // Read the AC components (stored in Zig-Zag)
+ readAC(readData);
+
+ // Calculate the DCT coefficients from the input sequence
+ int32 block[64];
+ for (uint8 i = 0; i < 64; i++) {
+ // Dequantize
+ int32 val = readData[i];
+ int16 quant = _quant[_currentComp->quantTableSelector][i];
+ val *= quant;
+
+ // Store the normalized coefficients, undoing the Zig-Zag
+ block[_zigZagOrder[i]] = val;
+ }
+
+ // Apply the IDCT
+ idct2D8x8(block);
+
+ // Level shift to make the values unsigned
+ for (int i = 0; i < 64; i++) {
+ block[i] = block[i] + 128;
+
+ if (block[i] < 0)
+ block[i] = 0;
+
+ if (block[i] > 255)
+ block[i] = 255;
+ }
+
+ // Paint the component surface
+ uint8 scalingV = _maxFactorV / _currentComp->factorV;
+ uint8 scalingH = _maxFactorH / _currentComp->factorH;
+
+ // Convert coordinates from MCU blocks to pixels
+ x <<= 3;
+ y <<= 3;
+
+ for (uint8 j = 0; j < 8; j++) {
+ for (uint16 sV = 0; sV < scalingV; sV++) {
+ // Get the beginning of the block line
+ byte *ptr = (byte *)_currentComp->surface.getBasePtr(x * scalingH, (y + j) * scalingV + sV);
+
+ for (uint8 i = 0; i < 8; i++) {
+ for (uint16 sH = 0; sH < scalingH; sH++) {
+ *ptr = (byte)(block[j * 8 + i]);
+ ptr++;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+int16 JPEGDecoder::readDC() {
+ // DC is type 0
+ uint8 tableNum = _currentComp->DCentropyTableSelector << 1;
+
+ // Get the number of bits to read
+ uint8 numBits = readHuff(tableNum);
+
+ // Read the requested bits
+ return readSignedBits(numBits);
+}
+
+void JPEGDecoder::readAC(int16 *out) {
+ // AC is type 1
+ uint8 tableNum = (_currentComp->ACentropyTableSelector << 1) + 1;
+
+ // Start reading AC element 1
+ uint8 cur = 1;
+ while (cur < 64) {
+ uint8 s = readHuff(tableNum);
+ uint8 r = s >> 4;
+ s &= 0xF;
+
+ if (s == 0) {
+ if (r == 15) {
+ // Skip 16 values
+ cur += 16;
+ } else {
+ // EOB: end of block
+ cur = 64;
+ }
+ } else {
+ // Skip r values
+ cur += r;
+
+ // Read the next value
+ out[cur] = readSignedBits(s);
+ cur++;
+ }
+ }
+}
+
+int16 JPEGDecoder::readSignedBits(uint8 numBits) {
+ uint16 ret = 0;
+ if (numBits > 16)
+ error("requested %d bits", numBits); //XXX
+
+ // MSB=0 for negatives, 1 for positives
+ for (int i = 0; i < numBits; i++)
+ ret = (ret << 1) + readBit();
+
+ // Extend sign bits (PAG109)
+ if (!(ret >> (numBits - 1))) {
+ uint16 tmp = ((uint16)-1 << numBits) + 1;
+ ret = ret + tmp;
+ }
+ return ret;
+}
+
+// TODO: optimize?
+uint8 JPEGDecoder::readHuff(uint8 table) {
+ bool foundCode = false;
+ uint8 val = 0;
+
+ uint8 cur = 0;
+ uint8 codeSize = 1;
+ uint16 code = readBit();
+ while (!foundCode) {
+ // Prepare a code of the current size
+ while (codeSize < _huff[table].sizes[cur]) {
+ code = (code << 1) + readBit();
+ codeSize++;
+ }
+
+ // Compare the codes of the current size
+ while (!foundCode && (codeSize == _huff[table].sizes[cur])) {
+ if (code == _huff[table].codes[cur]) {
+ // Found the code
+ val = _huff[table].values[cur];
+ foundCode = true;
+ } else {
+ // Continue reading
+ cur++;
+ }
+ }
+ }
+
+ return val;
+}
+
+uint8 JPEGDecoder::readBit() {
+ // Read a whole byte if necessary
+ if (_bitsNumber == 0) {
+ _bitsData = _stream->readByte();
+ _bitsNumber = 8;
+
+ // Detect markers
+ if (_bitsData == 0xFF) {
+ uint8 byte2 = _stream->readByte();
+
+ // A stuffed 0 validates the previous byte
+ if (byte2 != 0) {
+ if (byte2 == 0xDC) {
+ // DNL marker: Define Number of Lines
+ // TODO: terminate scan
+ warning("DNL marker detected: terminate scan");
+ } else if (byte2 >= 0xD0 && byte2 <= 0xD7) {
+ debug(7, "RST%d marker detected", byte2 & 7);
+ _bitsData = _stream->readByte();
+ } else {
+ warning("Error: marker 0x%02X read in entropy data", byte2);
+ }
+ }
+ }
+ }
+ _bitsNumber--;
+
+ return (_bitsData & (1 << _bitsNumber)) ? 1 : 0;
+}
+
+const Surface *JPEGDecoder::getComponent(uint c) const {
+ for (int i = 0; i < _numComp; i++)
+ if (_components[i].id == c) // We found the desired component
+ return &_components[i].surface;
+
+ error("JPEGDecoder::getComponent: No component %d present", c);
+ return NULL;
+}
+
+} // End of Graphics namespace
diff --git a/graphics/decoders/jpeg.h b/graphics/decoders/jpeg.h
new file mode 100644
index 0000000000..c566d5ad21
--- /dev/null
+++ b/graphics/decoders/jpeg.h
@@ -0,0 +1,133 @@
+/* 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.
+ *
+ */
+
+#ifndef GRAPHICS_JPEG_H
+#define GRAPHICS_JPEG_H
+
+#include "graphics/surface.h"
+#include "graphics/decoders/image_decoder.h"
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Graphics {
+
+struct PixelFormat;
+
+#define JPEG_MAX_QUANT_TABLES 4
+#define JPEG_MAX_HUFF_TABLES 2
+
+class JPEGDecoder : public ImageDecoder {
+public:
+ JPEGDecoder();
+ ~JPEGDecoder();
+
+ // ImageDecoder API
+ void destroy();
+ bool loadStream(Common::SeekableReadStream &str);
+ const Surface *getSurface() const;
+
+ bool isLoaded() const { return _numComp && _w && _h; }
+ uint16 getWidth() const { return _w; }
+ uint16 getHeight() const { return _h; }
+ const Surface *getComponent(uint c) const;
+
+private:
+ Common::SeekableReadStream *_stream;
+ uint16 _w, _h;
+ uint16 _restartInterval;
+
+ // mutable so that we can convert to RGB only during
+ // a getSurface() call while still upholding the
+ // const requirement in other ImageDecoders
+ mutable Graphics::Surface *_rgbSurface;
+
+ // Image components
+ uint8 _numComp;
+ struct Component {
+ // Global values
+ uint8 id;
+ uint8 factorH;
+ uint8 factorV;
+ uint8 quantTableSelector;
+
+ // Scan specific values
+ uint8 DCentropyTableSelector;
+ uint8 ACentropyTableSelector;
+ int16 DCpredictor;
+
+ // Result image for this component
+ Surface surface;
+ };
+
+ Component *_components;
+
+ // Scan components
+ uint8 _numScanComp;
+ Component **_scanComp;
+ Component *_currentComp;
+
+ // Maximum sampling factors, used to calculate the interleaving of the MCU
+ uint8 _maxFactorV;
+ uint8 _maxFactorH;
+
+ // Quantization tables
+ uint16 *_quant[JPEG_MAX_QUANT_TABLES];
+
+ // Huffman tables
+ struct HuffmanTable {
+ uint8 count;
+ uint8 *values;
+ uint8 *sizes;
+ uint16 *codes;
+ } _huff[2 * JPEG_MAX_HUFF_TABLES];
+
+ // Marker read functions
+ bool readJFIF();
+ bool readSOF0();
+ bool readDHT();
+ bool readSOS();
+ bool readDQT();
+ bool readDRI();
+
+ // Helper functions
+ bool readMCU(uint16 xMCU, uint16 yMCU);
+ bool readDataUnit(uint16 x, uint16 y);
+ int16 readDC();
+ void readAC(int16 *out);
+ int16 readSignedBits(uint8 numBits);
+
+ // Huffman decoding
+ uint8 readHuff(uint8 table);
+ uint8 readBit();
+ uint8 _bitsData;
+ uint8 _bitsNumber;
+
+ // Inverse Discrete Cosine Transformation
+ static void idct1D8x8(int32 src[8], int32 dest[64], int32 ps, int32 half);
+ static void idct2D8x8(int32 block[64]);
+};
+
+} // End of Graphics namespace
+
+#endif // GRAPHICS_JPEG_H
diff --git a/graphics/decoders/pict.cpp b/graphics/decoders/pict.cpp
new file mode 100644
index 0000000000..957342084e
--- /dev/null
+++ b/graphics/decoders/pict.cpp
@@ -0,0 +1,567 @@
+/* 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.
+ *
+ */
+
+#include "common/debug.h"
+#include "common/endian.h"
+#include "common/stream.h"
+#include "common/substream.h"
+#include "common/textconsole.h"
+
+#include "graphics/surface.h"
+#include "graphics/decoders/jpeg.h"
+#include "graphics/decoders/pict.h"
+
+namespace Graphics {
+
+// The PICT code is based off of the QuickDraw specs:
+// http://developer.apple.com/legacy/mac/library/documentation/mac/QuickDraw/QuickDraw-461.html
+// http://developer.apple.com/legacy/mac/library/documentation/mac/QuickDraw/QuickDraw-269.html
+
+PICTDecoder::PICTDecoder() {
+ _outputSurface = 0;
+}
+
+PICTDecoder::~PICTDecoder() {
+ destroy();
+}
+
+void PICTDecoder::destroy() {
+ if (_outputSurface) {
+ _outputSurface->free();
+ delete _outputSurface;
+ _outputSurface = 0;
+ }
+}
+
+#define OPCODE(a, b, c) _opcodes.push_back(PICTOpcode(a, &PICTDecoder::b, c))
+
+void PICTDecoder::setupOpcodesCommon() {
+ OPCODE(0x0000, o_nop, "NOP");
+ OPCODE(0x0001, o_clip, "Clip");
+ OPCODE(0x0003, o_txFont, "TxFont");
+ OPCODE(0x0004, o_txFace, "TxFace");
+ OPCODE(0x0007, o_pnSize, "PnSize");
+ OPCODE(0x000D, o_txSize, "TxSize");
+ OPCODE(0x0010, o_txRatio, "TxRatio");
+ OPCODE(0x0011, o_versionOp, "VersionOp");
+ OPCODE(0x001E, o_nop, "DefHilite");
+ OPCODE(0x0028, o_longText, "LongText");
+ OPCODE(0x00A1, o_longComment, "LongComment");
+ OPCODE(0x00FF, o_opEndPic, "OpEndPic");
+ OPCODE(0x0C00, o_headerOp, "HeaderOp");
+}
+
+void PICTDecoder::setupOpcodesNormal() {
+ setupOpcodesCommon();
+ OPCODE(0x0098, on_packBitsRect, "PackBitsRect");
+ OPCODE(0x009A, on_directBitsRect, "DirectBitsRect");
+ OPCODE(0x8200, on_compressedQuickTime, "CompressedQuickTime");
+}
+
+void PICTDecoder::setupOpcodesQuickTime() {
+ setupOpcodesCommon();
+ OPCODE(0x0098, oq_packBitsRect, "PackBitsRect");
+ OPCODE(0x009A, oq_directBitsRect, "DirectBitsRect");
+ OPCODE(0x8200, oq_compressedQuickTime, "CompressedQuickTime");
+}
+
+#undef OPCODE
+
+void PICTDecoder::o_nop(Common::SeekableReadStream &) {
+ // Nothing to do
+}
+
+void PICTDecoder::o_clip(Common::SeekableReadStream &stream) {
+ // Ignore
+ stream.skip(stream.readUint16BE() - 2);
+}
+
+void PICTDecoder::o_txFont(Common::SeekableReadStream &stream) {
+ // Ignore
+ stream.readUint16BE();
+}
+
+void PICTDecoder::o_txFace(Common::SeekableReadStream &stream) {
+ // Ignore
+ stream.readByte();
+}
+
+void PICTDecoder::o_pnSize(Common::SeekableReadStream &stream) {
+ // Ignore
+ stream.readUint16BE();
+ stream.readUint16BE();
+}
+
+void PICTDecoder::o_txSize(Common::SeekableReadStream &stream) {
+ // Ignore
+ stream.readUint16BE();
+}
+
+void PICTDecoder::o_txRatio(Common::SeekableReadStream &stream) {
+ // Ignore
+ stream.readUint16BE();
+ stream.readUint16BE();
+ stream.readUint16BE();
+ stream.readUint16BE();
+}
+
+void PICTDecoder::o_versionOp(Common::SeekableReadStream &stream) {
+ // We only support v2 extended
+ if (stream.readUint16BE() != 0x02FF)
+ error("Unknown PICT version");
+}
+
+void PICTDecoder::o_longText(Common::SeekableReadStream &stream) {
+ // Ignore
+ stream.readUint16BE();
+ stream.readUint16BE();
+ stream.skip(stream.readByte());
+}
+
+void PICTDecoder::o_longComment(Common::SeekableReadStream &stream) {
+ // Ignore
+ stream.readUint16BE();
+ stream.skip(stream.readUint16BE());
+}
+
+void PICTDecoder::o_opEndPic(Common::SeekableReadStream &stream) {
+ // We've reached the end of the picture
+ _continueParsing = false;
+}
+
+void PICTDecoder::o_headerOp(Common::SeekableReadStream &stream) {
+ // Read the basic header, but we don't really have to do anything with it
+ /* uint16 version = */ stream.readUint16BE();
+ stream.readUint16BE(); // Reserved
+ /* uint32 hRes = */ stream.readUint32BE();
+ /* uint32 vRes = */ stream.readUint32BE();
+ Common::Rect origResRect;
+ origResRect.top = stream.readUint16BE();
+ origResRect.left = stream.readUint16BE();
+ origResRect.bottom = stream.readUint16BE();
+ origResRect.right = stream.readUint16BE();
+ stream.readUint32BE(); // Reserved
+}
+
+void PICTDecoder::on_packBitsRect(Common::SeekableReadStream &stream) {
+ // Unpack data (8bpp or lower)
+ unpackBitsRect(stream, true);
+}
+
+void PICTDecoder::on_directBitsRect(Common::SeekableReadStream &stream) {
+ // Unpack data (16bpp or higher)
+ unpackBitsRect(stream, false);
+}
+
+void PICTDecoder::on_compressedQuickTime(Common::SeekableReadStream &stream) {
+ // OK, here's the fun. We get to completely change how QuickDraw draws
+ // the data in PICT files.
+
+ // Swap out the opcodes to the new ones
+ _opcodes.clear();
+ setupOpcodesQuickTime();
+
+ // We'll decode the first QuickTime data from here, but the QuickTime-specific
+ // opcodes will take over from here on out. Normal opcodes, signing off.
+ decodeCompressedQuickTime(stream);
+}
+
+void PICTDecoder::oq_packBitsRect(Common::SeekableReadStream &stream) {
+ // Skip any data here (8bpp or lower)
+ skipBitsRect(stream, true);
+}
+
+void PICTDecoder::oq_directBitsRect(Common::SeekableReadStream &stream) {
+ // Skip any data here (16bpp or higher)
+ skipBitsRect(stream, false);
+}
+
+void PICTDecoder::oq_compressedQuickTime(Common::SeekableReadStream &stream) {
+ // Just pass the data along
+ decodeCompressedQuickTime(stream);
+}
+
+bool PICTDecoder::loadStream(Common::SeekableReadStream &stream) {
+ destroy();
+
+ // Initialize opcodes to their normal state
+ _opcodes.clear();
+ setupOpcodesNormal();
+
+ _continueParsing = true;
+ memset(_palette, 0, sizeof(_palette));
+
+ uint16 fileSize = stream.readUint16BE();
+
+ // If we have no file size here, we probably have a PICT from a file
+ // and not a resource. The other two bytes are the fileSize which we
+ // don't actually need (and already read if from a resource).
+ if (!fileSize)
+ stream.seek(512 + 2);
+
+ _imageRect.top = stream.readUint16BE();
+ _imageRect.left = stream.readUint16BE();
+ _imageRect.bottom = stream.readUint16BE();
+ _imageRect.right = stream.readUint16BE();
+ _imageRect.debugPrint(0, "PICT Rect:");
+
+ // NOTE: This is only a subset of the full PICT format.
+ // - Only V2 (Extended) Images Supported
+ // - CompressedQuickTime (JPEG) compressed data is supported
+ // - DirectBitsRect/PackBitsRect compressed data is supported
+ for (uint32 opNum = 0; !stream.eos() && !stream.err() && stream.pos() < stream.size() && _continueParsing; opNum++) {
+ // PICT v2 opcodes are two bytes
+ uint16 opcode = stream.readUint16BE();
+
+ if (opNum == 0 && opcode != 0x0011)
+ error("Cannot find PICT version opcode");
+ else if (opNum == 1 && opcode != 0x0C00)
+ error("Cannot find PICT header opcode");
+
+ // Since opcodes are word-aligned, we need to mark our starting
+ // position here.
+ uint32 startPos = stream.pos();
+
+ for (uint32 i = 0; i < _opcodes.size(); i++) {
+ if (_opcodes[i].op == opcode) {
+ debug(4, "Running PICT opcode %04x '%s'", opcode, _opcodes[i].desc);
+ (this->*(_opcodes[i].proc))(stream);
+ break;
+ } else if (i == _opcodes.size() - 1) {
+ // Unknown opcode; attempt to continue forward
+ warning("Unknown PICT opcode %04x", opcode);
+ }
+ }
+
+ // Align
+ stream.skip((stream.pos() - startPos) & 1);
+ }
+
+ return _outputSurface;
+}
+
+PICTDecoder::PixMap PICTDecoder::readPixMap(Common::SeekableReadStream &stream, bool hasBaseAddr) {
+ PixMap pixMap;
+ pixMap.baseAddr = hasBaseAddr ? stream.readUint32BE() : 0;
+ pixMap.rowBytes = stream.readUint16BE() & 0x3fff;
+ pixMap.bounds.top = stream.readUint16BE();
+ pixMap.bounds.left = stream.readUint16BE();
+ pixMap.bounds.bottom = stream.readUint16BE();
+ pixMap.bounds.right = stream.readUint16BE();
+ pixMap.pmVersion = stream.readUint16BE();
+ pixMap.packType = stream.readUint16BE();
+ pixMap.packSize = stream.readUint32BE();
+ pixMap.hRes = stream.readUint32BE();
+ pixMap.vRes = stream.readUint32BE();
+ pixMap.pixelType = stream.readUint16BE();
+ pixMap.pixelSize = stream.readUint16BE();
+ pixMap.cmpCount = stream.readUint16BE();
+ pixMap.cmpSize = stream.readUint16BE();
+ pixMap.planeBytes = stream.readUint32BE();
+ pixMap.pmTable = stream.readUint32BE();
+ pixMap.pmReserved = stream.readUint32BE();
+ return pixMap;
+}
+
+struct PackBitsRectData {
+ PICTDecoder::PixMap pixMap;
+ Common::Rect srcRect;
+ Common::Rect dstRect;
+ uint16 mode;
+};
+
+void PICTDecoder::unpackBitsRect(Common::SeekableReadStream &stream, bool hasPalette) {
+ PackBitsRectData packBitsData;
+ packBitsData.pixMap = readPixMap(stream, !hasPalette);
+
+ // Read in the palette if there is one present
+ if (hasPalette) {
+ // See http://developer.apple.com/legacy/mac/library/documentation/mac/QuickDraw/QuickDraw-267.html
+ stream.readUint32BE(); // seed
+ stream.readUint16BE(); // flags
+ uint16 colorCount = stream.readUint16BE() + 1;
+
+ for (uint32 i = 0; i < colorCount; i++) {
+ stream.readUint16BE();
+ _palette[i * 3] = stream.readUint16BE() >> 8;
+ _palette[i * 3 + 1] = stream.readUint16BE() >> 8;
+ _palette[i * 3 + 2] = stream.readUint16BE() >> 8;
+ }
+ }
+
+ packBitsData.srcRect.top = stream.readUint16BE();
+ packBitsData.srcRect.left = stream.readUint16BE();
+ packBitsData.srcRect.bottom = stream.readUint16BE();
+ packBitsData.srcRect.right = stream.readUint16BE();
+ packBitsData.dstRect.top = stream.readUint16BE();
+ packBitsData.dstRect.left = stream.readUint16BE();
+ packBitsData.dstRect.bottom = stream.readUint16BE();
+ packBitsData.dstRect.right = stream.readUint16BE();
+ packBitsData.mode = stream.readUint16BE();
+
+ uint16 width = packBitsData.srcRect.width();
+ uint16 height = packBitsData.srcRect.height();
+
+ byte bytesPerPixel = 0;
+
+ if (packBitsData.pixMap.pixelSize <= 8)
+ bytesPerPixel = 1;
+ else if (packBitsData.pixMap.pixelSize == 32)
+ bytesPerPixel = packBitsData.pixMap.cmpCount;
+ else
+ bytesPerPixel = packBitsData.pixMap.pixelSize / 8;
+
+ // Ensure we have enough space in the buffer to hold an entire line's worth of pixels
+ uint32 lineSize = MAX<int>(width * bytesPerPixel + (8 * 2 / packBitsData.pixMap.pixelSize), packBitsData.pixMap.rowBytes);
+ byte *buffer = new byte[lineSize * height];
+
+ // Read in amount of data per row
+ for (uint16 i = 0; i < packBitsData.pixMap.bounds.height(); i++) {
+ // NOTE: Compression 0 is "default". The format in SCI games is packed when 0.
+ // In the future, we may need to have something to set the "default" packing
+ // format, but this is good for now.
+
+ if (packBitsData.pixMap.packType == 1 || packBitsData.pixMap.rowBytes < 8) { // Unpacked, Pad-Byte (on 24-bit)
+ // TODO: Finish this. Hasn't been needed (yet).
+ error("Unpacked DirectBitsRect data (padded)");
+ } else if (packBitsData.pixMap.packType == 2) { // Unpacked, No Pad-Byte (on 24-bit)
+ // TODO: Finish this. Hasn't been needed (yet).
+ error("Unpacked DirectBitsRect data (not padded)");
+ } else if (packBitsData.pixMap.packType == 0 || packBitsData.pixMap.packType > 2) { // Packed
+ uint16 byteCount = (packBitsData.pixMap.rowBytes > 250) ? stream.readUint16BE() : stream.readByte();
+ unpackBitsLine(buffer + i * width * bytesPerPixel, packBitsData.pixMap.rowBytes, stream.readStream(byteCount), packBitsData.pixMap.pixelSize, bytesPerPixel);
+ }
+ }
+
+ _outputSurface = new Graphics::Surface();
+
+ switch (bytesPerPixel) {
+ case 1:
+ // Just copy to the image
+ _outputSurface->create(width, height, PixelFormat::createFormatCLUT8());
+ memcpy(_outputSurface->pixels, buffer, _outputSurface->w * _outputSurface->h);
+ break;
+ case 2:
+ // Convert from 16-bit to whatever surface we need
+ _outputSurface->create(width, height, PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0));
+ for (uint16 y = 0; y < _outputSurface->h; y++)
+ for (uint16 x = 0; x < _outputSurface->w; x++)
+ WRITE_UINT16(_outputSurface->getBasePtr(x, y), READ_UINT16(buffer + (y * _outputSurface->w + x) * 2));
+ break;
+ case 3:
+ // Convert from 24-bit (planar!) to whatever surface we need
+ _outputSurface->create(width, height, Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0));
+ for (uint16 y = 0; y < _outputSurface->h; y++) {
+ for (uint16 x = 0; x < _outputSurface->w; x++) {
+ byte r = *(buffer + y * _outputSurface->w * 3 + x);
+ byte g = *(buffer + y * _outputSurface->w * 3 + _outputSurface->w + x);
+ byte b = *(buffer + y * _outputSurface->w * 3 + _outputSurface->w * 2 + x);
+ *((uint32 *)_outputSurface->getBasePtr(x, y)) = _outputSurface->format.RGBToColor(r, g, b);
+ }
+ }
+ break;
+ case 4:
+ // Convert from 32-bit (planar!) to whatever surface we need
+ _outputSurface->create(width, height, Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0));
+ for (uint16 y = 0; y < _outputSurface->h; y++) {
+ for (uint16 x = 0; x < _outputSurface->w; x++) {
+ byte r = *(buffer + y * _outputSurface->w * 4 + x);
+ byte g = *(buffer + y * _outputSurface->w * 4 + _outputSurface->w + x);
+ byte b = *(buffer + y * _outputSurface->w * 4 + _outputSurface->w * 2 + x);
+ byte a = *(buffer + y * _outputSurface->w * 4 + _outputSurface->w * 3 + x);
+ *((uint32 *)_outputSurface->getBasePtr(x, y)) = _outputSurface->format.ARGBToColor(r, g, b, a);
+ }
+ }
+ break;
+ }
+
+ delete[] buffer;
+}
+
+void PICTDecoder::unpackBitsLine(byte *out, uint32 length, Common::SeekableReadStream *data, byte bitsPerPixel, byte bytesPerPixel) {
+ uint32 dataDecoded = 0;
+ byte bytesPerDecode = (bytesPerPixel == 2) ? 2 : 1;
+
+ while (data->pos() < data->size() && dataDecoded < length) {
+ byte op = data->readByte();
+
+ if (op & 0x80) {
+ uint32 runSize = (op ^ 255) + 2;
+ uint16 value = (bytesPerDecode == 2) ? data->readUint16BE() : data->readByte();
+
+ for (uint32 i = 0; i < runSize; i++) {
+ if (bytesPerDecode == 2) {
+ WRITE_UINT16(out, value);
+ out += 2;
+ } else {
+ outputPixelBuffer(out, value, bitsPerPixel);
+ }
+ }
+ dataDecoded += runSize * bytesPerDecode;
+ } else {
+ uint32 runSize = op + 1;
+
+ if (bytesPerDecode == 1) {
+ for (uint32 i = 0; i < runSize; i++)
+ outputPixelBuffer(out, data->readByte(), bitsPerPixel);
+ } else {
+ for (uint32 i = 0; i < runSize; i++) {
+ WRITE_UINT16(out, data->readUint16BE());
+ out += 2;
+ }
+ }
+
+ dataDecoded += runSize * bytesPerDecode;
+ }
+ }
+
+ // HACK: Even if the data is 24-bit, rowBytes is still 32-bit
+ if (bytesPerPixel == 3)
+ dataDecoded += length / 4;
+
+ if (length != dataDecoded)
+ warning("Mismatched PackBits read (%d/%d)", dataDecoded, length);
+
+ delete data;
+}
+
+void PICTDecoder::outputPixelBuffer(byte *&out, byte value, byte bitsPerPixel) {
+ switch (bitsPerPixel) {
+ case 1:
+ for (int i = 7; i >= 0; i--)
+ *out++ = (value >> i) & 1;
+ break;
+ case 2:
+ for (int i = 6; i >= 0; i -= 2)
+ *out++ = (value >> i) & 3;
+ break;
+ case 4:
+ *out++ = (value >> 4) & 0xf;
+ *out++ = value & 0xf;
+ break;
+ default:
+ *out++ = value;
+ }
+}
+
+void PICTDecoder::skipBitsRect(Common::SeekableReadStream &stream, bool hasPalette) {
+ // Step through a PackBitsRect/DirectBitsRect function
+
+ if (!hasPalette)
+ stream.readUint32BE();
+
+ uint16 rowBytes = stream.readUint16BE();
+ uint16 height = stream.readUint16BE();
+ stream.readUint16BE();
+ height = stream.readUint16BE() - height;
+ stream.readUint16BE();
+
+ uint16 packType;
+
+ // Top two bits signify PixMap vs BitMap
+ if (rowBytes & 0xC000) {
+ // PixMap
+ stream.readUint16BE();
+ packType = stream.readUint16BE();
+ stream.skip(14);
+ stream.readUint16BE(); // pixelSize
+ stream.skip(16);
+
+ if (hasPalette) {
+ stream.readUint32BE();
+ stream.readUint16BE();
+ stream.skip((stream.readUint16BE() + 1) * 8);
+ }
+
+ rowBytes &= 0x3FFF;
+ } else {
+ // BitMap
+ packType = 0;
+ }
+
+ stream.skip(18);
+
+ for (uint16 i = 0; i < height; i++) {
+ if (packType == 1 || packType == 2 || rowBytes < 8)
+ error("Unpacked PackBitsRect data");
+ else if (packType == 0 || packType > 2)
+ stream.skip((rowBytes > 250) ? stream.readUint16BE() : stream.readByte());
+ }
+}
+
+// Compressed QuickTime details can be found here:
+// http://developer.apple.com/legacy/mac/library/#documentation/QuickTime/Rm/CompressDecompress/ImageComprMgr/B-Chapter/2TheImageCompression.html
+// http://developer.apple.com/legacy/mac/library/#documentation/QuickTime/Rm/CompressDecompress/ImageComprMgr/F-Chapter/6WorkingwiththeImage.html
+void PICTDecoder::decodeCompressedQuickTime(Common::SeekableReadStream &stream) {
+ // First, read all the fields from the opcode
+ uint32 dataSize = stream.readUint32BE();
+ uint32 startPos = stream.pos();
+
+ /* uint16 version = */ stream.readUint16BE();
+
+ // Read in the display matrix
+ uint32 matrix[3][3];
+ for (uint32 i = 0; i < 3; i++)
+ for (uint32 j = 0; j < 3; j++)
+ matrix[i][j] = stream.readUint32BE();
+
+ // We currently only support offseting images vertically from the matrix
+ uint16 xOffset = 0;
+ uint16 yOffset = matrix[2][1] >> 16;
+
+ uint32 matteSize = stream.readUint32BE();
+ stream.skip(8); // matte rect
+ /* uint16 transferMode = */ stream.readUint16BE();
+ stream.skip(8); // src rect
+ /* uint32 accuracy = */ stream.readUint32BE();
+ uint32 maskSize = stream.readUint32BE();
+
+ // Skip the matte and mask
+ stream.skip(matteSize + maskSize);
+
+ // Now we've reached the image descriptor, so read the relevant data from that
+ uint32 idStart = stream.pos();
+ uint32 idSize = stream.readUint32BE();
+ stream.skip(40); // miscellaneous stuff
+ uint32 jpegSize = stream.readUint32BE();
+ stream.skip(idSize - (stream.pos() - idStart)); // more useless stuff
+
+ Common::SeekableSubReadStream jpegStream(&stream, stream.pos(), stream.pos() + jpegSize);
+
+ JPEGDecoder jpeg;
+ if (!jpeg.loadStream(jpegStream))
+ error("PICTDecoder::decodeCompressedQuickTime(): Could not decode JPEG data");
+
+ const Graphics::Surface *jpegSurface = jpeg.getSurface();
+
+ if (!_outputSurface) {
+ _outputSurface = new Graphics::Surface();
+ _outputSurface->create(_imageRect.width(), _imageRect.height(), jpegSurface->format);
+ }
+
+ for (uint16 y = 0; y < jpegSurface->h; y++)
+ memcpy(_outputSurface->getBasePtr(0 + xOffset, y + yOffset), jpegSurface->getBasePtr(0, y), jpegSurface->w * jpegSurface->format.bytesPerPixel);
+
+ stream.seek(startPos + dataSize);
+}
+
+} // End of namespace Graphics
diff --git a/graphics/decoders/pict.h b/graphics/decoders/pict.h
new file mode 100644
index 0000000000..b1e45a6bc1
--- /dev/null
+++ b/graphics/decoders/pict.h
@@ -0,0 +1,130 @@
+/* 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.
+ *
+ */
+
+#ifndef GRAPHICS_PICT_H
+#define GRAPHICS_PICT_H
+
+#include "common/array.h"
+#include "common/rect.h"
+#include "common/scummsys.h"
+
+#include "graphics/decoders/image_decoder.h"
+#include "graphics/pixelformat.h"
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Graphics {
+
+struct Surface;
+
+#define DECLARE_OPCODE(x) void x(Common::SeekableReadStream &stream)
+
+class PICTDecoder : public ImageDecoder {
+public:
+ PICTDecoder();
+ ~PICTDecoder();
+
+ // ImageDecoder API
+ bool loadStream(Common::SeekableReadStream &stream);
+ void destroy();
+ const Surface *getSurface() const { return _outputSurface; }
+ const byte *getPalette() const { return _palette; }
+
+ struct PixMap {
+ uint32 baseAddr;
+ uint16 rowBytes;
+ Common::Rect bounds;
+ uint16 pmVersion;
+ uint16 packType;
+ uint32 packSize;
+ uint32 hRes;
+ uint32 vRes;
+ uint16 pixelType;
+ uint16 pixelSize;
+ uint16 cmpCount;
+ uint16 cmpSize;
+ uint32 planeBytes;
+ uint32 pmTable;
+ uint32 pmReserved;
+ };
+
+ static PixMap readPixMap(Common::SeekableReadStream &stream, bool hasBaseAddr = true);
+
+private:
+ Common::Rect _imageRect;
+ byte _palette[256 * 3];
+ Graphics::Surface *_outputSurface;
+ bool _continueParsing;
+
+ // Utility Functions
+ void unpackBitsRect(Common::SeekableReadStream &stream, bool hasPalette);
+ void unpackBitsLine(byte *out, uint32 length, Common::SeekableReadStream *stream, byte bitsPerPixel, byte bytesPerPixel);
+ void skipBitsRect(Common::SeekableReadStream &stream, bool hasPalette);
+ void decodeCompressedQuickTime(Common::SeekableReadStream &stream);
+ void outputPixelBuffer(byte *&out, byte value, byte bitsPerPixel);
+
+ // Opcodes
+ typedef void (PICTDecoder::*OpcodeProcPICT)(Common::SeekableReadStream &stream);
+ struct PICTOpcode {
+ PICTOpcode() { op = 0; proc = 0; desc = 0; }
+ PICTOpcode(uint16 o, OpcodeProcPICT p, const char *d) { op = o; proc = p; desc = d; }
+ uint16 op;
+ OpcodeProcPICT proc;
+ const char *desc;
+ };
+ Common::Array<PICTOpcode> _opcodes;
+
+ // Common Opcodes
+ void setupOpcodesCommon();
+ DECLARE_OPCODE(o_nop);
+ DECLARE_OPCODE(o_clip);
+ DECLARE_OPCODE(o_txFont);
+ DECLARE_OPCODE(o_txFace);
+ DECLARE_OPCODE(o_pnSize);
+ DECLARE_OPCODE(o_txSize);
+ DECLARE_OPCODE(o_txRatio);
+ DECLARE_OPCODE(o_versionOp);
+ DECLARE_OPCODE(o_longText);
+ DECLARE_OPCODE(o_longComment);
+ DECLARE_OPCODE(o_opEndPic);
+ DECLARE_OPCODE(o_headerOp);
+
+ // Regular-mode Opcodes
+ void setupOpcodesNormal();
+ DECLARE_OPCODE(on_packBitsRect);
+ DECLARE_OPCODE(on_directBitsRect);
+ DECLARE_OPCODE(on_compressedQuickTime);
+
+ // QuickTime-mode Opcodes
+ void setupOpcodesQuickTime();
+ DECLARE_OPCODE(oq_packBitsRect);
+ DECLARE_OPCODE(oq_directBitsRect);
+ DECLARE_OPCODE(oq_compressedQuickTime);
+};
+
+#undef DECLARE_OPCODE
+
+} // End of namespace Graphics
+
+#endif
diff --git a/graphics/decoders/png.cpp b/graphics/decoders/png.cpp
new file mode 100644
index 0000000000..b87b6fdc7a
--- /dev/null
+++ b/graphics/decoders/png.cpp
@@ -0,0 +1,503 @@
+/* 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.
+ *
+ */
+
+#include "graphics/decoders/png.h"
+
+#include "graphics/pixelformat.h"
+#include "graphics/surface.h"
+
+#include "common/endian.h"
+#include "common/memstream.h"
+#include "common/stream.h"
+#include "common/types.h"
+#include "common/util.h"
+#include "common/zlib.h"
+
+// PNG decoder, based on the W3C specs:
+// http://www.w3.org/TR/PNG/
+// Parts of the code have been adapted from LodePNG, by Lode Vandevenne:
+// http://members.gamedev.net/lode/projects/LodePNG/
+
+/*
+LodePNG version 20101211
+
+Copyright (c) 2005-2010 Lode Vandevenne
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+
+ 3. This notice may not be removed or altered from any source
+ distribution.
+*/
+
+namespace Graphics {
+
+enum PNGChunks {
+ // == Critical chunks =====================================================
+ kChunkIHDR = MKTAG('I','H','D','R'), // Image header
+ kChunkIDAT = MKTAG('I','D','A','T'), // Image data
+ kChunkPLTE = MKTAG('P','L','T','E'), // Palette
+ kChunkIEND = MKTAG('I','E','N','D'), // Image trailer
+ // == Ancillary chunks ====================================================
+ kChunktRNS = MKTAG('t','R','N','S') // 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
+};
+
+PNGDecoder::PNGDecoder() : _compressedBuffer(0), _compressedBufferSize(0),
+ _transparentColorSpecified(false), _outputSurface(0) {
+}
+
+PNGDecoder::~PNGDecoder() {
+ destroy();
+}
+
+void PNGDecoder::destroy() {
+ if (_outputSurface) {
+ _outputSurface->free();
+ delete _outputSurface;
+ _outputSurface = 0;
+ }
+}
+
+bool PNGDecoder::loadStream(Common::SeekableReadStream &stream) {
+ destroy();
+
+ uint32 chunkLength = 0, chunkType = 0;
+ _stream = &stream;
+
+ // First, check the PNG signature
+ if (_stream->readUint32BE() != MKTAG(0x89, 'P', 'N', 'G')) {
+ delete _stream;
+ return false;
+ }
+ if (_stream->readUint32BE() != MKTAG(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 = (byte *)malloc(_compressedBufferSize);
+ _stream->read(_compressedBuffer, chunkLength);
+ } else {
+ // Expand the buffer
+ uint32 prevSize = _compressedBufferSize;
+ _compressedBufferSize += chunkLength;
+ byte *tmp = new byte[prevSize];
+ memcpy(tmp, _compressedBuffer, prevSize);
+ free(_compressedBuffer);
+ _compressedBuffer = (byte *)malloc(_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;
+ _stream->read(_palette, _paletteEntries * 3);
+ memset(_paletteTransparency, 0xff, sizeof(_paletteTransparency));
+ 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
+ _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 PNGDecoder::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 PNGDecoder::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");
+ }
+
+}
+
+int PNGDecoder::getBytesPerPixel() const {
+ return (getNumColorChannels() * _header.bitDepth + 7) / 8;
+}
+
+void PNGDecoder::constructImage() {
+ assert (_header.bitDepth != 0);
+
+ int bytesPerPixel = getBytesPerPixel();
+ int pitch = bytesPerPixel * _header.width;
+ byte *unfilteredSurface = new byte[pitch * _header.height];
+ byte *dest = unfilteredSurface;
+ uint16 scanLineWidth = (_header.width * getNumColorChannels() * _header.bitDepth + 7) / 8;
+ byte *scanLine = new byte[scanLineWidth];
+ byte *prevLine = 0;
+
+ switch(_header.interlaceType) {
+ case kNonInterlaced:
+ for (uint16 y = 0; y < _header.height; y++) {
+ byte filterType = _imageData->readByte();
+ _imageData->read(scanLine, scanLineWidth);
+ unfilterScanLine(dest, scanLine, prevLine, bytesPerPixel, filterType, scanLineWidth);
+ prevLine = dest;
+ dest += 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;
+
+ constructOutput(unfilteredSurface);
+ delete[] unfilteredSurface;
+}
+
+Graphics::PixelFormat PNGDecoder::findPixelFormat() const {
+ // Try to find the best pixel format based on what we have here
+ // Which is basically 8bpp for paletted non-transparent
+ // and 32bpp for everything else
+
+ switch (_header.colorType) {
+ case kIndexed:
+ if (!_transparentColorSpecified)
+ return Graphics::PixelFormat::createFormatCLUT8();
+ // fall through
+ case kGrayScale:
+ case kTrueColor:
+ case kGrayScaleWithAlpha:
+ case kTrueColorWithAlpha:
+ // We'll go with standard RGBA 32-bit
+ return Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0);
+ }
+
+ error("Unknown PNG color type");
+ return Graphics::PixelFormat();
+}
+
+void PNGDecoder::constructOutput(const byte *surface) {
+ _outputSurface = new Graphics::Surface();
+ _outputSurface->create(_header.width, _header.height, findPixelFormat());
+
+ const byte *src = surface;
+ byte a = 0xFF;
+ int bytesPerPixel = getBytesPerPixel();
+
+ if (_header.colorType != kIndexed) {
+ if (_header.colorType == kTrueColor ||
+ _header.colorType == kTrueColorWithAlpha) {
+ if (bytesPerPixel != 3 && bytesPerPixel != 4)
+ error("Unsupported truecolor PNG format");
+ } else if (_header.colorType == kGrayScale ||
+ _header.colorType == kGrayScaleWithAlpha) {
+ if (bytesPerPixel != 1 && bytesPerPixel != 2)
+ error("Unsupported grayscale PNG format");
+ }
+
+ for (uint16 i = 0; i < _outputSurface->h; i++) {
+ for (uint16 j = 0; j < _outputSurface->w; j++) {
+ uint32 result = 0;
+
+ switch (bytesPerPixel) {
+ case 1: // Grayscale
+ if (_transparentColorSpecified)
+ a = (src[0] == _transparentColor[0]) ? 0 : 0xFF;
+ result = _outputSurface->format.ARGBToColor(a, src[0], src[0], src[0]);
+ break;
+ case 2: // Grayscale + alpha
+ result = _outputSurface->format.ARGBToColor(src[1], src[0], src[0], src[0]);
+ break;
+ case 3: // RGB
+ if (_transparentColorSpecified) {
+ bool isTransparentColor = (src[0] == _transparentColor[0] &&
+ src[1] == _transparentColor[1] &&
+ src[2] == _transparentColor[2]);
+ a = isTransparentColor ? 0 : 0xFF;
+ }
+
+ result = _outputSurface->format.ARGBToColor(a, src[0], src[1], src[2]);
+ break;
+ case 4: // RGBA
+ result = _outputSurface->format.ARGBToColor(src[3], src[0], src[1], src[2]);
+ break;
+ }
+
+ *((uint32 *)_outputSurface->getBasePtr(j, i)) = result;
+ src += bytesPerPixel;
+ }
+ }
+ } else {
+ uint32 mask = (0xff >> (8 - _header.bitDepth)) << (8 - _header.bitDepth);
+
+ // Convert the indexed surface to the target pixel format
+ for (uint16 i = 0; i < _outputSurface->h; i++) {
+ int data = 0;
+ int bitCount = 8;
+ const byte *src1 = src;
+
+ for (uint16 j = 0; j < _outputSurface->w; j++) {
+ if (bitCount == 8) {
+ data = *src;
+ src++;
+ }
+
+ byte index = (data & mask) >> (8 - _header.bitDepth);
+ data = (data << _header.bitDepth) & 0xff;
+ bitCount -= _header.bitDepth;
+
+ if (bitCount == 0)
+ bitCount = 8;
+
+ if (_transparentColorSpecified) {
+ byte r = _palette[index * 3 + 0];
+ byte g = _palette[index * 3 + 1];
+ byte b = _palette[index * 3 + 2];
+ a = _paletteTransparency[index];
+ *((uint32 *)_outputSurface->getBasePtr(j, i)) = _outputSurface->format.ARGBToColor(a, r, g, b);
+ } else {
+ *((byte *)_outputSurface->getBasePtr(j, i)) = index;
+ }
+ }
+
+ src = src1 + _outputSurface->w;
+ }
+ }
+}
+
+void PNGDecoder::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 PNGDecoder::getNumColorChannels() const {
+ 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 PNGDecoder::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:
+ _stream->read(_paletteTransparency, chunkLength);
+
+ // 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/decoders/png.h b/graphics/decoders/png.h
new file mode 100644
index 0000000000..1da0bea1ab
--- /dev/null
+++ b/graphics/decoders/png.h
@@ -0,0 +1,135 @@
+/* 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.
+ *
+ */
+
+/*
+ * PNG decoder used in engines:
+ * - sword25
+ * Dependencies:
+ * - zlib
+ */
+
+#ifndef GRAPHICS_PNG_H
+#define GRAPHICS_PNG_H
+
+// PNG decoder, based on the W3C specs:
+// http://www.w3.org/TR/PNG/
+// Parts of the code have been adapted from LodePNG, by Lode Vandevenne:
+// http://members.gamedev.net/lode/projects/LodePNG/
+
+// 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.
+
+#include "common/scummsys.h"
+#include "common/textconsole.h"
+#include "graphics/decoders/image_decoder.h"
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Graphics {
+
+struct Surface;
+struct PixelFormat;
+
+class PNGDecoder : public ImageDecoder {
+public:
+ PNGDecoder();
+ ~PNGDecoder();
+
+ bool loadStream(Common::SeekableReadStream &stream);
+ void destroy();
+ const Graphics::Surface *getSurface() const { return _outputSurface; }
+ const byte *getPalette() const { return _palette; }
+
+private:
+ 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;
+ };
+
+ void readHeaderChunk();
+ byte getNumColorChannels() const;
+
+ 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 * 3]; // RGB
+ byte _paletteTransparency[256];
+ uint16 _paletteEntries;
+ uint16 _transparentColor[3];
+ bool _transparentColorSpecified;
+
+ byte *_compressedBuffer;
+ uint32 _compressedBufferSize;
+
+ Graphics::Surface *_outputSurface;
+ Graphics::PixelFormat findPixelFormat() const;
+ int getBytesPerPixel() const;
+ void constructOutput(const byte *surface);
+};
+
+} // End of namespace Graphics
+
+#endif // GRAPHICS_PNG_H