aboutsummaryrefslogtreecommitdiff
path: root/image
diff options
context:
space:
mode:
Diffstat (limited to 'image')
-rw-r--r--image/bmp.cpp183
-rw-r--r--image/bmp.h68
-rw-r--r--image/iff.cpp244
-rw-r--r--image/iff.h126
-rw-r--r--image/image_decoder.h106
-rw-r--r--image/jpeg.cpp266
-rw-r--r--image/jpeg.h95
-rw-r--r--image/module.mk13
-rw-r--r--image/pcx.cpp214
-rw-r--r--image/pcx.h66
-rw-r--r--image/pict.cpp580
-rw-r--r--image/pict.h141
-rw-r--r--image/png.cpp245
-rw-r--r--image/png.h68
-rw-r--r--image/tga.cpp430
-rw-r--r--image/tga.h100
16 files changed, 2945 insertions, 0 deletions
diff --git a/image/bmp.cpp b/image/bmp.cpp
new file mode 100644
index 0000000000..113e268956
--- /dev/null
+++ b/image/bmp.cpp
@@ -0,0 +1,183 @@
+/* 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 "image/bmp.h"
+
+#include "common/stream.h"
+#include "common/textconsole.h"
+#include "graphics/pixelformat.h"
+#include "graphics/surface.h"
+
+namespace Image {
+
+BitmapDecoder::BitmapDecoder() {
+ _surface = 0;
+ _palette = 0;
+ _paletteColorCount = 0;
+}
+
+BitmapDecoder::~BitmapDecoder() {
+ destroy();
+}
+
+void BitmapDecoder::destroy() {
+ if (_surface) {
+ _surface->free();
+ delete _surface; _surface = 0;
+ }
+
+ delete[] _palette; _palette = 0;
+ _paletteColorCount = 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 && bitsPerPixel != 32) {
+ 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();
+ _paletteColorCount = stream.readUint32LE();
+ /* uint32 colorsImportant = */ stream.readUint32LE();
+
+ if (bitsPerPixel == 8) {
+ if (_paletteColorCount == 0)
+ _paletteColorCount = 256;
+
+ // Read the palette
+ _palette = new byte[_paletteColorCount * 3];
+ for (uint16 i = 0; i < _paletteColorCount; 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 and 32 bpp
+ if (bitsPerPixel == 24 || bitsPerPixel == 32)
+ 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->getPixels();
+
+ for (int32 i = 0; i < height; i++) {
+ stream.read(dst + (height - i - 1) * width, width);
+ stream.skip(extraDataLength);
+ }
+ } else if (bitsPerPixel == 24) {
+ byte *dst = (byte *)_surface->getBasePtr(0, height - 1);
+
+ 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;
+ }
+ } else { // 32 bpp
+ byte *dst = (byte *)_surface->getBasePtr(0, height - 1);
+
+ 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();
+ // Ignore the last byte, as in v3 it is unused
+ // and should thus NOT be used as alpha.
+ // ref: http://msdn.microsoft.com/en-us/library/windows/desktop/dd183376%28v=vs.85%29.aspx
+ 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 Image
diff --git a/image/bmp.h b/image/bmp.h
new file mode 100644
index 0000000000..bc4cfc3edd
--- /dev/null
+++ b/image/bmp.h
@@ -0,0 +1,68 @@
+/* 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.
+ *
+ */
+
+/**
+ * @file
+ * Image decoder used in engines:
+ * - hugo
+ * - mohawk
+ * - wintermute
+ */
+
+#ifndef IMAGE_BMP_H
+#define IMAGE_BMP_H
+
+#include "common/scummsys.h"
+#include "common/str.h"
+#include "image/image_decoder.h"
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Graphics {
+struct Surface;
+}
+
+namespace Image {
+
+class BitmapDecoder : public ImageDecoder {
+public:
+ BitmapDecoder();
+ virtual ~BitmapDecoder();
+
+ // ImageDecoder API
+ void destroy();
+ virtual bool loadStream(Common::SeekableReadStream &stream);
+ virtual const Graphics::Surface *getSurface() const { return _surface; }
+ const byte *getPalette() const { return _palette; }
+ uint16 getPaletteColorCount() const { return _paletteColorCount; }
+
+private:
+ Graphics::Surface *_surface;
+ byte *_palette;
+ uint16 _paletteColorCount;
+};
+
+} // End of namespace Image
+
+#endif
diff --git a/image/iff.cpp b/image/iff.cpp
new file mode 100644
index 0000000000..d93e9ff878
--- /dev/null
+++ b/image/iff.cpp
@@ -0,0 +1,244 @@
+/* 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 "image/iff.h"
+
+#include "common/iff_container.h"
+#include "common/stream.h"
+#include "common/util.h"
+
+namespace Image {
+
+IFFDecoder::IFFDecoder() {
+ _surface = 0;
+ _palette = 0;
+
+ // these 2 properties are not reset by destroy(), so the default is set here.
+ _numRelevantPlanes = 8;
+ _pixelPacking = false;
+
+ destroy();
+}
+
+IFFDecoder::~IFFDecoder() {
+ destroy();
+}
+
+void IFFDecoder::destroy() {
+ if (_surface) {
+ _surface->free();
+ delete _surface;
+ _surface = 0;
+ }
+
+ if (_palette) {
+ delete[] _palette;
+ _palette = 0;
+ }
+
+ memset(&_header, 0, sizeof(Header));
+ _paletteRanges.clear();
+ _type = TYPE_UNKNOWN;
+ _paletteColorCount = 0;
+}
+
+bool IFFDecoder::loadStream(Common::SeekableReadStream &stream) {
+ destroy();
+
+ const uint32 form = stream.readUint32BE();
+
+ if (form != ID_FORM) {
+ warning("Failed reading IFF-file");
+ return false;
+ }
+
+ stream.skip(4);
+
+ const uint32 type = stream.readUint32BE();
+
+ switch (type) {
+ case ID_ILBM:
+ _type = TYPE_ILBM;
+ break;
+ case ID_PBM:
+ _type = TYPE_PBM;
+ break;
+ }
+
+ if (type == TYPE_UNKNOWN) {
+ warning("Failed reading IFF-file");
+ return false;
+ }
+
+ while (1) {
+ const uint32 chunkType = stream.readUint32BE();
+ const uint32 chunkSize = stream.readUint32BE();
+
+ if (stream.eos())
+ break;
+
+ switch (chunkType) {
+ case ID_BMHD:
+ loadHeader(stream);
+ break;
+ case ID_CMAP:
+ loadPalette(stream, chunkSize);
+ break;
+ case ID_CRNG:
+ loadPaletteRange(stream, chunkSize);
+ break;
+ case ID_BODY:
+ loadBitmap(stream);
+ break;
+ default:
+ stream.skip(chunkSize);
+ }
+ }
+
+ return true;
+}
+
+void IFFDecoder::loadHeader(Common::SeekableReadStream &stream) {
+ _header.width = stream.readUint16BE();
+ _header.height = stream.readUint16BE();
+ _header.x = stream.readUint16BE();
+ _header.y = stream.readUint16BE();
+ _header.numPlanes = stream.readByte();
+ _header.masking = stream.readByte();
+ _header.compression = stream.readByte();
+ _header.flags = stream.readByte();
+ _header.transparentColor = stream.readUint16BE();
+ _header.xAspect = stream.readByte();
+ _header.yAspect = stream.readByte();
+ _header.pageWidth = stream.readUint16BE();
+ _header.pageHeight = stream.readUint16BE();
+
+ assert(_header.width >= 1);
+ assert(_header.height >= 1);
+ assert(_header.numPlanes >= 1 && _header.numPlanes <= 8 && _header.numPlanes != 7);
+}
+
+void IFFDecoder::loadPalette(Common::SeekableReadStream &stream, const uint32 size) {
+ _palette = new byte[size];
+ stream.read(_palette, size);
+ _paletteColorCount = size / 3;
+}
+
+void IFFDecoder::loadPaletteRange(Common::SeekableReadStream &stream, const uint32 size) {
+ PaletteRange range;
+
+ range.timer = stream.readSint16BE();
+ range.step = stream.readSint16BE();
+ range.flags = stream.readSint16BE();
+ range.first = stream.readByte();
+ range.last = stream.readByte();
+
+ _paletteRanges.push_back(range);
+}
+
+void IFFDecoder::loadBitmap(Common::SeekableReadStream &stream) {
+ _numRelevantPlanes = MIN(_numRelevantPlanes, _header.numPlanes);
+
+ if (_numRelevantPlanes != 1 && _numRelevantPlanes != 2 && _numRelevantPlanes != 4)
+ _pixelPacking = false;
+
+ uint16 outPitch = _header.width;
+
+ if (_pixelPacking)
+ outPitch /= (8 / _numRelevantPlanes);
+
+ // FIXME: CLUT8 is not a proper format for packed bitmaps but there is no way to tell it to use 1, 2 or 4 bits per pixel
+ _surface = new Graphics::Surface();
+ _surface->create(outPitch, _header.height, Graphics::PixelFormat::createFormatCLUT8());
+
+ if (_type == TYPE_ILBM) {
+ uint32 scanlinePitch = ((_header.width + 15) >> 4) << 1;
+ byte *scanlines = new byte[scanlinePitch * _header.numPlanes];
+ byte *data = (byte *)_surface->getPixels();
+
+ for (uint16 i = 0; i < _header.height; ++i) {
+ byte *scanline = scanlines;
+
+ for (uint16 j = 0; j < _header.numPlanes; ++j) {
+ uint16 outSize = scanlinePitch;
+
+ if (_header.compression) {
+ Common::PackBitsReadStream packStream(stream);
+ packStream.read(scanline, outSize);
+ } else {
+ stream.read(scanline, outSize);
+ }
+
+ scanline += outSize;
+ }
+
+ packPixels(scanlines, data, scanlinePitch, outPitch);
+ data += outPitch;
+ }
+
+ delete[] scanlines;
+ } else if (_type == TYPE_PBM) {
+ byte *data = (byte *)_surface->getPixels();
+ uint32 outSize = _header.width * _header.height;
+
+ if (_header.compression) {
+ Common::PackBitsReadStream packStream(stream);
+ packStream.read(data, outSize);
+ } else {
+ stream.read(data, outSize);
+ }
+ }
+}
+
+void IFFDecoder::packPixels(byte *scanlines, byte *data, const uint16 scanlinePitch, const uint16 outPitch) {
+ uint32 numPixels = _header.width;
+
+ if (_pixelPacking)
+ numPixels = outPitch * (8 / _numRelevantPlanes);
+
+ for (uint32 x = 0; x < numPixels; ++x) {
+ byte *scanline = scanlines;
+ byte pixel = 0;
+ byte offset = x >> 3;
+ byte bit = 0x80 >> (x & 7);
+
+ // first build a pixel by scanning all the usable planes in the input
+ for (uint32 plane = 0; plane < _numRelevantPlanes; ++plane) {
+ if (scanline[offset] & bit)
+ pixel |= (1 << plane);
+
+ scanline += scanlinePitch;
+ }
+
+ // then output the pixel according to the requested packing
+ if (!_pixelPacking)
+ data[x] = pixel;
+ else if (_numRelevantPlanes == 1)
+ data[x / 8] |= (pixel << (x & 7));
+ else if (_numRelevantPlanes == 2)
+ data[x / 4] |= (pixel << ((x & 3) << 1));
+ else if (_numRelevantPlanes == 4)
+ data[x / 2] |= (pixel << ((x & 1) << 2));
+ }
+}
+
+} // End of namespace Image
diff --git a/image/iff.h b/image/iff.h
new file mode 100644
index 0000000000..3d342b3173
--- /dev/null
+++ b/image/iff.h
@@ -0,0 +1,126 @@
+/* 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.
+ *
+ */
+
+/**
+ * @file
+ * Image decoder used in engines:
+ * - gob
+ * - parallaction
+ * - queen
+ * - saga
+ */
+
+#ifndef IMAGE_IFF_H
+#define IMAGE_IFF_H
+
+#include "common/array.h"
+#include "common/endian.h"
+#include "graphics/surface.h"
+
+#include "image/image_decoder.h"
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Graphics {
+struct Surface;
+}
+
+namespace Image {
+
+class IFFDecoder : public ImageDecoder {
+public:
+ struct Header {
+ uint16 width, height;
+ uint16 x, y;
+ byte numPlanes;
+ byte masking;
+ byte compression;
+ byte flags;
+ uint16 transparentColor;
+ byte xAspect, yAspect;
+ uint16 pageWidth, pageHeight;
+ };
+
+ struct PaletteRange {
+ int16 timer, step, flags;
+ byte first, last;
+ };
+
+ enum Type {
+ TYPE_UNKNOWN = 0,
+ TYPE_ILBM,
+ TYPE_PBM
+ };
+
+ IFFDecoder();
+ virtual ~IFFDecoder();
+
+ // ImageDecoder API
+ void destroy();
+ bool loadStream(Common::SeekableReadStream &stream);
+ const Header *getHeader() const { return &_header; }
+ const Graphics::Surface *getSurface() const { return _surface; }
+ const byte *getPalette() const { return _palette; }
+ const Common::Array<PaletteRange> &getPaletteRanges() const { return _paletteRanges; }
+ uint16 getPaletteColorCount() const { return _paletteColorCount; }
+
+ /**
+ * The number of planes to decode, also determines the pixel packing if _packPixels is true.
+ * 8 == decode all planes, map 1 pixel in 1 byte. (default, no packing even if _packPixels is true)
+ *
+ * NOTE: this property must be reset manually, and is not reset by a call to destroy().
+ */
+ void setNumRelevantPlanes(const uint8 numRelevantPlanes) { _numRelevantPlanes = numRelevantPlanes; }
+
+ /**
+ * Enables pixel packing, the amount of packing is determined by _numRelevantPlanes
+ * 1 == decode first plane, pack 8 pixels in 1 byte. This makes _surface->w 1/8th of _header.width
+ * 2 == decode first 2 planes, pack 4 pixels in 1 byte. This makes _surface->w 1/4th of _header.width
+ * 4 == decode first 4 planes, pack 2 pixels in 1 byte. This makes _surface->w half of _header.width
+ * Packed bitmaps won't have a proper surface format since there is no way to tell it to use 1, 2 or 4 bits per pixel
+ *
+ * NOTE: this property must be reset manually, and is not reset by a call to destroy().
+ */
+ void setPixelPacking(const bool pixelPacking) { _pixelPacking = pixelPacking; }
+private:
+
+ Header _header;
+ Graphics::Surface *_surface;
+ byte *_palette;
+ Common::Array<PaletteRange> _paletteRanges;
+ Type _type;
+ uint16 _paletteColorCount;
+ uint8 _numRelevantPlanes;
+ bool _pixelPacking;
+
+ void loadHeader(Common::SeekableReadStream &stream);
+ void loadPalette(Common::SeekableReadStream &stream, const uint32 size);
+ void loadPaletteRange(Common::SeekableReadStream &stream, const uint32 size);
+ void loadBitmap(Common::SeekableReadStream &stream);
+ void packPixels(byte *scanlines, byte *data, const uint16 scanlinePitch, const uint16 outPitch);
+};
+
+} // End of namespace Image
+
+#endif
diff --git a/image/image_decoder.h b/image/image_decoder.h
new file mode 100644
index 0000000000..08fa4d82b4
--- /dev/null
+++ b/image/image_decoder.h
@@ -0,0 +1,106 @@
+/* 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 IMAGE_IMAGEDECODER_H
+#define IMAGE_IMAGEDECODER_H
+
+#include "common/scummsys.h"
+#include "common/str.h"
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Graphics {
+struct Surface;
+}
+
+namespace Image {
+
+/**
+ * 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
+ *
+ * loadStream() should implicitly call destroy() to free the memory
+ * of the last loadStream() call.
+ *
+ * @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
+ *
+ * This should be called by a loadStream() implementation as well
+ * as the destructor.
+ */
+ 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 Graphics::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.
+ *
+ * The palette's format is the same as PaletteManager's palette
+ * (interleaved RGB values).
+ *
+ * @return the decoded palette, or undefined if no palette is present
+ */
+ virtual const byte *getPalette() const { return 0; }
+
+ /**
+ * Query if the decoded image has a palette.
+ */
+ virtual bool hasPalette() const { return getPaletteColorCount() != 0; }
+
+ /** Return the starting index of the palette. */
+ virtual byte getPaletteStartIndex() const { return 0; }
+ /** Return the number of colors in the palette. */
+ virtual uint16 getPaletteColorCount() const { return 0; }
+};
+
+} // End of namespace Image
+
+#endif
diff --git a/image/jpeg.cpp b/image/jpeg.cpp
new file mode 100644
index 0000000000..9d4b0a7cfe
--- /dev/null
+++ b/image/jpeg.cpp
@@ -0,0 +1,266 @@
+/* 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.
+ *
+ */
+
+// libjpeg uses forbidden symbols in its header. Thus, we need to allow them
+// here.
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+
+#include "image/jpeg.h"
+
+#include "common/debug.h"
+#include "common/endian.h"
+#include "common/stream.h"
+#include "common/textconsole.h"
+#include "graphics/pixelformat.h"
+
+#ifdef USE_JPEG
+// The original release of libjpeg v6b did not contain any extern "C" in case
+// its header files are included in a C++ environment. To avoid any linking
+// issues we need to add it on our own.
+extern "C" {
+#include <jpeglib.h>
+#include <jerror.h>
+}
+#endif
+
+namespace Image {
+
+JPEGDecoder::JPEGDecoder() : ImageDecoder(), _surface(), _colorSpace(kColorSpaceRGBA) {
+}
+
+JPEGDecoder::~JPEGDecoder() {
+ destroy();
+}
+
+const Graphics::Surface *JPEGDecoder::getSurface() const {
+ return &_surface;
+}
+
+void JPEGDecoder::destroy() {
+ _surface.free();
+}
+
+#ifdef USE_JPEG
+namespace {
+
+#define JPEG_BUFFER_SIZE 4096
+
+struct StreamSource : public jpeg_source_mgr {
+ Common::SeekableReadStream *stream;
+ bool startOfFile;
+ JOCTET buffer[JPEG_BUFFER_SIZE];
+};
+
+void initSource(j_decompress_ptr cinfo) {
+ StreamSource *source = (StreamSource *)cinfo->src;
+ source->startOfFile = true;
+}
+
+boolean fillInputBuffer(j_decompress_ptr cinfo) {
+ StreamSource *source = (StreamSource *)cinfo->src;
+
+ uint32 bufferSize = source->stream->read((byte *)source->buffer, sizeof(source->buffer));
+ if (bufferSize == 0) {
+ if (source->startOfFile) {
+ // An empty file is a fatal error
+ ERREXIT(cinfo, JERR_INPUT_EMPTY);
+ } else {
+ // Otherwise we insert an EOF marker
+ WARNMS(cinfo, JWRN_JPEG_EOF);
+ source->buffer[0] = (JOCTET)0xFF;
+ source->buffer[1] = (JOCTET)JPEG_EOI;
+ bufferSize = 2;
+ }
+ }
+
+ source->next_input_byte = source->buffer;
+ source->bytes_in_buffer = bufferSize;
+ source->startOfFile = false;
+
+ return TRUE;
+}
+
+void skipInputData(j_decompress_ptr cinfo, long numBytes) {
+ StreamSource *source = (StreamSource *)cinfo->src;
+
+ if (numBytes > 0) {
+ if (numBytes > (long)source->bytes_in_buffer) {
+ // In case we need to skip more bytes than there are in the buffer
+ // we will skip the remaining data and fill the buffer again
+ numBytes -= (long)source->bytes_in_buffer;
+
+ // Skip the remaining bytes
+ source->stream->skip(numBytes);
+
+ // Fill up the buffer again
+ (*source->fill_input_buffer)(cinfo);
+ } else {
+ source->next_input_byte += (size_t)numBytes;
+ source->bytes_in_buffer -= (size_t)numBytes;
+ }
+
+ }
+}
+
+void termSource(j_decompress_ptr cinfo) {
+}
+
+void jpeg_scummvm_src(j_decompress_ptr cinfo, Common::SeekableReadStream *stream) {
+ StreamSource *source;
+
+ // Initialize the source in case it has not been done yet.
+ if (cinfo->src == NULL) {
+ cinfo->src = (jpeg_source_mgr *)(*cinfo->mem->alloc_small)((j_common_ptr)cinfo, JPOOL_PERMANENT, sizeof(StreamSource));
+ }
+
+ source = (StreamSource *)cinfo->src;
+ source->init_source = &initSource;
+ source->fill_input_buffer = &fillInputBuffer;
+ source->skip_input_data = &skipInputData;
+ source->resync_to_restart = &jpeg_resync_to_restart;
+ source->term_source = &termSource;
+ source->bytes_in_buffer = 0;
+ source->next_input_byte = NULL;
+
+ source->stream = stream;
+}
+
+void errorExit(j_common_ptr cinfo) {
+ char buffer[JMSG_LENGTH_MAX];
+ (*cinfo->err->format_message)(cinfo, buffer);
+ // This function is not allowed to return to the caller, thus we simply
+ // error out with our error handling here.
+ error("%s", buffer);
+}
+
+void outputMessage(j_common_ptr cinfo) {
+ char buffer[JMSG_LENGTH_MAX];
+ (*cinfo->err->format_message)(cinfo, buffer);
+ // Is using debug here a good idea? Or do we want to ignore all libjpeg
+ // messages?
+ debug(3, "libjpeg: %s", buffer);
+}
+
+} // End of anonymous namespace
+#endif
+
+bool JPEGDecoder::loadStream(Common::SeekableReadStream &stream) {
+#ifdef USE_JPEG
+ // Reset member variables from previous decodings
+ destroy();
+
+ jpeg_decompress_struct cinfo;
+ jpeg_error_mgr jerr;
+
+ // Initialize error handling callbacks
+ cinfo.err = jpeg_std_error(&jerr);
+ cinfo.err->error_exit = &errorExit;
+ cinfo.err->output_message = &outputMessage;
+
+ // Initialize the decompression structure
+ jpeg_create_decompress(&cinfo);
+
+ // Initialize our buffer handling
+ jpeg_scummvm_src(&cinfo, &stream);
+
+ // Read the file header
+ jpeg_read_header(&cinfo, TRUE);
+
+ // We can request YUV output because Groovie requires it
+ switch (_colorSpace) {
+ case kColorSpaceRGBA:
+ cinfo.out_color_space = JCS_RGB;
+ break;
+
+ case kColorSpaceYUV:
+ cinfo.out_color_space = JCS_YCbCr;
+ break;
+ }
+
+ // Actually start decompressing the image
+ jpeg_start_decompress(&cinfo);
+
+ // Allocate buffers for the output data
+ switch (_colorSpace) {
+ case kColorSpaceRGBA:
+ // We use RGBA8888 in this scenario
+ _surface.create(cinfo.output_width, cinfo.output_height, Graphics::PixelFormat(4, 8, 8, 8, 0, 24, 16, 8, 0));
+ break;
+
+ case kColorSpaceYUV:
+ // We use YUV with 3 bytes per pixel otherwise.
+ // This is pretty ugly since our PixelFormat cannot express YUV...
+ _surface.create(cinfo.output_width, cinfo.output_height, Graphics::PixelFormat(3, 0, 0, 0, 0, 0, 0, 0, 0));
+ break;
+ }
+
+ // Allocate buffer for one scanline
+ assert(cinfo.output_components == 3);
+ JDIMENSION pitch = cinfo.output_width * cinfo.output_components;
+ assert(_surface.pitch >= pitch);
+ JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, pitch, 1);
+
+ // Go through the image data scanline by scanline
+ while (cinfo.output_scanline < cinfo.output_height) {
+ byte *dst = (byte *)_surface.getBasePtr(0, cinfo.output_scanline);
+
+ jpeg_read_scanlines(&cinfo, buffer, 1);
+
+ const byte *src = buffer[0];
+ switch (_colorSpace) {
+ case kColorSpaceRGBA: {
+ for (int remaining = cinfo.output_width; remaining > 0; --remaining) {
+ byte r = *src++;
+ byte g = *src++;
+ byte b = *src++;
+ // We need to insert a alpha value of 255 (opaque) here.
+#ifdef SCUMM_BIG_ENDIAN
+ *dst++ = r;
+ *dst++ = g;
+ *dst++ = b;
+ *dst++ = 0xFF;
+#else
+ *dst++ = 0xFF;
+ *dst++ = b;
+ *dst++ = g;
+ *dst++ = r;
+#endif
+ }
+ } break;
+
+ case kColorSpaceYUV:
+ memcpy(dst, src, pitch);
+ break;
+ }
+ }
+
+ // We are done with decompressing, thus free all the data
+ jpeg_finish_decompress(&cinfo);
+ jpeg_destroy_decompress(&cinfo);
+
+ return true;
+#else
+ return false;
+#endif
+}
+
+} // End of Graphics namespace
diff --git a/image/jpeg.h b/image/jpeg.h
new file mode 100644
index 0000000000..f578170517
--- /dev/null
+++ b/image/jpeg.h
@@ -0,0 +1,95 @@
+/* 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.
+ *
+ */
+
+/**
+ * @file
+ * Image decoder used in engines:
+ * - groovie
+ * - mohawk
+ * - wintermute
+ */
+
+#ifndef IMAGE_JPEG_H
+#define IMAGE_JPEG_H
+
+#include "graphics/surface.h"
+#include "image/image_decoder.h"
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Image {
+
+class JPEGDecoder : public ImageDecoder {
+public:
+ JPEGDecoder();
+ ~JPEGDecoder();
+
+ // ImageDecoder API
+ virtual void destroy();
+ virtual bool loadStream(Common::SeekableReadStream &str);
+ virtual const Graphics::Surface *getSurface() const;
+
+ // Special API for JPEG
+ enum ColorSpace {
+ /**
+ * Output 32bit RGBA data.
+ *
+ * This is the default output.
+ */
+ kColorSpaceRGBA,
+
+ /**
+ * Output (interleaved) YUV data.
+ *
+ * Be aware that some images cannot be output in YUV mode.
+ * These are (non-standard) JPEG images which are in RGB colorspace.
+ *
+ * The resulting Surface will have a PixelFormat with 3 bytes per
+ * pixel and the remaining entries are completely zeroed. This works
+ * around the fact that PixelFormat can only describe RGB formats.
+ *
+ * You should only use this when you are really aware of what you are
+ * doing!
+ */
+ kColorSpaceYUV
+ };
+
+ /**
+ * Request the output color space. This can be used to obtain raw YUV
+ * data from the JPEG file. But this might not work for all files!
+ *
+ * The decoder itself defaults to RGBA.
+ *
+ * @param outSpace The color space to output.
+ */
+ void setOutputColorSpace(ColorSpace outSpace) { _colorSpace = outSpace; }
+
+private:
+ Graphics::Surface _surface;
+ ColorSpace _colorSpace;
+};
+
+} // End of Graphics namespace
+
+#endif // GRAPHICS_JPEG_H
diff --git a/image/module.mk b/image/module.mk
new file mode 100644
index 0000000000..22febe1460
--- /dev/null
+++ b/image/module.mk
@@ -0,0 +1,13 @@
+MODULE := image
+
+MODULE_OBJS := \
+ bmp.o \
+ iff.o \
+ jpeg.o \
+ pcx.o \
+ pict.o \
+ png.o \
+ tga.o
+
+# Include common rules
+include $(srcdir)/rules.mk
diff --git a/image/pcx.cpp b/image/pcx.cpp
new file mode 100644
index 0000000000..282a6c68c7
--- /dev/null
+++ b/image/pcx.cpp
@@ -0,0 +1,214 @@
+/* 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 "image/pcx.h"
+
+#include "common/stream.h"
+#include "common/textconsole.h"
+#include "graphics/pixelformat.h"
+#include "graphics/surface.h"
+
+/**
+ * Based on the PCX specs:
+ * http://www.fileformat.info/format/pcx/spec/a10e75307b3a4cc49c3bbe6db4c41fa2/view.htm
+ * and the PCX decoder of FFmpeg (libavcodec/pcx.c):
+ * http://git.videolan.org/?p=ffmpeg.git;a=blob;f=libavcodec/pcx.c
+ */
+
+namespace Image {
+
+PCXDecoder::PCXDecoder() {
+ _surface = 0;
+ _palette = 0;
+ _paletteColorCount = 0;
+}
+
+PCXDecoder::~PCXDecoder() {
+ destroy();
+}
+
+void PCXDecoder::destroy() {
+ if (_surface) {
+ _surface->free();
+ delete _surface;
+ _surface = 0;
+ }
+
+ delete[] _palette;
+ _palette = 0;
+ _paletteColorCount = 0;
+}
+
+bool PCXDecoder::loadStream(Common::SeekableReadStream &stream) {
+ destroy();
+
+ if (stream.readByte() != 0x0a) // ZSoft PCX
+ return false;
+
+ byte version = stream.readByte(); // 0 - 5
+ if (version > 5)
+ return false;
+
+ bool compressed = stream.readByte(); // encoding, 1 = run length encoding
+ byte bitsPerPixel = stream.readByte(); // 1, 2, 4 or 8
+
+ // Window
+ uint16 xMin = stream.readUint16LE();
+ uint16 yMin = stream.readUint16LE();
+ uint16 xMax = stream.readUint16LE();
+ uint16 yMax = stream.readUint16LE();
+
+ uint16 width = xMax - xMin + 1;
+ uint16 height = yMax - yMin + 1;
+
+ if (xMax < xMin || yMax < yMin) {
+ warning("Invalid PCX image dimensions");
+ return false;
+ }
+
+ stream.skip(4); // HDpi, VDpi
+
+ // Read the EGA palette (colormap)
+ _palette = new byte[16 * 3];
+ for (uint16 i = 0; i < 16; i++) {
+ _palette[i * 3 + 0] = stream.readByte();
+ _palette[i * 3 + 1] = stream.readByte();
+ _palette[i * 3 + 2] = stream.readByte();
+ }
+
+ if (stream.readByte() != 0) // reserved, should be set to 0
+ return false;
+
+ byte nPlanes = stream.readByte();
+ uint16 bytesPerLine = stream.readUint16LE();
+ uint16 bytesPerscanLine = nPlanes * bytesPerLine;
+
+ if (bytesPerscanLine < width * bitsPerPixel * nPlanes / 8) {
+ warning("PCX data is corrupted");
+ return false;
+ }
+
+ stream.skip(60); // PaletteInfo, HscreenSize, VscreenSize, Filler
+
+ _surface = new Graphics::Surface();
+
+ byte *scanLine = new byte[bytesPerscanLine];
+ byte *dst;
+ int x, y;
+
+ if (nPlanes == 3 && bitsPerPixel == 8) { // 24bpp
+ Graphics::PixelFormat format = Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0);
+ _surface->create(width, height, format);
+ dst = (byte *)_surface->getPixels();
+ _paletteColorCount = 0;
+
+ for (y = 0; y < height; y++) {
+ decodeRLE(stream, scanLine, bytesPerscanLine, compressed);
+
+ for (x = 0; x < width; x++) {
+ byte b = scanLine[x];
+ byte g = scanLine[x + bytesPerLine];
+ byte r = scanLine[x + (bytesPerLine << 1)];
+ uint32 color = format.RGBToColor(r, g, b);
+
+ *((uint32 *)dst) = color;
+ dst += format.bytesPerPixel;
+ }
+ }
+ } else if (nPlanes == 1 && bitsPerPixel == 8) { // 8bpp indexed
+ _surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8());
+ dst = (byte *)_surface->getPixels();
+ _paletteColorCount = 16;
+
+ for (y = 0; y < height; y++, dst += _surface->pitch) {
+ decodeRLE(stream, scanLine, bytesPerscanLine, compressed);
+ memcpy(dst, scanLine, width);
+ }
+
+ if (version == 5) {
+ if (stream.readByte() != 12) {
+ warning("Expected a palette after the PCX image data");
+ delete[] scanLine;
+ return false;
+ }
+
+ // Read the VGA palette
+ delete[] _palette;
+ _palette = new byte[256 * 3];
+ for (uint16 i = 0; i < 256; i++) {
+ _palette[i * 3 + 0] = stream.readByte();
+ _palette[i * 3 + 1] = stream.readByte();
+ _palette[i * 3 + 2] = stream.readByte();
+ }
+
+ _paletteColorCount = 256;
+ }
+ } else if ((nPlanes == 2 || nPlanes == 3 || nPlanes == 4) && bitsPerPixel == 1) { // planar, 4, 8 or 16 colors
+ _surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8());
+ dst = (byte *)_surface->getPixels();
+ _paletteColorCount = 16;
+
+ for (y = 0; y < height; y++, dst += _surface->pitch) {
+ decodeRLE(stream, scanLine, bytesPerscanLine, compressed);
+
+ for (x = 0; x < width; x++) {
+ int m = 0x80 >> (x & 7), v = 0;
+ for (int i = nPlanes - 1; i >= 0; i--) {
+ v <<= 1;
+ v += (scanLine[i * bytesPerLine + (x >> 3)] & m) == 0 ? 0 : 1;
+ }
+ dst[x] = v;
+ }
+ }
+ } else {
+ // Known unsupported case: 1 plane and bpp < 8 (1, 2 or 4)
+ warning("Invalid PCX file (%d planes, %d bpp)", nPlanes, bitsPerPixel);
+ delete[] scanLine;
+ return false;
+ }
+
+ delete[] scanLine;
+
+ return true;
+}
+
+void PCXDecoder::decodeRLE(Common::SeekableReadStream &stream, byte *dst, uint32 bytesPerscanLine, bool compressed) {
+ uint32 i = 0;
+ byte run, value;
+
+ if (compressed) {
+ while (i < bytesPerscanLine) {
+ run = 1;
+ value = stream.readByte();
+ if (value >= 0xc0) {
+ run = value & 0x3f;
+ value = stream.readByte();
+ }
+ while (i < bytesPerscanLine && run--)
+ dst[i++] = value;
+ }
+ } else {
+ stream.read(dst, bytesPerscanLine);
+ }
+}
+
+} // End of namespace Image
diff --git a/image/pcx.h b/image/pcx.h
new file mode 100644
index 0000000000..ce30ab5eb2
--- /dev/null
+++ b/image/pcx.h
@@ -0,0 +1,66 @@
+/* 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.
+ *
+ */
+
+/**
+ * PCX decoder used in engines:
+ * - dreamweb
+ * - hugo
+ * - queen
+ * - tucker
+ */
+
+#ifndef IMAGE_PCX_H
+#define IMAGE_PCX_H
+
+#include "common/scummsys.h"
+#include "common/str.h"
+#include "image/image_decoder.h"
+
+namespace Common{
+class SeekableReadStream;
+}
+
+namespace Image {
+
+class PCXDecoder : public ImageDecoder {
+public:
+ PCXDecoder();
+ virtual ~PCXDecoder();
+
+ // ImageDecoder API
+ void destroy();
+ virtual bool loadStream(Common::SeekableReadStream &stream);
+ virtual const Graphics::Surface *getSurface() const { return _surface; }
+ const byte *getPalette() const { return _palette; }
+ uint16 getPaletteColorCount() const { return _paletteColorCount; }
+
+private:
+ void decodeRLE(Common::SeekableReadStream &stream, byte *dst, uint32 bytesPerScanline, bool compressed);
+
+ Graphics::Surface *_surface;
+ byte *_palette;
+ uint16 _paletteColorCount;
+};
+
+} // End of namespace Image
+
+#endif
diff --git a/image/pict.cpp b/image/pict.cpp
new file mode 100644
index 0000000000..07e657f4b2
--- /dev/null
+++ b/image/pict.cpp
@@ -0,0 +1,580 @@
+/* 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 "image/jpeg.h"
+#include "image/pict.h"
+
+#include "common/debug.h"
+#include "common/endian.h"
+#include "common/stream.h"
+#include "common/substream.h"
+#include "common/textconsole.h"
+#include "graphics/surface.h"
+
+namespace Image {
+
+// 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;
+ _paletteColorCount = 0;
+}
+
+PICTDecoder::~PICTDecoder() {
+ destroy();
+}
+
+void PICTDecoder::destroy() {
+ if (_outputSurface) {
+ _outputSurface->free();
+ delete _outputSurface;
+ _outputSurface = 0;
+ }
+
+ _paletteColorCount = 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) {
+ warning("Cannot find PICT version opcode");
+ return false;
+ } else if (opNum == 1 && opcode != 0x0C00) {
+ warning("Cannot find PICT header opcode");
+ return false;
+ }
+
+ // 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 withPalette) {
+ PackBitsRectData packBitsData;
+ packBitsData.pixMap = readPixMap(stream, !withPalette);
+
+ // Read in the palette if there is one present
+ if (withPalette) {
+ // See http://developer.apple.com/legacy/mac/library/documentation/mac/QuickDraw/QuickDraw-267.html
+ stream.readUint32BE(); // seed
+ stream.readUint16BE(); // flags
+ _paletteColorCount = stream.readUint16BE() + 1;
+
+ for (uint32 i = 0; i < _paletteColorCount; 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, Graphics::PixelFormat::createFormatCLUT8());
+ memcpy(_outputSurface->getPixels(), buffer, _outputSurface->w * _outputSurface->h);
+ break;
+ case 2:
+ // We have a 16-bit surface
+ _outputSurface->create(width, height, Graphics::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:
+ // We have a planar 24-bit surface
+ _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:
+ // We have a planar 32-bit surface
+ // Note that we ignore the alpha channel since it seems to not be correct
+ // Mac OS X does not ignore it, but then displays it incorrectly. Photoshop
+ // does ignore it and displays it correctly.
+ _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 a = 0xFF;
+ byte r = *(buffer + y * _outputSurface->w * 4 + _outputSurface->w + x);
+ byte g = *(buffer + y * _outputSurface->w * 4 + _outputSurface->w * 2 + x);
+ byte b = *(buffer + y * _outputSurface->w * 4 + _outputSurface->w * 3 + x);
+ *((uint32 *)_outputSurface->getBasePtr(x, y)) = _outputSurface->format.ARGBToColor(a, r, g, b);
+ }
+ }
+ 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 withPalette) {
+ // Step through a PackBitsRect/DirectBitsRect function
+
+ if (!withPalette)
+ 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 (withPalette) {
+ 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();
+ uint32 codec = stream.readUint32BE();
+ stream.skip(36); // miscellaneous stuff
+ uint32 jpegSize = stream.readUint32BE();
+ stream.skip(idSize - (stream.pos() - idStart)); // more useless stuff
+
+ if (codec != MKTAG('j', 'p', 'e', 'g'))
+ error("Unhandled CompressedQuickTime format '%s'", tag2str(codec));
+
+ 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 Image
diff --git a/image/pict.h b/image/pict.h
new file mode 100644
index 0000000000..77b450f4a2
--- /dev/null
+++ b/image/pict.h
@@ -0,0 +1,141 @@
+/* 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.
+ *
+ */
+
+/**
+ * @file
+ * Image decoder used in engines:
+ * - mohawk
+ * - pegasus
+ * - sci
+ */
+
+#ifndef IMAGE_PICT_H
+#define IMAGE_PICT_H
+
+#include "common/array.h"
+#include "common/rect.h"
+#include "common/scummsys.h"
+
+#include "image/image_decoder.h"
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Graphics {
+struct Surface;
+}
+
+namespace Image {
+
+#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 Graphics::Surface *getSurface() const { return _outputSurface; }
+ const byte *getPalette() const { return _palette; }
+ uint16 getPaletteColorCount() const { return _paletteColorCount; }
+
+ 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];
+ uint16 _paletteColorCount;
+ Graphics::Surface *_outputSurface;
+ bool _continueParsing;
+
+ // Utility Functions
+ void unpackBitsRect(Common::SeekableReadStream &stream, bool withPalette);
+ void unpackBitsLine(byte *out, uint32 length, Common::SeekableReadStream *stream, byte bitsPerPixel, byte bytesPerPixel);
+ void skipBitsRect(Common::SeekableReadStream &stream, bool withPalette);
+ 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 Image
+
+#endif
diff --git a/image/png.cpp b/image/png.cpp
new file mode 100644
index 0000000000..158acfa8a5
--- /dev/null
+++ b/image/png.cpp
@@ -0,0 +1,245 @@
+/* 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.
+ *
+ */
+
+// Since we need to work with libpng here, we need to allow all symbols
+// to avoid compilation issues.
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+#include "common/scummsys.h"
+
+#ifdef USE_PNG
+#include <png.h>
+#endif
+
+#include "image/png.h"
+
+#include "graphics/pixelformat.h"
+#include "graphics/surface.h"
+
+#include "common/stream.h"
+
+namespace Image {
+
+PNGDecoder::PNGDecoder() : _outputSurface(0), _palette(0), _paletteColorCount(0), _stream(0) {
+}
+
+PNGDecoder::~PNGDecoder() {
+ destroy();
+}
+
+void PNGDecoder::destroy() {
+ if (_outputSurface) {
+ _outputSurface->free();
+ delete _outputSurface;
+ _outputSurface = 0;
+ }
+ delete[] _palette;
+ _palette = NULL;
+}
+
+#ifdef USE_PNG
+// libpng-error-handling:
+void pngError(png_structp pngptr, png_const_charp errorMsg) {
+ error("%s", errorMsg);
+}
+
+void pngWarning(png_structp pngptr, png_const_charp warningMsg) {
+ warning("%s", warningMsg);
+}
+
+// libpng-I/O-helper:
+void pngReadFromStream(png_structp pngPtr, png_bytep data, png_size_t length) {
+ void *readIOptr = png_get_io_ptr(pngPtr);
+ Common::SeekableReadStream *stream = (Common::SeekableReadStream *)readIOptr;
+ stream->read(data, length);
+}
+#endif
+
+/*
+ * This code is based on Broken Sword 2.5 engine
+ *
+ * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer
+ *
+ * Licensed under GNU GPL v2
+ *
+ */
+
+bool PNGDecoder::loadStream(Common::SeekableReadStream &stream) {
+#ifdef USE_PNG
+ destroy();
+
+ _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;
+ }
+
+ // The following is based on the guide provided in:
+ //http://www.libpng.org/pub/png/libpng-1.2.5-manual.html#section-3
+ //http://www.libpng.org/pub/png/libpng-1.4.0-manual.pdf
+ // along with the png-loading code used in the sword25-engine.
+ png_structp pngPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+ if (!pngPtr) {
+ delete _stream;
+ return false;
+ }
+ png_infop infoPtr = png_create_info_struct(pngPtr);
+ if (!infoPtr) {
+ png_destroy_read_struct(&pngPtr, NULL, NULL);
+ delete _stream;
+ return false;
+ }
+ png_infop endInfo = png_create_info_struct(pngPtr);
+ if (!endInfo) {
+ png_destroy_read_struct(&pngPtr, &infoPtr, NULL);
+ delete _stream;
+ return false;
+ }
+
+ png_set_error_fn(pngPtr, NULL, pngError, pngWarning);
+ // TODO: The manual says errors should be handled via setjmp
+
+ png_set_read_fn(pngPtr, _stream, pngReadFromStream);
+ png_set_crc_action(pngPtr, PNG_CRC_DEFAULT, PNG_CRC_WARN_USE);
+ // We already verified the PNG-header
+ png_set_sig_bytes(pngPtr, 8);
+
+ // Read PNG header
+ png_read_info(pngPtr, infoPtr);
+
+ // No handling for unknown chunks yet.
+ int bitDepth, colorType, width, height, interlaceType;
+ png_uint_32 w, h;
+ png_get_IHDR(pngPtr, infoPtr, &w, &h, &bitDepth, &colorType, &interlaceType, NULL, NULL);
+ width = w;
+ height = h;
+
+ // Allocate memory for the final image data.
+ // To keep memory framentation low this happens before allocating memory for temporary image data.
+ _outputSurface = new Graphics::Surface();
+
+ // Images of all color formats except PNG_COLOR_TYPE_PALETTE
+ // will be transformed into ARGB images
+ if (colorType == PNG_COLOR_TYPE_PALETTE && !png_get_valid(pngPtr, infoPtr, PNG_INFO_tRNS)) {
+ int numPalette = 0;
+ png_colorp palette = NULL;
+ uint32 success = png_get_PLTE(pngPtr, infoPtr, &palette, &numPalette);
+ if (success != PNG_INFO_PLTE) {
+ png_destroy_read_struct(&pngPtr, &infoPtr, NULL);
+ return false;
+ }
+ _paletteColorCount = numPalette;
+ _palette = new byte[_paletteColorCount * 3];
+ for (int i = 0; i < _paletteColorCount; i++) {
+ _palette[(i * 3)] = palette[i].red;
+ _palette[(i * 3) + 1] = palette[i].green;
+ _palette[(i * 3) + 2] = palette[i].blue;
+
+ }
+ _outputSurface->create(width, height, Graphics::PixelFormat::createFormatCLUT8());
+ png_set_packing(pngPtr);
+ } else {
+ bool isAlpha = (colorType & PNG_COLOR_MASK_ALPHA);
+ if (png_get_valid(pngPtr, infoPtr, PNG_INFO_tRNS)) {
+ isAlpha = true;
+ png_set_expand(pngPtr);
+ }
+ _outputSurface->create(width, height, Graphics::PixelFormat(4,
+ 8, 8, 8, isAlpha ? 8 : 0, 24, 16, 8, 0));
+ if (!_outputSurface->getPixels()) {
+ error("Could not allocate memory for output image.");
+ }
+ if (bitDepth == 16)
+ png_set_strip_16(pngPtr);
+ if (bitDepth < 8)
+ png_set_expand(pngPtr);
+ if (colorType == PNG_COLOR_TYPE_GRAY ||
+ colorType == PNG_COLOR_TYPE_GRAY_ALPHA)
+ png_set_gray_to_rgb(pngPtr);
+
+ // PNGs are Big-Endian:
+#ifdef SCUMM_LITTLE_ENDIAN
+ png_set_bgr(pngPtr);
+ png_set_swap_alpha(pngPtr);
+ if (colorType != PNG_COLOR_TYPE_RGB_ALPHA)
+ png_set_filler(pngPtr, 0xff, PNG_FILLER_BEFORE);
+#else
+ if (colorType != PNG_COLOR_TYPE_RGB_ALPHA)
+ png_set_filler(pngPtr, 0xff, PNG_FILLER_AFTER);
+#endif
+
+ }
+
+ // After the transformations have been registered, the image data is read again.
+ png_set_interlace_handling(pngPtr);
+ png_read_update_info(pngPtr, infoPtr);
+ png_get_IHDR(pngPtr, infoPtr, &w, &h, &bitDepth, &colorType, NULL, NULL, NULL);
+ width = w;
+ height = h;
+
+ if (interlaceType == PNG_INTERLACE_NONE) {
+ // PNGs without interlacing can simply be read row by row.
+ for (int i = 0; i < height; i++) {
+ png_read_row(pngPtr, (png_bytep)_outputSurface->getBasePtr(0, i), NULL);
+ }
+ } else {
+ // PNGs with interlacing require us to allocate an auxillary
+ // buffer with pointers to all row starts.
+
+ // Allocate row pointer buffer
+ png_bytep *rowPtr = new png_bytep[height];
+ if (!rowPtr) {
+ error("Could not allocate memory for row pointers.");
+ }
+
+ // Initialize row pointers
+ for (int i = 0; i < height; i++)
+ rowPtr[i] = (png_bytep)_outputSurface->getBasePtr(0, i);
+
+ // Read image data
+ png_read_image(pngPtr, rowPtr);
+
+ // Free row pointer buffer
+ delete[] rowPtr;
+ }
+
+ // Read additional data at the end.
+ png_read_end(pngPtr, NULL);
+
+ // Destroy libpng structures
+ png_destroy_read_struct(&pngPtr, &infoPtr, &endInfo);
+
+ // We no longer need the file stream, thus close it here
+ _stream = 0;
+
+ return true;
+#else
+ return false;
+#endif
+}
+
+} // End of namespace Image
diff --git a/image/png.h b/image/png.h
new file mode 100644
index 0000000000..c4532fd03b
--- /dev/null
+++ b/image/png.h
@@ -0,0 +1,68 @@
+/* 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
+ * - wintermute
+ * Dependencies:
+ * - libpng
+ */
+
+#ifndef IMAGE_PNG_H
+#define IMAGE_PNG_H
+
+#include "common/scummsys.h"
+#include "common/textconsole.h"
+#include "image/image_decoder.h"
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Graphics {
+struct Surface;
+}
+
+namespace Image {
+
+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; }
+ uint16 getPaletteColorCount() const { return _paletteColorCount; }
+private:
+ Common::SeekableReadStream *_stream;
+ byte *_palette;
+ uint16 _paletteColorCount;
+
+ Graphics::Surface *_outputSurface;
+};
+
+} // End of namespace Image
+
+#endif
diff --git a/image/tga.cpp b/image/tga.cpp
new file mode 100644
index 0000000000..e251f64677
--- /dev/null
+++ b/image/tga.cpp
@@ -0,0 +1,430 @@
+/* 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.
+ *
+ */
+
+/* Based on code from xoreos https://github.com/DrMcCoy/xoreos/
+ * relicensed under GPLv2+ with permission from DrMcCoy and clone2727
+ */
+
+#include "image/tga.h"
+
+#include "common/util.h"
+#include "common/stream.h"
+#include "common/textconsole.h"
+#include "common/error.h"
+
+namespace Image {
+
+TGADecoder::TGADecoder() {
+ _colorMapSize = 0;
+ _colorMapOrigin = 0;
+ _colorMapLength = 0;
+ _colorMapEntryLength = 0;
+ _colorMap = NULL;
+}
+
+TGADecoder::~TGADecoder() {
+ destroy();
+}
+
+void TGADecoder::destroy() {
+ _surface.free();
+ delete[] _colorMap;
+}
+
+bool TGADecoder::loadStream(Common::SeekableReadStream &tga) {
+ destroy();
+
+ byte imageType, pixelDepth;
+ bool success;
+ success = readHeader(tga, imageType, pixelDepth);
+ if (success) {
+ switch (imageType) {
+ case TYPE_BW:
+ case TYPE_TRUECOLOR:
+ success = readData(tga, imageType, pixelDepth);
+ break;
+ case TYPE_RLE_BW:
+ case TYPE_RLE_TRUECOLOR:
+ case TYPE_RLE_CMAP:
+ success = readDataRLE(tga, imageType, pixelDepth);
+ break;
+ case TYPE_CMAP:
+ success = readDataColorMapped(tga, imageType, pixelDepth);
+ break;
+ default:
+ success = false;
+ break;
+ }
+ }
+ if (tga.err() || !success) {
+ warning("Failed reading TGA-file");
+ return false;
+ }
+ return success;
+}
+
+bool TGADecoder::readHeader(Common::SeekableReadStream &tga, byte &imageType, byte &pixelDepth) {
+ if (!tga.seek(0)) {
+ warning("Failed reading TGA-file");
+ return false;
+ }
+
+ // TGAs have an optional "id" string in the header
+ uint32 idLength = tga.readByte();
+
+ // Number of colors in the color map / palette
+ int hasColorMap = tga.readByte();
+
+ // Image type. See header for numeric constants
+ imageType = tga.readByte();
+
+ switch (imageType) {
+ case TYPE_CMAP:
+ case TYPE_TRUECOLOR:
+ case TYPE_BW:
+ case TYPE_RLE_CMAP:
+ case TYPE_RLE_TRUECOLOR:
+ case TYPE_RLE_BW:
+ break;
+ default:
+ warning("Unsupported image type: %d", imageType);
+ return false;
+ }
+
+ // Color map specifications
+ if (hasColorMap == 0) {
+ tga.skip(5);
+ } else {
+ _colorMapOrigin = tga.readUint16LE();
+ _colorMapLength = tga.readUint16LE();
+ _colorMapEntryLength = tga.readByte();
+ }
+ // Origin-defintions
+ tga.skip(2 + 2);
+
+ // Image dimensions
+ _surface.w = tga.readUint16LE();
+ _surface.h = tga.readUint16LE();
+
+ // Bits per pixel
+ pixelDepth = tga.readByte();
+ _surface.format.bytesPerPixel = pixelDepth / 8;
+
+ // Image descriptor
+ byte imgDesc = tga.readByte();
+ int attributeBits = imgDesc & 0x0F;
+ assert((imgDesc & 0x10) == 0);
+ _originTop = (imgDesc & 0x20);
+
+ // Interleaving is not handled at this point
+ //int interleave = (imgDesc & 0xC);
+ if (imageType == TYPE_CMAP || imageType == TYPE_RLE_CMAP) {
+ if (pixelDepth == 8) {
+ _format = Graphics::PixelFormat::createFormatCLUT8();
+ } else {
+ warning("Unsupported index-depth: %d", pixelDepth);
+ return false;
+ }
+ } else if (imageType == TYPE_TRUECOLOR || imageType == TYPE_RLE_TRUECOLOR) {
+ if (pixelDepth == 24) {
+ _format = Graphics::PixelFormat(3, 8, 8, 8, 0, 16, 8, 0, 0);
+ } else if (pixelDepth == 32) {
+ // HACK: According to the spec, attributeBits should determine the amount
+ // of alpha-bits, however, as the game files that use this decoder seems
+ // to ignore that fact, we force the amount to 8 for 32bpp files for now.
+ _format = Graphics::PixelFormat(4, 8, 8, 8, /* attributeBits */ 8, 16, 8, 0, 24);
+ } else if (pixelDepth == 16 && imageType == TYPE_TRUECOLOR) {
+ // 16bpp TGA is ARGB1555
+ _format = Graphics::PixelFormat(2, 5, 5, 5, attributeBits, 10, 5, 0, 15);
+ } else {
+ warning("Unsupported pixel depth: %d, %d", imageType, pixelDepth);
+ return false;
+ }
+ } else if (imageType == TYPE_BW || TYPE_RLE_BW) {
+ if (pixelDepth == 8) {
+ _format = Graphics::PixelFormat(4, 8, 8, 8, 0, 16, 8, 0, 0);
+ } else {
+ warning("Unsupported pixel depth: %d, %d", imageType, pixelDepth);
+ return false;
+ }
+
+ } else {
+ warning("Unsupported image type: %d", imageType);
+ return false;
+ }
+
+ // Skip the id string
+ tga.skip(idLength);
+
+ if (hasColorMap) {
+ return readColorMap(tga, imageType, pixelDepth);
+ }
+ return true;
+}
+
+bool TGADecoder::readColorMap(Common::SeekableReadStream &tga, byte imageType, byte pixelDepth) {
+ _colorMap = new byte[3 * _colorMapLength];
+ for (int i = 0; i < _colorMapLength * 3; i += 3) {
+ byte r, g, b;
+ if (_colorMapEntryLength == 32) {
+ byte a;
+ Graphics::PixelFormat format(4, 8, 8, 8, 0, 16, 8, 0, 24);
+ uint32 color = tga.readUint32LE();
+ format.colorToARGB(color, a, r, g, b);
+ } else if (_colorMapEntryLength == 24) {
+ r = tga.readByte();
+ g = tga.readByte();
+ b = tga.readByte();
+ } else if (_colorMapEntryLength == 16) {
+ byte a;
+ Graphics::PixelFormat format(2, 5, 5, 5, 0, 10, 5, 0, 15);
+ uint16 color = tga.readUint16LE();
+ format.colorToARGB(color, a, r, g, b);
+ } else {
+ warning("Unsupported image type: %d", imageType);
+ r = g = b = 0;
+ }
+#ifdef SCUMM_LITTLE_ENDIAN
+ _colorMap[i] = r;
+ _colorMap[i + 1] = g;
+ _colorMap[i + 2] = b;
+#else
+ _colorMap[i] = b;
+ _colorMap[i + 1] = g;
+ _colorMap[i + 2] = r;
+#endif
+ }
+ return true;
+}
+
+// Additional information found from http://paulbourke.net/dataformats/tga/
+// With some details from the link referenced in the header.
+bool TGADecoder::readData(Common::SeekableReadStream &tga, byte imageType, byte pixelDepth) {
+ // TrueColor
+ if (imageType == TYPE_TRUECOLOR) {
+ _surface.create(_surface.w, _surface.h, _format);
+
+ if (pixelDepth == 16) {
+ for (int i = 0; i < _surface.h; i++) {
+ uint16 *dst;
+ if (!_originTop) {
+ dst = (uint16 *)_surface.getBasePtr(0, _surface.h - i - 1);
+ } else {
+ dst = (uint16 *)_surface.getBasePtr(0, i);
+ }
+ for (int j = 0; j < _surface.w; j++) {
+ *dst++ = tga.readUint16LE();
+ }
+ }
+ } else if (pixelDepth == 32) {
+ for (int i = 0; i < _surface.h; i++) {
+ uint32 *dst;
+ if (!_originTop) {
+ dst = (uint32 *)_surface.getBasePtr(0, _surface.h - i - 1);
+ } else {
+ dst = (uint32 *)_surface.getBasePtr(0, i);
+ }
+ for (int j = 0; j < _surface.w; j++) {
+ *dst++ = tga.readUint32LE();
+ }
+ }
+ } else if (pixelDepth == 24) {
+ for (int i = 0; i < _surface.h; i++) {
+ byte *dst;
+ if (!_originTop) {
+ dst = (byte *)_surface.getBasePtr(0, _surface.h - i - 1);
+ } else {
+ dst = (byte *)_surface.getBasePtr(0, i);
+ }
+ for (int j = 0; j < _surface.w; j++) {
+ byte r = tga.readByte();
+ byte g = tga.readByte();
+ byte b = tga.readByte();
+#ifdef SCUMM_LITTLE_ENDIAN
+ *dst++ = r;
+ *dst++ = g;
+ *dst++ = b;
+#else
+ *dst++ = b;
+ *dst++ = g;
+ *dst++ = r;
+#endif
+ }
+ }
+ }
+ // Black/White
+ } else if (imageType == TYPE_BW) {
+ _surface.create(_surface.w, _surface.h, _format);
+
+ byte *data = (byte *)_surface.getPixels();
+ uint32 count = _surface.w * _surface.h;
+
+ while (count-- > 0) {
+ byte g = tga.readByte();
+ *data++ = g;
+ *data++ = g;
+ *data++ = g;
+ *data++ = g;
+ }
+ }
+ return true;
+}
+
+bool TGADecoder::readDataColorMapped(Common::SeekableReadStream &tga, byte imageType, byte indexDepth) {
+ // Color-mapped
+ if (imageType == TYPE_CMAP) {
+ _surface.create(_surface.w, _surface.h, _format);
+ if (indexDepth == 8) {
+ for (int i = 0; i < _surface.h; i++) {
+ byte *dst;
+ if (!_originTop) {
+ dst = (byte *)_surface.getBasePtr(0, _surface.h - i - 1);
+ } else {
+ dst = (byte *)_surface.getBasePtr(0, i);
+ }
+ for (int j = 0; j < _surface.w; j++) {
+ byte index = tga.readByte();
+ *dst++ = index;
+ }
+ }
+ } else if (indexDepth == 16) {
+ warning("16 bit indexes not supported");
+ return false;
+ }
+ } else {
+ return false;
+ }
+ return true;
+}
+
+bool TGADecoder::readDataRLE(Common::SeekableReadStream &tga, byte imageType, byte pixelDepth) {
+ // RLE-TrueColor / RLE-Black/White
+ if (imageType == TYPE_RLE_TRUECOLOR || imageType == TYPE_RLE_BW || imageType == TYPE_RLE_CMAP) {
+ _surface.create(_surface.w, _surface.h, _format);
+ uint32 count = _surface.w * _surface.h;
+ byte *data = (byte *)_surface.getPixels();
+
+ while (count > 0) {
+ uint32 header = tga.readByte();
+ byte type = (header & 0x80) >> 7;
+ uint32 rleCount = (header & 0x7F) + 1;
+
+ // RLE-packet
+ if (type == 1) {
+ if (pixelDepth == 32 && imageType == TYPE_RLE_TRUECOLOR) {
+ uint32 color = tga.readUint32LE();
+ while (rleCount-- > 0) {
+ *((uint32 *)data) = color;
+ data += 4;
+ count--;
+ }
+ } else if (pixelDepth == 24 && imageType == TYPE_RLE_TRUECOLOR) {
+ byte r = tga.readByte();
+ byte g = tga.readByte();
+ byte b = tga.readByte();
+ while (rleCount-- > 0) {
+#ifdef SCUMM_LITTLE_ENDIAN
+ *data++ = r;
+ *data++ = g;
+ *data++ = b;
+#else
+ *data++ = b;
+ *data++ = g;
+ *data++ = r;
+#endif
+ count--;
+ }
+ } else if (pixelDepth == 8 && imageType == TYPE_RLE_BW) {
+ byte color = tga.readByte();
+ while (rleCount-- > 0) {
+ *data++ = color;
+ *data++ = color;
+ *data++ = color;
+ *data++ = color;
+ count--;
+ }
+ } else if (pixelDepth == 8 && imageType == TYPE_RLE_CMAP) {
+ byte index = tga.readByte();
+ while (rleCount-- > 0) {
+ *data++ = index;
+ count--;
+ }
+ } else {
+ warning("Unhandled pixel-depth for image-type 10");
+ return false;
+ }
+ // Raw-packet
+ } else if (type == 0) {
+ if (pixelDepth == 32 && imageType == TYPE_RLE_TRUECOLOR) {
+ while (rleCount-- > 0) {
+ uint32 color = tga.readUint32LE();
+ *((uint32 *)data) = color;
+ data += 4;
+ count--;
+ }
+ } else if (pixelDepth == 24 && imageType == TYPE_RLE_TRUECOLOR) {
+ while (rleCount-- > 0) {
+ byte r = tga.readByte();
+ byte g = tga.readByte();
+ byte b = tga.readByte();
+#ifdef SCUMM_LITTLE_ENDIAN
+ *data++ = r;
+ *data++ = g;
+ *data++ = b;
+#else
+ *data++ = b;
+ *data++ = g;
+ *data++ = r;
+#endif
+ count--;
+ }
+ } else if (pixelDepth == 8 && imageType == TYPE_RLE_BW) {
+ while (rleCount-- > 0) {
+ byte color = tga.readByte();
+ *data++ = color;
+ *data++ = color;
+ *data++ = color;
+ *data++ = color;
+ count--;
+ }
+ } else if (pixelDepth == 8 && imageType == TYPE_RLE_CMAP) {
+ while (rleCount-- > 0) {
+ byte index = tga.readByte();
+ *data++ = index;
+ count--;
+ }
+ } else {
+ warning("Unhandled pixel-depth for image-type 10");
+ return false;
+ }
+ } else {
+ warning("Unknown header for RLE-packet %d", type);
+ return false;
+ }
+ }
+ } else {
+ return false;
+ }
+ return true;
+}
+
+} // End of namespace Image
diff --git a/image/tga.h b/image/tga.h
new file mode 100644
index 0000000000..677c17834f
--- /dev/null
+++ b/image/tga.h
@@ -0,0 +1,100 @@
+/* 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.
+ *
+ */
+
+/* Based on code from eos https://github.com/DrMcCoy/xoreos/
+ * relicensed under GPLv2+ with permission from DrMcCoy and clone2727
+ */
+
+/*
+ * TGA decoder used in engines:
+ * - wintermute
+ * - zvision
+ */
+
+#ifndef IMAGE_TGA_H
+#define IMAGE_TGA_H
+
+#include "graphics/surface.h"
+#include "image/image_decoder.h"
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Image {
+
+/** TarGa image-decoder
+ * The following variations of TGA are supported:
+ * - Type 1 - Color-mapped images in 16/24/32 bpp with 8 bit indexes
+ * - Type 2 - 16/24/32 bpp Top AND Bottom origined.
+ * - Type 3 - Black/White images, 8bpp.
+ * - Type 9 - RLE-encoded color-mapped images. (8 bit indexes only)
+ * - Type 10 - RLE-encoded TrueColor, 24/32bpp.
+ * - Type 11 - RLE-encoded Black/White, 8bpp.
+ *
+ * No images are returned with a palette, instead they are converted
+ * to 16 bpp for Type 1, or 32 bpp for Black/White-images.
+ */
+class TGADecoder : public ImageDecoder {
+public:
+ TGADecoder();
+ virtual ~TGADecoder();
+ virtual void destroy();
+ virtual const Graphics::Surface *getSurface() const { return &_surface; }
+ virtual const byte *getPalette() const { return _colorMap; }
+ virtual uint16 getPaletteColorCount() const { return _colorMapLength; }
+ virtual bool loadStream(Common::SeekableReadStream &stream);
+private:
+ // Format-spec from:
+ //http://www.ludorg.net/amnesia/TGA_File_Format_Spec.html
+ enum {
+ TYPE_CMAP = 1,
+ TYPE_TRUECOLOR = 2,
+ TYPE_BW = 3,
+ TYPE_RLE_CMAP = 9,
+ TYPE_RLE_TRUECOLOR = 10,
+ TYPE_RLE_BW = 11
+ };
+
+ // Color-map:
+ bool _colorMapSize;
+ byte *_colorMap;
+ int16 _colorMapOrigin;
+ int16 _colorMapLength;
+ byte _colorMapEntryLength;
+
+ // Origin may be at the top, or bottom
+ bool _originTop;
+
+ Graphics::PixelFormat _format;
+ Graphics::Surface _surface;
+ // Loading helpers
+ bool readHeader(Common::SeekableReadStream &tga, byte &imageType, byte &pixelDepth);
+ bool readData(Common::SeekableReadStream &tga, byte imageType, byte pixelDepth);
+ bool readDataColorMapped(Common::SeekableReadStream &tga, byte imageType, byte indexDepth);
+ bool readDataRLE(Common::SeekableReadStream &tga, byte imageType, byte pixelDepth);
+ bool readColorMap(Common::SeekableReadStream &tga, byte imageType, byte pixelDepth);
+};
+
+} // End of namespace Image
+
+#endif