diff options
author | Matthew Hoops | 2014-02-27 21:27:23 -0500 |
---|---|---|
committer | Matthew Hoops | 2014-02-28 00:27:28 -0500 |
commit | 740b6e8fbdece44ae2a5295cb0549a53b6dc6ae7 (patch) | |
tree | d1f414522b8effe91dc2e4c75443ca18a30fc0ef /image | |
parent | cbf085287c74e0994fb18fb0830ccdb340b5b0ac (diff) | |
download | scummvm-rg350-740b6e8fbdece44ae2a5295cb0549a53b6dc6ae7.tar.gz scummvm-rg350-740b6e8fbdece44ae2a5295cb0549a53b6dc6ae7.tar.bz2 scummvm-rg350-740b6e8fbdece44ae2a5295cb0549a53b6dc6ae7.zip |
IMAGE: Move all ImageDecoders to image/
Diffstat (limited to 'image')
-rw-r--r-- | image/bmp.cpp | 183 | ||||
-rw-r--r-- | image/bmp.h | 68 | ||||
-rw-r--r-- | image/iff.cpp | 244 | ||||
-rw-r--r-- | image/iff.h | 126 | ||||
-rw-r--r-- | image/image_decoder.h | 106 | ||||
-rw-r--r-- | image/jpeg.cpp | 266 | ||||
-rw-r--r-- | image/jpeg.h | 95 | ||||
-rw-r--r-- | image/module.mk | 13 | ||||
-rw-r--r-- | image/pcx.cpp | 214 | ||||
-rw-r--r-- | image/pcx.h | 66 | ||||
-rw-r--r-- | image/pict.cpp | 580 | ||||
-rw-r--r-- | image/pict.h | 141 | ||||
-rw-r--r-- | image/png.cpp | 245 | ||||
-rw-r--r-- | image/png.h | 68 | ||||
-rw-r--r-- | image/tga.cpp | 430 | ||||
-rw-r--r-- | image/tga.h | 100 |
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 |