From 426c81a7a7a4d6f47b73db1a75908b548d250e4e Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Thu, 30 Jun 2011 08:16:19 -0400 Subject: GRAPHICS: Rewrite ImageDecoder to have an improved API The new bitmap decoder class is based off the Mohawk one, and now has 8bpp decoding capability. --- graphics/decoders/bmp.cpp | 159 ++++++++++++++++++++++++++++++++++++++ graphics/decoders/bmp.h | 56 ++++++++++++++ graphics/decoders/image_decoder.h | 85 ++++++++++++++++++++ 3 files changed, 300 insertions(+) create mode 100644 graphics/decoders/bmp.cpp create mode 100644 graphics/decoders/bmp.h create mode 100644 graphics/decoders/image_decoder.h (limited to 'graphics/decoders') diff --git a/graphics/decoders/bmp.cpp b/graphics/decoders/bmp.cpp new file mode 100644 index 0000000000..0d44881d7c --- /dev/null +++ b/graphics/decoders/bmp.cpp @@ -0,0 +1,159 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "common/stream.h" +#include "common/textconsole.h" + +#include "graphics/pixelformat.h" +#include "graphics/surface.h" +#include "graphics/decoders/bmp.h" + +namespace Graphics { + +BitmapDecoder::BitmapDecoder() { + _surface = 0; + _palette = 0; +} + +BitmapDecoder::~BitmapDecoder() { + destroy(); +} + +void BitmapDecoder::destroy() { + if (_surface) { + _surface->free(); + delete _surface; _surface = 0; + } + + delete[] _palette; _palette = 0; +} + +bool BitmapDecoder::loadStream(Common::SeekableReadStream &stream) { + destroy(); + + if (stream.readByte() != 'B') + return false; + + if (stream.readByte() != 'M') + return false; + + /* uint32 fileSize = */ stream.readUint32LE(); + /* uint16 res1 = */ stream.readUint16LE(); + /* uint16 res2 = */ stream.readUint16LE(); + uint32 imageOffset = stream.readUint32LE(); + + uint32 infoSize = stream.readUint32LE(); + if (infoSize != 40) { + warning("Only Windows v3 bitmaps are supported"); + return false; + } + + uint32 width = stream.readUint32LE(); + int32 height = stream.readSint32LE(); + + if (width == 0 || height == 0) + return false; + + if (height < 0) { + warning("Right-side up bitmaps not supported"); + return false; + } + + /* uint16 planes = */ stream.readUint16LE(); + uint16 bitsPerPixel = stream.readUint16LE(); + + if (bitsPerPixel != 8 && bitsPerPixel != 24) { + warning("%dbpp bitmaps not supported", bitsPerPixel); + return false; + } + + uint32 compression = stream.readUint32LE(); + + if (compression != 0) { + warning("Compressed bitmaps not supported"); + return false; + } + + /* uint32 imageSize = */ stream.readUint32LE(); + /* uint32 pixelsPerMeterX = */ stream.readUint32LE(); + /* uint32 pixelsPerMeterY = */ stream.readUint32LE(); + uint32 colorsUsed = stream.readUint32LE(); + /* uint32 colorsImportant = */ stream.readUint32LE(); + + if (colorsUsed == 0) + colorsUsed = 256; + + if (bitsPerPixel == 8) { + // Read the palette + _palette = new byte[colorsUsed * 3]; + for (uint16 i = 0; i < colorsUsed; i++) { + _palette[i * 3 + 2] = stream.readByte(); + _palette[i * 3 + 1] = stream.readByte(); + _palette[i * 3 + 0] = stream.readByte(); + stream.readByte(); + } + } + + // Start us at the beginning of the image + stream.seek(imageOffset); + + Graphics::PixelFormat format = Graphics::PixelFormat::createFormatCLUT8(); + + // BGRA for 24bpp + if (bitsPerPixel == 24) + format = Graphics::PixelFormat(4, 8, 8, 8, 8, 8, 16, 24, 0); + + _surface = new Graphics::Surface(); + _surface->create(width, height, format); + + int srcPitch = width * (bitsPerPixel >> 3); + const int extraDataLength = (srcPitch % 4) ? 4 - (srcPitch % 4) : 0; + + if (bitsPerPixel == 8) { + byte *dst = (byte *)_surface->pixels; + + for (int32 i = 0; i < height; i++) { + stream.read(dst + (height - i - 1) * width, width); + stream.skip(extraDataLength); + } + } else { + byte *dst = (byte *)_surface->pixels + (height - 1) * _surface->pitch; + + for (int32 i = 0; i < height; i++) { + for (uint32 j = 0; j < width; j++) { + byte b = stream.readByte(); + byte g = stream.readByte(); + byte r = stream.readByte(); + uint32 color = format.RGBToColor(r, g, b); + + *((uint32 *)dst) = color; + dst += format.bytesPerPixel; + } + + stream.skip(extraDataLength); + dst -= _surface->pitch * 2; + } + } + + return true; +} + +} // End of namespace Graphics diff --git a/graphics/decoders/bmp.h b/graphics/decoders/bmp.h new file mode 100644 index 0000000000..e11b12fad6 --- /dev/null +++ b/graphics/decoders/bmp.h @@ -0,0 +1,56 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef GRAPHICS_DECODERS_BMP_H +#define GRAPHICS_DECODERS_BMP_H + +#include "common/scummsys.h" +#include "common/str.h" +#include "graphics/decoders/image_decoder.h" + +namespace Common{ +class SeekableReadStream; +} + +namespace Graphics { + +struct PixelFormat; +struct Surface; + +class BitmapDecoder : public ImageDecoder { +public: + BitmapDecoder(); + virtual ~BitmapDecoder(); + + // ImageDecoder API + void destroy(); + virtual bool loadStream(Common::SeekableReadStream &stream); + virtual const Surface *getSurface() const { return _surface; } + virtual const byte *getPalette() { return _palette; } + +private: + Surface *_surface; + byte *_palette; +}; + +} // End of namespace Graphics + +#endif diff --git a/graphics/decoders/image_decoder.h b/graphics/decoders/image_decoder.h new file mode 100644 index 0000000000..e768f7f9a2 --- /dev/null +++ b/graphics/decoders/image_decoder.h @@ -0,0 +1,85 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef GRAPHICS_DECODERS_IMAGEDECODER_H +#define GRAPHICS_DECODERS_IMAGEDECODER_H + +#include "common/scummsys.h" +#include "common/str.h" + +namespace Common{ +class SeekableReadStream; +} + +namespace Graphics { + +struct PixelFormat; +struct Surface; + +/** + * A representation of an image decoder that maintains ownership of the surface + * and palette it decodes to. + */ +class ImageDecoder { +public: + virtual ~ImageDecoder() {} + + /** + * Load an image from the specified stream + * + * @param stream the input stream + * @return whether loading the file succeeded + * @see getSurface + * @see getPalette + */ + virtual bool loadStream(Common::SeekableReadStream &stream) = 0; + + /** + * Destroy this decoder's surface and palette + */ + virtual void destroy() = 0; + + /** + * Get the decoded surface + * + * This surface is owned by this ImageDecoder and will remain valid + * until destroy() or loadStream() is called, or until this ImageDecoder's + * destructor is called. + * + * @return the decoded surface, or 0 if no surface is present + */ + virtual const Surface *getSurface() const = 0; + + /** + * Get the decoded palette + * + * This palette is owned by this ImageDecoder and will remain valid + * until destroy() or loadStream() is called, or until this ImageDecoder's + * destructor is called. + * + * @return the decoded palette, or 0 if no palette is present + */ + virtual const byte *getPalette() const { return 0; } +}; + +} // End of namespace Graphics + +#endif -- cgit v1.2.3 From 4516b5ea24e6c0056984a65fe4fff33553931487 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Sat, 2 Jul 2011 21:02:58 -0400 Subject: GRAPHICS: Convert PictDecoder to the ImageDecoder API --- graphics/decoders/pict.cpp | 531 +++++++++++++++++++++++++++++++++++++++++++++ graphics/decoders/pict.h | 130 +++++++++++ 2 files changed, 661 insertions(+) create mode 100644 graphics/decoders/pict.cpp create mode 100644 graphics/decoders/pict.h (limited to 'graphics/decoders') diff --git a/graphics/decoders/pict.cpp b/graphics/decoders/pict.cpp new file mode 100644 index 0000000000..9b28f4352e --- /dev/null +++ b/graphics/decoders/pict.cpp @@ -0,0 +1,531 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/debug.h" +#include "common/endian.h" +#include "common/stream.h" +#include "common/substream.h" +#include "common/textconsole.h" + +#include "graphics/surface.h" +#include "graphics/jpeg.h" +#include "graphics/decoders/pict.h" + +namespace Graphics { + +// The PICT code is based off of the QuickDraw specs: +// http://developer.apple.com/legacy/mac/library/documentation/mac/QuickDraw/QuickDraw-461.html +// http://developer.apple.com/legacy/mac/library/documentation/mac/QuickDraw/QuickDraw-269.html + +PICTDecoder::PICTDecoder() { + _outputSurface = 0; +} + +PICTDecoder::~PICTDecoder() { + destroy(); +} + +void PICTDecoder::destroy() { + if (_outputSurface) { + _outputSurface->free(); + delete _outputSurface; + _outputSurface = 0; + } +} + +#define OPCODE(a, b, c) _opcodes.push_back(PICTOpcode(a, &PICTDecoder::b, c)) + +void PICTDecoder::setupOpcodesCommon() { + OPCODE(0x0000, o_nop, "NOP"); + OPCODE(0x0001, o_clip, "Clip"); + OPCODE(0x0003, o_txFont, "TxFont"); + OPCODE(0x0004, o_txFace, "TxFace"); + OPCODE(0x0007, o_pnSize, "PnSize"); + OPCODE(0x000D, o_txSize, "TxSize"); + OPCODE(0x0010, o_txRatio, "TxRatio"); + OPCODE(0x0011, o_versionOp, "VersionOp"); + OPCODE(0x001E, o_nop, "DefHilite"); + OPCODE(0x0028, o_longText, "LongText"); + OPCODE(0x00A1, o_longComment, "LongComment"); + OPCODE(0x00FF, o_opEndPic, "OpEndPic"); + OPCODE(0x0C00, o_headerOp, "HeaderOp"); +} + +void PICTDecoder::setupOpcodesNormal() { + setupOpcodesCommon(); + OPCODE(0x0098, on_packBitsRect, "PackBitsRect"); + OPCODE(0x009A, on_directBitsRect, "DirectBitsRect"); + OPCODE(0x8200, on_compressedQuickTime, "CompressedQuickTime"); +} + +void PICTDecoder::setupOpcodesQuickTime() { + setupOpcodesCommon(); + OPCODE(0x0098, oq_packBitsRect, "PackBitsRect"); + OPCODE(0x009A, oq_directBitsRect, "DirectBitsRect"); + OPCODE(0x8200, oq_compressedQuickTime, "CompressedQuickTime"); +} + +#undef OPCODE + +void PICTDecoder::o_nop(Common::SeekableReadStream &) { + // Nothing to do +} + +void PICTDecoder::o_clip(Common::SeekableReadStream &stream) { + // Ignore + stream.skip(stream.readUint16BE() - 2); +} + +void PICTDecoder::o_txFont(Common::SeekableReadStream &stream) { + // Ignore + stream.readUint16BE(); +} + +void PICTDecoder::o_txFace(Common::SeekableReadStream &stream) { + // Ignore + stream.readByte(); +} + +void PICTDecoder::o_pnSize(Common::SeekableReadStream &stream) { + // Ignore + stream.readUint16BE(); + stream.readUint16BE(); +} + +void PICTDecoder::o_txSize(Common::SeekableReadStream &stream) { + // Ignore + stream.readUint16BE(); +} + +void PICTDecoder::o_txRatio(Common::SeekableReadStream &stream) { + // Ignore + stream.readUint16BE(); + stream.readUint16BE(); + stream.readUint16BE(); + stream.readUint16BE(); +} + +void PICTDecoder::o_versionOp(Common::SeekableReadStream &stream) { + // We only support v2 extended + if (stream.readUint16BE() != 0x02FF) + error("Unknown PICT version"); +} + +void PICTDecoder::o_longText(Common::SeekableReadStream &stream) { + // Ignore + stream.readUint16BE(); + stream.readUint16BE(); + stream.skip(stream.readByte()); +} + +void PICTDecoder::o_longComment(Common::SeekableReadStream &stream) { + // Ignore + stream.readUint16BE(); + stream.skip(stream.readUint16BE()); +} + +void PICTDecoder::o_opEndPic(Common::SeekableReadStream &stream) { + // We've reached the end of the picture + _continueParsing = false; +} + +void PICTDecoder::o_headerOp(Common::SeekableReadStream &stream) { + // Read the basic header, but we don't really have to do anything with it + /* uint16 version = */ stream.readUint16BE(); + stream.readUint16BE(); // Reserved + /* uint32 hRes = */ stream.readUint32BE(); + /* uint32 vRes = */ stream.readUint32BE(); + Common::Rect origResRect; + origResRect.top = stream.readUint16BE(); + origResRect.left = stream.readUint16BE(); + origResRect.bottom = stream.readUint16BE(); + origResRect.right = stream.readUint16BE(); + stream.readUint32BE(); // Reserved +} + +void PICTDecoder::on_packBitsRect(Common::SeekableReadStream &stream) { + // Unpack data (8bpp or lower) + unpackBitsRect(stream, true); +} + +void PICTDecoder::on_directBitsRect(Common::SeekableReadStream &stream) { + // Unpack data (16bpp or higher) + unpackBitsRect(stream, false); +} + +void PICTDecoder::on_compressedQuickTime(Common::SeekableReadStream &stream) { + // OK, here's the fun. We get to completely change how QuickDraw draws + // the data in PICT files. + + // Swap out the opcodes to the new ones + _opcodes.clear(); + setupOpcodesQuickTime(); + + // We'll decode the first QuickTime data from here, but the QuickTime-specific + // opcodes will take over from here on out. Normal opcodes, signing off. + decodeCompressedQuickTime(stream); +} + +void PICTDecoder::oq_packBitsRect(Common::SeekableReadStream &stream) { + // Skip any data here (8bpp or lower) + skipBitsRect(stream, true); +} + +void PICTDecoder::oq_directBitsRect(Common::SeekableReadStream &stream) { + // Skip any data here (16bpp or higher) + skipBitsRect(stream, false); +} + +void PICTDecoder::oq_compressedQuickTime(Common::SeekableReadStream &stream) { + // Just pass the data along + decodeCompressedQuickTime(stream); +} + +bool PICTDecoder::loadStream(Common::SeekableReadStream &stream) { + destroy(); + + // Initialize opcodes to their normal state + _opcodes.clear(); + setupOpcodesNormal(); + + _continueParsing = true; + memset(_palette, 0, sizeof(_palette)); + + uint16 fileSize = stream.readUint16BE(); + + // If we have no file size here, we probably have a PICT from a file + // and not a resource. The other two bytes are the fileSize which we + // don't actually need (and already read if from a resource). + if (!fileSize) + stream.seek(512 + 2); + + _imageRect.top = stream.readUint16BE(); + _imageRect.left = stream.readUint16BE(); + _imageRect.bottom = stream.readUint16BE(); + _imageRect.right = stream.readUint16BE(); + _imageRect.debugPrint(0, "PICT Rect:"); + + // NOTE: This is only a subset of the full PICT format. + // - Only V2 (Extended) Images Supported + // - CompressedQuickTime (JPEG) compressed data is supported + // - DirectBitsRect/PackBitsRect compressed data is supported + for (uint32 opNum = 0; !stream.eos() && !stream.err() && stream.pos() < stream.size() && _continueParsing; opNum++) { + // PICT v2 opcodes are two bytes + uint16 opcode = stream.readUint16BE(); + + if (opNum == 0 && opcode != 0x0011) + error("Cannot find PICT version opcode"); + else if (opNum == 1 && opcode != 0x0C00) + error("Cannot find PICT header opcode"); + + // Since opcodes are word-aligned, we need to mark our starting + // position here. + uint32 startPos = stream.pos(); + + for (uint32 i = 0; i < _opcodes.size(); i++) { + if (_opcodes[i].op == opcode) { + debug(4, "Running PICT opcode %04x '%s'", opcode, _opcodes[i].desc); + (this->*(_opcodes[i].proc))(stream); + break; + } else if (i == _opcodes.size() - 1) { + // Unknown opcode; attempt to continue forward + warning("Unknown PICT opcode %04x", opcode); + } + } + + // Align + stream.skip((stream.pos() - startPos) & 1); + } + + return _outputSurface; +} + +PICTDecoder::PixMap PICTDecoder::readPixMap(Common::SeekableReadStream &stream, bool hasBaseAddr) { + PixMap pixMap; + pixMap.baseAddr = hasBaseAddr ? stream.readUint32BE() : 0; + pixMap.rowBytes = stream.readUint16BE() & 0x3fff; + pixMap.bounds.top = stream.readUint16BE(); + pixMap.bounds.left = stream.readUint16BE(); + pixMap.bounds.bottom = stream.readUint16BE(); + pixMap.bounds.right = stream.readUint16BE(); + pixMap.pmVersion = stream.readUint16BE(); + pixMap.packType = stream.readUint16BE(); + pixMap.packSize = stream.readUint32BE(); + pixMap.hRes = stream.readUint32BE(); + pixMap.vRes = stream.readUint32BE(); + pixMap.pixelType = stream.readUint16BE(); + pixMap.pixelSize = stream.readUint16BE(); + pixMap.cmpCount = stream.readUint16BE(); + pixMap.cmpSize = stream.readUint16BE(); + pixMap.planeBytes = stream.readUint32BE(); + pixMap.pmTable = stream.readUint32BE(); + pixMap.pmReserved = stream.readUint32BE(); + return pixMap; +} + +struct PackBitsRectData { + PICTDecoder::PixMap pixMap; + Common::Rect srcRect; + Common::Rect dstRect; + uint16 mode; +}; + +void PICTDecoder::unpackBitsRect(Common::SeekableReadStream &stream, bool hasPalette) { + PackBitsRectData packBitsData; + packBitsData.pixMap = readPixMap(stream, !hasPalette); + + // Read in the palette if there is one present + if (hasPalette) { + // See http://developer.apple.com/legacy/mac/library/documentation/mac/QuickDraw/QuickDraw-267.html + stream.readUint32BE(); // seed + stream.readUint16BE(); // flags + uint16 colorCount = stream.readUint16BE() + 1; + + for (uint32 i = 0; i < colorCount; i++) { + stream.readUint16BE(); + _palette[i * 3] = stream.readUint16BE() >> 8; + _palette[i * 3 + 1] = stream.readUint16BE() >> 8; + _palette[i * 3 + 2] = stream.readUint16BE() >> 8; + } + } + + packBitsData.srcRect.top = stream.readUint16BE(); + packBitsData.srcRect.left = stream.readUint16BE(); + packBitsData.srcRect.bottom = stream.readUint16BE(); + packBitsData.srcRect.right = stream.readUint16BE(); + packBitsData.dstRect.top = stream.readUint16BE(); + packBitsData.dstRect.left = stream.readUint16BE(); + packBitsData.dstRect.bottom = stream.readUint16BE(); + packBitsData.dstRect.right = stream.readUint16BE(); + packBitsData.mode = stream.readUint16BE(); + + uint16 width = packBitsData.srcRect.width(); + uint16 height = packBitsData.srcRect.height(); + + byte bytesPerPixel = 0; + + if (packBitsData.pixMap.pixelSize <= 8) + bytesPerPixel = 1; + else if (packBitsData.pixMap.pixelSize == 32) + bytesPerPixel = packBitsData.pixMap.cmpCount; + else + bytesPerPixel = packBitsData.pixMap.pixelSize / 8; + + // Ensure we have enough space in the buffer to hold an entire line's worth of pixels + uint32 lineSize = MAX(width * bytesPerPixel + (8 * 2 / packBitsData.pixMap.pixelSize), packBitsData.pixMap.rowBytes); + byte *buffer = new byte[lineSize * height]; + + // Read in amount of data per row + for (uint16 i = 0; i < packBitsData.pixMap.bounds.height(); i++) { + // NOTE: Compression 0 is "default". The format in SCI games is packed when 0. + // In the future, we may need to have something to set the "default" packing + // format, but this is good for now. + + if (packBitsData.pixMap.packType == 1 || packBitsData.pixMap.rowBytes < 8) { // Unpacked, Pad-Byte (on 24-bit) + // TODO: Finish this. Hasn't been needed (yet). + error("Unpacked DirectBitsRect data (padded)"); + } else if (packBitsData.pixMap.packType == 2) { // Unpacked, No Pad-Byte (on 24-bit) + // TODO: Finish this. Hasn't been needed (yet). + error("Unpacked DirectBitsRect data (not padded)"); + } else if (packBitsData.pixMap.packType == 0 || packBitsData.pixMap.packType > 2) { // Packed + uint16 byteCount = (packBitsData.pixMap.rowBytes > 250) ? stream.readUint16BE() : stream.readByte(); + unpackBitsLine(buffer + i * width * bytesPerPixel, packBitsData.pixMap.rowBytes, stream.readStream(byteCount), packBitsData.pixMap.pixelSize, bytesPerPixel); + } + } + + _outputSurface = new Graphics::Surface(); + + switch (bytesPerPixel) { + case 1: + // Just copy to the image + _outputSurface->create(width, height, PixelFormat::createFormatCLUT8()); + memcpy(_outputSurface->pixels, buffer, _outputSurface->w * _outputSurface->h); + break; + case 2: + // Convert from 16-bit to whatever surface we need + _outputSurface->create(width, height, PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0)); + for (uint16 y = 0; y < _outputSurface->h; y++) + for (uint16 x = 0; x < _outputSurface->w; x++) + WRITE_UINT16(_outputSurface->getBasePtr(x, y), READ_BE_UINT16(buffer + (y * _outputSurface->w + x) * 2)); + break; + case 3: + // Convert from 24-bit (planar!) to whatever surface we need + _outputSurface->create(width, height, Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0)); + for (uint16 y = 0; y < _outputSurface->h; y++) { + for (uint16 x = 0; x < _outputSurface->w; x++) { + byte r = *(buffer + y * _outputSurface->w * 3 + x); + byte g = *(buffer + y * _outputSurface->w * 3 + _outputSurface->w + x); + byte b = *(buffer + y * _outputSurface->w * 3 + _outputSurface->w * 2 + x); + *((uint32 *)_outputSurface->getBasePtr(x, y)) = _outputSurface->format.RGBToColor(r, g, b); + } + } + break; + case 4: + // Convert from 32-bit (planar!) to whatever surface we need + _outputSurface->create(width, height, Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0)); + for (uint16 y = 0; y < _outputSurface->h; y++) { + for (uint16 x = 0; x < _outputSurface->w; x++) { + byte r = *(buffer + y * _outputSurface->w * 4 + x); + byte g = *(buffer + y * _outputSurface->w * 4 + _outputSurface->w + x); + byte b = *(buffer + y * _outputSurface->w * 4 + _outputSurface->w * 2 + x); + byte a = *(buffer + y * _outputSurface->w * 4 + _outputSurface->w * 3 + x); + *((uint32 *)_outputSurface->getBasePtr(x, y)) = _outputSurface->format.ARGBToColor(r, g, b, a); + } + } + break; + } + + delete[] buffer; +} + +void PICTDecoder::unpackBitsLine(byte *out, uint32 length, Common::SeekableReadStream *data, byte bitsPerPixel, byte bytesPerPixel) { + uint32 dataDecoded = 0; + byte bytesPerDecode = (bytesPerPixel == 2) ? 2 : 1; + + while (data->pos() < data->size() && dataDecoded < length) { + byte op = data->readByte(); + + if (op & 0x80) { + uint32 runSize = (op ^ 255) + 2; + uint16 value = (bytesPerDecode == 2) ? data->readUint16BE() : data->readByte(); + + for (uint32 i = 0; i < runSize; i++) { + if (bytesPerDecode == 2) { + WRITE_UINT16(out, value); + out += 2; + } else { + outputPixelBuffer(out, value, bitsPerPixel); + } + } + dataDecoded += runSize * bytesPerDecode; + } else { + uint32 runSize = op + 1; + + if (bytesPerDecode == 1) { + for (uint32 i = 0; i < runSize; i++) + outputPixelBuffer(out, data->readByte(), bitsPerPixel); + } else { + for (uint32 i = 0; i < runSize; i++) { + WRITE_UINT16(out, data->readUint16BE()); + out += 2; + } + } + + dataDecoded += runSize * bytesPerDecode; + } + } + + // HACK: Even if the data is 24-bit, rowBytes is still 32-bit + if (bytesPerPixel == 3) + dataDecoded += length / 4; + + if (length != dataDecoded) + warning("Mismatched PackBits read (%d/%d)", dataDecoded, length); + + delete data; +} + +void PICTDecoder::outputPixelBuffer(byte *&out, byte value, byte bitsPerPixel) { + switch (bitsPerPixel) { + case 1: + for (int i = 7; i >= 0; i--) + *out++ = (value >> i) & 1; + break; + case 2: + for (int i = 6; i >= 0; i -= 2) + *out++ = (value >> i) & 3; + break; + case 4: + *out++ = (value >> 4) & 0xf; + *out++ = value & 0xf; + break; + default: + *out++ = value; + } +} + +void PICTDecoder::skipBitsRect(Common::SeekableReadStream &stream, bool hasPalette) { + // Step through a PackBitsRect/DirectBitsRect function + + if (!hasPalette) + stream.readUint32BE(); + + uint16 rowBytes = stream.readUint16BE(); + uint16 height = stream.readUint16BE(); + stream.readUint16BE(); + height = stream.readUint16BE() - height; + stream.readUint16BE(); + + uint16 packType; + + // Top two bits signify PixMap vs BitMap + if (rowBytes & 0xC000) { + // PixMap + stream.readUint16BE(); + packType = stream.readUint16BE(); + stream.skip(14); + stream.readUint16BE(); // pixelSize + stream.skip(16); + + if (hasPalette) { + stream.readUint32BE(); + stream.readUint16BE(); + stream.skip((stream.readUint16BE() + 1) * 8); + } + + rowBytes &= 0x3FFF; + } else { + // BitMap + packType = 0; + } + + stream.skip(18); + + for (uint16 i = 0; i < height; i++) { + if (packType == 1 || packType == 2 || rowBytes < 8) + error("Unpacked PackBitsRect data"); + else if (packType == 0 || packType > 2) + stream.skip((rowBytes > 250) ? stream.readUint16BE() : stream.readByte()); + } +} + +// Compressed QuickTime details can be found here: +// http://developer.apple.com/legacy/mac/library/#documentation/QuickTime/Rm/CompressDecompress/ImageComprMgr/B-Chapter/2TheImageCompression.html +// http://developer.apple.com/legacy/mac/library/#documentation/QuickTime/Rm/CompressDecompress/ImageComprMgr/F-Chapter/6WorkingwiththeImage.html +void PICTDecoder::decodeCompressedQuickTime(Common::SeekableReadStream &stream) { + JPEG jpeg; + + uint32 dataSize = stream.readUint32BE(); + uint32 startPos = stream.pos(); + + Common::SeekableReadStream *jpegStream = new Common::SeekableSubReadStream(&stream, stream.pos() + 156, stream.pos() + dataSize); + + if (!jpeg.read(jpegStream)) + error("PICTDecoder::decodeCompressedQuickTime(): Could not decode JPEG data"); + + _outputSurface = jpeg.getSurface(Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0)); + + stream.seek(startPos + dataSize); + delete jpegStream; +} + +} // End of namespace Graphics diff --git a/graphics/decoders/pict.h b/graphics/decoders/pict.h new file mode 100644 index 0000000000..b1e45a6bc1 --- /dev/null +++ b/graphics/decoders/pict.h @@ -0,0 +1,130 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GRAPHICS_PICT_H +#define GRAPHICS_PICT_H + +#include "common/array.h" +#include "common/rect.h" +#include "common/scummsys.h" + +#include "graphics/decoders/image_decoder.h" +#include "graphics/pixelformat.h" + +namespace Common { +class SeekableReadStream; +} + +namespace Graphics { + +struct Surface; + +#define DECLARE_OPCODE(x) void x(Common::SeekableReadStream &stream) + +class PICTDecoder : public ImageDecoder { +public: + PICTDecoder(); + ~PICTDecoder(); + + // ImageDecoder API + bool loadStream(Common::SeekableReadStream &stream); + void destroy(); + const Surface *getSurface() const { return _outputSurface; } + const byte *getPalette() const { return _palette; } + + struct PixMap { + uint32 baseAddr; + uint16 rowBytes; + Common::Rect bounds; + uint16 pmVersion; + uint16 packType; + uint32 packSize; + uint32 hRes; + uint32 vRes; + uint16 pixelType; + uint16 pixelSize; + uint16 cmpCount; + uint16 cmpSize; + uint32 planeBytes; + uint32 pmTable; + uint32 pmReserved; + }; + + static PixMap readPixMap(Common::SeekableReadStream &stream, bool hasBaseAddr = true); + +private: + Common::Rect _imageRect; + byte _palette[256 * 3]; + Graphics::Surface *_outputSurface; + bool _continueParsing; + + // Utility Functions + void unpackBitsRect(Common::SeekableReadStream &stream, bool hasPalette); + void unpackBitsLine(byte *out, uint32 length, Common::SeekableReadStream *stream, byte bitsPerPixel, byte bytesPerPixel); + void skipBitsRect(Common::SeekableReadStream &stream, bool hasPalette); + void decodeCompressedQuickTime(Common::SeekableReadStream &stream); + void outputPixelBuffer(byte *&out, byte value, byte bitsPerPixel); + + // Opcodes + typedef void (PICTDecoder::*OpcodeProcPICT)(Common::SeekableReadStream &stream); + struct PICTOpcode { + PICTOpcode() { op = 0; proc = 0; desc = 0; } + PICTOpcode(uint16 o, OpcodeProcPICT p, const char *d) { op = o; proc = p; desc = d; } + uint16 op; + OpcodeProcPICT proc; + const char *desc; + }; + Common::Array _opcodes; + + // Common Opcodes + void setupOpcodesCommon(); + DECLARE_OPCODE(o_nop); + DECLARE_OPCODE(o_clip); + DECLARE_OPCODE(o_txFont); + DECLARE_OPCODE(o_txFace); + DECLARE_OPCODE(o_pnSize); + DECLARE_OPCODE(o_txSize); + DECLARE_OPCODE(o_txRatio); + DECLARE_OPCODE(o_versionOp); + DECLARE_OPCODE(o_longText); + DECLARE_OPCODE(o_longComment); + DECLARE_OPCODE(o_opEndPic); + DECLARE_OPCODE(o_headerOp); + + // Regular-mode Opcodes + void setupOpcodesNormal(); + DECLARE_OPCODE(on_packBitsRect); + DECLARE_OPCODE(on_directBitsRect); + DECLARE_OPCODE(on_compressedQuickTime); + + // QuickTime-mode Opcodes + void setupOpcodesQuickTime(); + DECLARE_OPCODE(oq_packBitsRect); + DECLARE_OPCODE(oq_directBitsRect); + DECLARE_OPCODE(oq_compressedQuickTime); +}; + +#undef DECLARE_OPCODE + +} // End of namespace Graphics + +#endif -- cgit v1.2.3 From 765a8704454d705acc9ce5aa7a90c1c3079fb989 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Sat, 2 Jul 2011 22:05:45 -0400 Subject: GRAPHICS: Convert JPEG to the ImageDecoder API --- graphics/decoders/jpeg.cpp | 787 +++++++++++++++++++++++++++++++++++++++++++++ graphics/decoders/jpeg.h | 133 ++++++++ graphics/decoders/pict.cpp | 12 +- 3 files changed, 926 insertions(+), 6 deletions(-) create mode 100644 graphics/decoders/jpeg.cpp create mode 100644 graphics/decoders/jpeg.h (limited to 'graphics/decoders') diff --git a/graphics/decoders/jpeg.cpp b/graphics/decoders/jpeg.cpp new file mode 100644 index 0000000000..0cd2388d52 --- /dev/null +++ b/graphics/decoders/jpeg.cpp @@ -0,0 +1,787 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "graphics/conversion.h" +#include "graphics/pixelformat.h" +#include "graphics/decoders/jpeg.h" + +#include "common/debug.h" +#include "common/endian.h" +#include "common/stream.h" +#include "common/textconsole.h" + +namespace Graphics { + +// Order used to traverse the quantization tables +static const uint8 _zigZagOrder[64] = { + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63 +}; + +JPEGDecoder::JPEGDecoder() : ImageDecoder(), + _stream(NULL), _w(0), _h(0), _numComp(0), _components(NULL), _numScanComp(0), + _scanComp(NULL), _currentComp(NULL), _rgbSurface(0) { + + // Initialize the quantization tables + for (int i = 0; i < JPEG_MAX_QUANT_TABLES; i++) + _quant[i] = NULL; + + // Initialize the Huffman tables + for (int i = 0; i < 2 * JPEG_MAX_HUFF_TABLES; i++) { + _huff[i].count = 0; + _huff[i].values = NULL; + _huff[i].sizes = NULL; + _huff[i].codes = NULL; + } +} + +JPEGDecoder::~JPEGDecoder() { + destroy(); +} + +const Surface *JPEGDecoder::getSurface() const { + // Make sure we have loaded data + if (!isLoaded()) + return 0; + + if (_rgbSurface) + return _rgbSurface; + + // Create an RGBA8888 surface + _rgbSurface = new Graphics::Surface(); + _rgbSurface->create(_w, _h, Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0)); + + // Get our component surfaces + const Graphics::Surface *yComponent = getComponent(1); + const Graphics::Surface *uComponent = getComponent(2); + const Graphics::Surface *vComponent = getComponent(3); + + for (uint16 i = 0; i < _h; i++) { + for (uint16 j = 0; j < _w; j++) { + byte r = 0, g = 0, b = 0; + YUV2RGB(*((const byte *)yComponent->getBasePtr(j, i)), *((const byte *)uComponent->getBasePtr(j, i)), *((const byte *)vComponent->getBasePtr(j, i)), r, g, b); + *((uint32 *)_rgbSurface->getBasePtr(j, i)) = _rgbSurface->format.RGBToColor(r, g, b); + } + } + + return _rgbSurface; +} + +void JPEGDecoder::destroy() { + // Reset member variables + _stream = NULL; + _w = _h = 0; + _restartInterval = 0; + + // Free the components + for (int c = 0; c < _numComp; c++) + _components[c].surface.free(); + delete[] _components; _components = NULL; + _numComp = 0; + + // Free the scan components + delete[] _scanComp; _scanComp = NULL; + _numScanComp = 0; + _currentComp = NULL; + + // Free the quantization tables + for (int i = 0; i < JPEG_MAX_QUANT_TABLES; i++) { + delete[] _quant[i]; + _quant[i] = NULL; + } + + // Free the Huffman tables + for (int i = 0; i < 2 * JPEG_MAX_HUFF_TABLES; i++) { + _huff[i].count = 0; + delete[] _huff[i].values; _huff[i].values = NULL; + delete[] _huff[i].sizes; _huff[i].sizes = NULL; + delete[] _huff[i].codes; _huff[i].codes = NULL; + } + + if (_rgbSurface) { + _rgbSurface->free(); + delete _rgbSurface; + } +} + +bool JPEGDecoder::loadStream(Common::SeekableReadStream &stream) { + // Reset member variables and tables from previous reads + destroy(); + + // Save the input stream + _stream = &stream; + + bool ok = true; + bool done = false; + while (!_stream->eos() && ok && !done) { + // Read the marker + + // WORKAROUND: While each and every JPEG file should end with + // an EOI (end of image) tag, in reality this may not be the + // case. For instance, at least one image in the Masterpiece + // edition of Myst doesn't, yet other programs are able to read + // the image without complaining. + // + // Apparently, the customary workaround is to insert a fake + // EOI tag. + + uint16 marker = _stream->readByte(); + bool fakeEOI = false; + + if (_stream->eos()) { + fakeEOI = true; + marker = 0xFF; + } + + if (marker != 0xFF) { + error("JPEG: Invalid marker[0]: 0x%02X", marker); + ok = false; + break; + } + + while (marker == 0xFF && !_stream->eos()) + marker = _stream->readByte(); + + if (_stream->eos()) { + fakeEOI = true; + marker = 0xD9; + } + + if (fakeEOI) + warning("JPEG: Inserted fake EOI"); + + // Process the marker data + switch (marker) { + case 0xC0: // Start Of Frame + ok = readSOF0(); + break; + case 0xC4: // Define Huffman Tables + ok = readDHT(); + break; + case 0xD8: // Start Of Image + break; + case 0xD9: // End Of Image + done = true; + break; + case 0xDA: // Start Of Scan + ok = readSOS(); + break; + case 0xDB: // Define Quantization Tables + ok = readDQT(); + break; + case 0xE0: // JFIF/JFXX segment + ok = readJFIF(); + break; + case 0xDD: // Define Restart Interval + ok = readDRI(); + break; + case 0xFE: // Comment + _stream->seek(_stream->readUint16BE() - 2, SEEK_CUR); + break; + default: { // Unknown marker + uint16 size = _stream->readUint16BE(); + + if ((marker & 0xE0) != 0xE0) + warning("JPEG: Unknown marker %02X, skipping %d bytes", marker, size - 2); + + _stream->seek(size - 2, SEEK_CUR); + } + } + } + + _stream = 0; + return ok; +} + +bool JPEGDecoder::readJFIF() { + uint16 length = _stream->readUint16BE(); + uint32 tag = _stream->readUint32BE(); + if (tag != MKTAG('J', 'F', 'I', 'F')) { + warning("JPEGDecoder::readJFIF() tag mismatch"); + return false; + } + if (_stream->readByte() != 0) { // NULL + warning("JPEGDecoder::readJFIF() NULL mismatch"); + return false; + } + byte majorVersion = _stream->readByte(); + byte minorVersion = _stream->readByte(); + if (majorVersion != 1 || minorVersion != 1) + warning("JPEGDecoder::readJFIF() Non-v1.1 JPEGs may not be handled correctly"); + /* byte densityUnits = */_stream->readByte(); + /* uint16 xDensity = */_stream->readUint16BE(); + /* uint16 yDensity = */_stream->readUint16BE(); + byte thumbW = _stream->readByte(); + byte thumbH = _stream->readByte(); + _stream->seek(thumbW * thumbH * 3, SEEK_CUR); // Ignore thumbnail + if (length != (thumbW * thumbH * 3) + 16) { + warning("JPEGDecoder::readJFIF() length mismatch"); + return false; + } + return true; +} + +// Marker 0xC0 (Start Of Frame, Baseline DCT) +bool JPEGDecoder::readSOF0() { + debug(5, "JPEG: readSOF0"); + uint16 size = _stream->readUint16BE(); + + // Read the sample precision + uint8 precision = _stream->readByte(); + if (precision != 8) { + warning("JPEG: Just 8 bit precision supported at the moment"); + return false; + } + + // Image size + _h = _stream->readUint16BE(); + _w = _stream->readUint16BE(); + + // Number of components + _numComp = _stream->readByte(); + if (size != 8 + 3 * _numComp) { + warning("JPEG: Invalid number of components"); + return false; + } + + // Allocate the new components + delete[] _components; + _components = new Component[_numComp]; + + // Read the components details + for (int c = 0; c < _numComp; c++) { + _components[c].id = _stream->readByte(); + _components[c].factorH = _stream->readByte(); + _components[c].factorV = _components[c].factorH & 0xF; + _components[c].factorH >>= 4; + _components[c].quantTableSelector = _stream->readByte(); + } + + return true; +} + +// Marker 0xC4 (Define Huffman Tables) +bool JPEGDecoder::readDHT() { + debug(5, "JPEG: readDHT"); + uint16 size = _stream->readUint16BE() - 2; + uint32 pos = _stream->pos(); + + while ((uint32)_stream->pos() < (size + pos)) { + // Read the table type and id + uint8 tableId = _stream->readByte(); + uint8 tableType = tableId >> 4; // type 0: DC, 1: AC + tableId &= 0xF; + uint8 tableNum = (tableId << 1) + tableType; + + // Free the Huffman table + delete[] _huff[tableNum].values; _huff[tableNum].values = NULL; + delete[] _huff[tableNum].sizes; _huff[tableNum].sizes = NULL; + delete[] _huff[tableNum].codes; _huff[tableNum].codes = NULL; + + // Read the number of values for each length + uint8 numValues[16]; + _huff[tableNum].count = 0; + for (int len = 0; len < 16; len++) { + numValues[len] = _stream->readByte(); + _huff[tableNum].count += numValues[len]; + } + + // Allocate memory for the current table + _huff[tableNum].values = new uint8[_huff[tableNum].count]; + _huff[tableNum].sizes = new uint8[_huff[tableNum].count]; + _huff[tableNum].codes = new uint16[_huff[tableNum].count]; + + // Read the table contents + int cur = 0; + for (int len = 0; len < 16; len++) { + for (int i = 0; i < numValues[len]; i++) { + _huff[tableNum].values[cur] = _stream->readByte(); + _huff[tableNum].sizes[cur] = len + 1; + cur++; + } + } + + // Fill the table of Huffman codes + cur = 0; + uint16 curCode = 0; + uint8 curCodeSize = _huff[tableNum].sizes[0]; + while (cur < _huff[tableNum].count) { + // Increase the code size to fit the request + while (_huff[tableNum].sizes[cur] != curCodeSize) { + curCode <<= 1; + curCodeSize++; + } + + // Assign the current code + _huff[tableNum].codes[cur] = curCode; + curCode++; + cur++; + } + } + + return true; +} + +// Marker 0xDA (Start Of Scan) +bool JPEGDecoder::readSOS() { + debug(5, "JPEG: readSOS"); + uint16 size = _stream->readUint16BE(); + + // Number of scan components + _numScanComp = _stream->readByte(); + if (size != 6 + 2 * _numScanComp) { + warning("JPEG: Invalid number of components"); + return false; + } + + // Allocate the new scan components + delete[] _scanComp; + _scanComp = new Component *[_numScanComp]; + + // Reset the maximum sampling factors + _maxFactorV = 0; + _maxFactorH = 0; + + // Component-specification parameters + for (int c = 0; c < _numScanComp; c++) { + // Read the desired component id + uint8 id = _stream->readByte(); + + // Search the component with the specified id + bool found = false; + for (int i = 0; !found && i < _numComp; i++) { + if (_components[i].id == id) { + // We found the desired component + found = true; + + // Assign the found component to the c'th scan component + _scanComp[c] = &_components[i]; + } + } + + if (!found) { + warning("JPEG: Invalid component"); + return false; + } + + // Read the entropy table selectors + _scanComp[c]->DCentropyTableSelector = _stream->readByte(); + _scanComp[c]->ACentropyTableSelector = _scanComp[c]->DCentropyTableSelector & 0xF; + _scanComp[c]->DCentropyTableSelector >>= 4; + + // Calculate the maximum sampling factors + if (_scanComp[c]->factorV > _maxFactorV) + _maxFactorV = _scanComp[c]->factorV; + + if (_scanComp[c]->factorH > _maxFactorH) + _maxFactorH = _scanComp[c]->factorH; + + // Initialize the DC predictor + _scanComp[c]->DCpredictor = 0; + } + + // Start of spectral selection + if (_stream->readByte() != 0) { + warning("JPEG: Progressive scanning not supported"); + return false; + } + + // End of spectral selection + if (_stream->readByte() != 63) { + warning("JPEG: Progressive scanning not supported"); + return false; + } + + // Successive approximation parameters + if (_stream->readByte() != 0) { + warning("JPEG: Progressive scanning not supported"); + return false; + } + + // Entropy coded sequence starts, initialize Huffman decoder + _bitsNumber = 0; + + // Read all the scan MCUs + uint16 xMCU = _w / (_maxFactorH * 8); + uint16 yMCU = _h / (_maxFactorV * 8); + + // Check for non- multiple-of-8 dimensions + if (_w % (_maxFactorH * 8) != 0) + xMCU++; + if (_h % (_maxFactorV * 8) != 0) + yMCU++; + + // Initialize the scan surfaces + for (uint16 c = 0; c < _numScanComp; c++) { + _scanComp[c]->surface.create(xMCU * _maxFactorH * 8, yMCU * _maxFactorV * 8, PixelFormat::createFormatCLUT8()); + } + + bool ok = true; + uint16 interval = _restartInterval; + + for (int y = 0; ok && (y < yMCU); y++) { + for (int x = 0; ok && (x < xMCU); x++) { + ok = readMCU(x, y); + + // If we have a restart interval, we'll need to reset a couple + // variables + if (_restartInterval != 0) { + interval--; + + if (interval == 0) { + interval = _restartInterval; + _bitsNumber = 0; + + for (byte i = 0; i < _numScanComp; i++) + _scanComp[i]->DCpredictor = 0; + } + } + } + } + + // Trim Component surfaces back to image height and width + // Note: Code using jpeg must use surface.pitch correctly... + for (uint16 c = 0; c < _numScanComp; c++) { + _scanComp[c]->surface.w = _w; + _scanComp[c]->surface.h = _h; + } + + return ok; +} + +// Marker 0xDB (Define Quantization Tables) +bool JPEGDecoder::readDQT() { + debug(5, "JPEG: readDQT"); + uint16 size = _stream->readUint16BE() - 2; + uint32 pos = _stream->pos(); + + while ((uint32)_stream->pos() < (pos + size)) { + // Read the table precision and id + uint8 tableId = _stream->readByte(); + bool highPrecision = (tableId & 0xF0) != 0; + + // Validate the table id + tableId &= 0xF; + if (tableId > JPEG_MAX_QUANT_TABLES) { + warning("JPEG: Invalid number of components"); + return false; + } + + // Create the new table if necessary + if (!_quant[tableId]) + _quant[tableId] = new uint16[64]; + + // Read the table (stored in Zig-Zag order) + for (int i = 0; i < 64; i++) + _quant[tableId][i] = highPrecision ? _stream->readUint16BE() : _stream->readByte(); + } + + return true; +} + +// Marker 0xDD (Define Restart Interval) +bool JPEGDecoder::readDRI() { + debug(5, "JPEG: readDRI"); + uint16 size = _stream->readUint16BE() - 2; + + if (size != 2) { + warning("JPEG: Invalid DRI size %d", size); + return false; + } + + _restartInterval = _stream->readUint16BE(); + debug(5, "Restart interval: %d", _restartInterval); + return true; +} + +bool JPEGDecoder::readMCU(uint16 xMCU, uint16 yMCU) { + bool ok = true; + for (int c = 0; ok && (c < _numComp); c++) { + // Set the current component + _currentComp = _scanComp[c]; + + // Read the data units of the current component + for (int y = 0; ok && (y < _scanComp[c]->factorV); y++) + for (int x = 0; ok && (x < _scanComp[c]->factorH); x++) + ok = readDataUnit(xMCU * _scanComp[c]->factorH + x, yMCU * _scanComp[c]->factorV + y); + } + + return ok; +} + +// triple-butterfly-add (and possible rounding) +#define xadd3(xa, xb, xc, xd, h) \ + p = xa + xb; \ + n = xa - xb; \ + xa = p + xc + h; \ + xb = n + xd + h; \ + xc = p - xc + h; \ + xd = n - xd + h; + +// butterfly-mul +#define xmul(xa, xb, k1, k2, sh) \ + n = k1 * (xa + xb); \ + p = xa; \ + xa = (n + (k2 - k1) * xb) >> sh; \ + xb = (n - (k2 + k1) * p) >> sh; + +// IDCT based on public domain code from http://halicery.com/jpeg/idct.html +void JPEGDecoder::idct1D8x8(int32 src[8], int32 dest[64], int32 ps, int32 half) { + int p, n; + + src[0] <<= 9; + src[1] <<= 7; + src[3] *= 181; + src[4] <<= 9; + src[5] *= 181; + src[7] <<= 7; + + // Even part + xmul(src[6], src[2], 277, 669, 0) + xadd3(src[0], src[4], src[6], src[2], half) + + // Odd part + xadd3(src[1], src[7], src[3], src[5], 0) + xmul(src[5], src[3], 251, 50, 6) + xmul(src[1], src[7], 213, 142, 6) + + dest[0 * 8] = (src[0] + src[1]) >> ps; + dest[1 * 8] = (src[4] + src[5]) >> ps; + dest[2 * 8] = (src[2] + src[3]) >> ps; + dest[3 * 8] = (src[6] + src[7]) >> ps; + dest[4 * 8] = (src[6] - src[7]) >> ps; + dest[5 * 8] = (src[2] - src[3]) >> ps; + dest[6 * 8] = (src[4] - src[5]) >> ps; + dest[7 * 8] = (src[0] - src[1]) >> ps; +} + +void JPEGDecoder::idct2D8x8(int32 block[64]) { + int32 tmp[64]; + + // Apply 1D IDCT to rows + for (int i = 0; i < 8; i++) + idct1D8x8(&block[i * 8], &tmp[i], 9, 1 << 8); + + // Apply 1D IDCT to columns + for (int i = 0; i < 8; i++) + idct1D8x8(&tmp[i * 8], &block[i], 12, 1 << 11); + } + +bool JPEGDecoder::readDataUnit(uint16 x, uint16 y) { + // Prepare an empty data array + int16 readData[64]; + for (int i = 1; i < 64; i++) + readData[i] = 0; + + // Read the DC component + readData[0] = _currentComp->DCpredictor + readDC(); + _currentComp->DCpredictor = readData[0]; + + // Read the AC components (stored in Zig-Zag) + readAC(readData); + + // Calculate the DCT coefficients from the input sequence + int32 block[64]; + for (uint8 i = 0; i < 64; i++) { + // Dequantize + int32 val = readData[i]; + int16 quant = _quant[_currentComp->quantTableSelector][i]; + val *= quant; + + // Store the normalized coefficients, undoing the Zig-Zag + block[_zigZagOrder[i]] = val; + } + + // Apply the IDCT + idct2D8x8(block); + + // Level shift to make the values unsigned + for (int i = 0; i < 64; i++) { + block[i] = block[i] + 128; + + if (block[i] < 0) + block[i] = 0; + + if (block[i] > 255) + block[i] = 255; + } + + // Paint the component surface + uint8 scalingV = _maxFactorV / _currentComp->factorV; + uint8 scalingH = _maxFactorH / _currentComp->factorH; + + // Convert coordinates from MCU blocks to pixels + x <<= 3; + y <<= 3; + + for (uint8 j = 0; j < 8; j++) { + for (uint16 sV = 0; sV < scalingV; sV++) { + // Get the beginning of the block line + byte *ptr = (byte *)_currentComp->surface.getBasePtr(x * scalingH, (y + j) * scalingV + sV); + + for (uint8 i = 0; i < 8; i++) { + for (uint16 sH = 0; sH < scalingH; sH++) { + *ptr = (byte)(block[j * 8 + i]); + ptr++; + } + } + } + } + + return true; +} + +int16 JPEGDecoder::readDC() { + // DC is type 0 + uint8 tableNum = _currentComp->DCentropyTableSelector << 1; + + // Get the number of bits to read + uint8 numBits = readHuff(tableNum); + + // Read the requested bits + return readSignedBits(numBits); +} + +void JPEGDecoder::readAC(int16 *out) { + // AC is type 1 + uint8 tableNum = (_currentComp->ACentropyTableSelector << 1) + 1; + + // Start reading AC element 1 + uint8 cur = 1; + while (cur < 64) { + uint8 s = readHuff(tableNum); + uint8 r = s >> 4; + s &= 0xF; + + if (s == 0) { + if (r == 15) { + // Skip 16 values + cur += 16; + } else { + // EOB: end of block + cur = 64; + } + } else { + // Skip r values + cur += r; + + // Read the next value + out[cur] = readSignedBits(s); + cur++; + } + } +} + +int16 JPEGDecoder::readSignedBits(uint8 numBits) { + uint16 ret = 0; + if (numBits > 16) + error("requested %d bits", numBits); //XXX + + // MSB=0 for negatives, 1 for positives + for (int i = 0; i < numBits; i++) + ret = (ret << 1) + readBit(); + + // Extend sign bits (PAG109) + if (!(ret >> (numBits - 1))) { + uint16 tmp = ((uint16)-1 << numBits) + 1; + ret = ret + tmp; + } + return ret; +} + +// TODO: optimize? +uint8 JPEGDecoder::readHuff(uint8 table) { + bool foundCode = false; + uint8 val = 0; + + uint8 cur = 0; + uint8 codeSize = 1; + uint16 code = readBit(); + while (!foundCode) { + // Prepare a code of the current size + while (codeSize < _huff[table].sizes[cur]) { + code = (code << 1) + readBit(); + codeSize++; + } + + // Compare the codes of the current size + while (!foundCode && (codeSize == _huff[table].sizes[cur])) { + if (code == _huff[table].codes[cur]) { + // Found the code + val = _huff[table].values[cur]; + foundCode = true; + } else { + // Continue reading + cur++; + } + } + } + + return val; +} + +uint8 JPEGDecoder::readBit() { + // Read a whole byte if necessary + if (_bitsNumber == 0) { + _bitsData = _stream->readByte(); + _bitsNumber = 8; + + // Detect markers + if (_bitsData == 0xFF) { + uint8 byte2 = _stream->readByte(); + + // A stuffed 0 validates the previous byte + if (byte2 != 0) { + if (byte2 == 0xDC) { + // DNL marker: Define Number of Lines + // TODO: terminate scan + warning("DNL marker detected: terminate scan"); + } else if (byte2 >= 0xD0 && byte2 <= 0xD7) { + debug(7, "RST%d marker detected", byte2 & 7); + _bitsData = _stream->readByte(); + } else { + warning("Error: marker 0x%02X read in entropy data", byte2); + } + } + } + } + _bitsNumber--; + + return (_bitsData & (1 << _bitsNumber)) ? 1 : 0; +} + +const Surface *JPEGDecoder::getComponent(uint c) const { + for (int i = 0; i < _numComp; i++) + if (_components[i].id == c) // We found the desired component + return &_components[i].surface; + + error("JPEGDecoder::getComponent: No component %d present", c); + return NULL; +} + +} // End of Graphics namespace diff --git a/graphics/decoders/jpeg.h b/graphics/decoders/jpeg.h new file mode 100644 index 0000000000..c566d5ad21 --- /dev/null +++ b/graphics/decoders/jpeg.h @@ -0,0 +1,133 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GRAPHICS_JPEG_H +#define GRAPHICS_JPEG_H + +#include "graphics/surface.h" +#include "graphics/decoders/image_decoder.h" + +namespace Common { +class SeekableReadStream; +} + +namespace Graphics { + +struct PixelFormat; + +#define JPEG_MAX_QUANT_TABLES 4 +#define JPEG_MAX_HUFF_TABLES 2 + +class JPEGDecoder : public ImageDecoder { +public: + JPEGDecoder(); + ~JPEGDecoder(); + + // ImageDecoder API + void destroy(); + bool loadStream(Common::SeekableReadStream &str); + const Surface *getSurface() const; + + bool isLoaded() const { return _numComp && _w && _h; } + uint16 getWidth() const { return _w; } + uint16 getHeight() const { return _h; } + const Surface *getComponent(uint c) const; + +private: + Common::SeekableReadStream *_stream; + uint16 _w, _h; + uint16 _restartInterval; + + // mutable so that we can convert to RGB only during + // a getSurface() call while still upholding the + // const requirement in other ImageDecoders + mutable Graphics::Surface *_rgbSurface; + + // Image components + uint8 _numComp; + struct Component { + // Global values + uint8 id; + uint8 factorH; + uint8 factorV; + uint8 quantTableSelector; + + // Scan specific values + uint8 DCentropyTableSelector; + uint8 ACentropyTableSelector; + int16 DCpredictor; + + // Result image for this component + Surface surface; + }; + + Component *_components; + + // Scan components + uint8 _numScanComp; + Component **_scanComp; + Component *_currentComp; + + // Maximum sampling factors, used to calculate the interleaving of the MCU + uint8 _maxFactorV; + uint8 _maxFactorH; + + // Quantization tables + uint16 *_quant[JPEG_MAX_QUANT_TABLES]; + + // Huffman tables + struct HuffmanTable { + uint8 count; + uint8 *values; + uint8 *sizes; + uint16 *codes; + } _huff[2 * JPEG_MAX_HUFF_TABLES]; + + // Marker read functions + bool readJFIF(); + bool readSOF0(); + bool readDHT(); + bool readSOS(); + bool readDQT(); + bool readDRI(); + + // Helper functions + bool readMCU(uint16 xMCU, uint16 yMCU); + bool readDataUnit(uint16 x, uint16 y); + int16 readDC(); + void readAC(int16 *out); + int16 readSignedBits(uint8 numBits); + + // Huffman decoding + uint8 readHuff(uint8 table); + uint8 readBit(); + uint8 _bitsData; + uint8 _bitsNumber; + + // Inverse Discrete Cosine Transformation + static void idct1D8x8(int32 src[8], int32 dest[64], int32 ps, int32 half); + static void idct2D8x8(int32 block[64]); +}; + +} // End of Graphics namespace + +#endif // GRAPHICS_JPEG_H diff --git a/graphics/decoders/pict.cpp b/graphics/decoders/pict.cpp index 9b28f4352e..f8b2553ea0 100644 --- a/graphics/decoders/pict.cpp +++ b/graphics/decoders/pict.cpp @@ -27,7 +27,7 @@ #include "common/textconsole.h" #include "graphics/surface.h" -#include "graphics/jpeg.h" +#include "graphics/decoders/jpeg.h" #include "graphics/decoders/pict.h" namespace Graphics { @@ -512,20 +512,20 @@ void PICTDecoder::skipBitsRect(Common::SeekableReadStream &stream, bool hasPalet // 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) { - JPEG jpeg; + JPEGDecoder jpeg; uint32 dataSize = stream.readUint32BE(); uint32 startPos = stream.pos(); - Common::SeekableReadStream *jpegStream = new Common::SeekableSubReadStream(&stream, stream.pos() + 156, stream.pos() + dataSize); + Common::SeekableSubReadStream jpegStream(&stream, stream.pos() + 156, stream.pos() + dataSize); - if (!jpeg.read(jpegStream)) + if (!jpeg.loadStream(jpegStream)) error("PICTDecoder::decodeCompressedQuickTime(): Could not decode JPEG data"); - _outputSurface = jpeg.getSurface(Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0)); + _outputSurface = new Graphics::Surface(); + _outputSurface->copyFrom(*jpeg.getSurface()); stream.seek(startPos + dataSize); - delete jpegStream; } } // End of namespace Graphics -- cgit v1.2.3 From b6d2a11432c16b12fea48830f7aa131dba091ef7 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Sat, 2 Jul 2011 23:43:52 -0400 Subject: GRAPHICS: Make the JPEG code use the new YUV to RGB converter --- graphics/decoders/jpeg.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) (limited to 'graphics/decoders') diff --git a/graphics/decoders/jpeg.cpp b/graphics/decoders/jpeg.cpp index 0cd2388d52..a871377ca1 100644 --- a/graphics/decoders/jpeg.cpp +++ b/graphics/decoders/jpeg.cpp @@ -20,8 +20,8 @@ * */ -#include "graphics/conversion.h" #include "graphics/pixelformat.h" +#include "graphics/yuv_to_rgb.h" #include "graphics/decoders/jpeg.h" #include "common/debug.h" @@ -81,13 +81,7 @@ const Surface *JPEGDecoder::getSurface() const { const Graphics::Surface *uComponent = getComponent(2); const Graphics::Surface *vComponent = getComponent(3); - for (uint16 i = 0; i < _h; i++) { - for (uint16 j = 0; j < _w; j++) { - byte r = 0, g = 0, b = 0; - YUV2RGB(*((const byte *)yComponent->getBasePtr(j, i)), *((const byte *)uComponent->getBasePtr(j, i)), *((const byte *)vComponent->getBasePtr(j, i)), r, g, b); - *((uint32 *)_rgbSurface->getBasePtr(j, i)) = _rgbSurface->format.RGBToColor(r, g, b); - } - } + convertYUV444ToRGB(_rgbSurface, (byte *)yComponent->pixels, (byte *)uComponent->pixels, (byte *)vComponent->pixels, yComponent->w, yComponent->h, yComponent->pitch, uComponent->pitch); return _rgbSurface; } -- cgit v1.2.3 From a658fc660e5932410b91c00d3eef81963ed02972 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Sat, 28 Jan 2012 17:55:56 -0500 Subject: GRAPHICS: Move PNG to the ImageDecoder interface --- graphics/decoders/png.cpp | 503 ++++++++++++++++++++++++++++++++++++++++++++++ graphics/decoders/png.h | 135 +++++++++++++ 2 files changed, 638 insertions(+) create mode 100644 graphics/decoders/png.cpp create mode 100644 graphics/decoders/png.h (limited to 'graphics/decoders') diff --git a/graphics/decoders/png.cpp b/graphics/decoders/png.cpp new file mode 100644 index 0000000000..b87b6fdc7a --- /dev/null +++ b/graphics/decoders/png.cpp @@ -0,0 +1,503 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "graphics/decoders/png.h" + +#include "graphics/pixelformat.h" +#include "graphics/surface.h" + +#include "common/endian.h" +#include "common/memstream.h" +#include "common/stream.h" +#include "common/types.h" +#include "common/util.h" +#include "common/zlib.h" + +// PNG decoder, based on the W3C specs: +// http://www.w3.org/TR/PNG/ +// Parts of the code have been adapted from LodePNG, by Lode Vandevenne: +// http://members.gamedev.net/lode/projects/LodePNG/ + +/* +LodePNG version 20101211 + +Copyright (c) 2005-2010 Lode Vandevenne + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +namespace Graphics { + +enum PNGChunks { + // == Critical chunks ===================================================== + kChunkIHDR = MKTAG('I','H','D','R'), // Image header + kChunkIDAT = MKTAG('I','D','A','T'), // Image data + kChunkPLTE = MKTAG('P','L','T','E'), // Palette + kChunkIEND = MKTAG('I','E','N','D'), // Image trailer + // == Ancillary chunks ==================================================== + kChunktRNS = MKTAG('t','R','N','S') // Transparency + // All of the other ancillary chunks are ignored. They're added here for + // reference only. + // cHRM - Primary chromacities and white point + // gAMA - Image gamma + // iCCP - Embedded ICC profile + // sBIT - Significant bits + // sRGB - Standard RGB color space + // tEXT - Textual data + // sTXt - Compressed textual data + // iTXt - International textual data + // bKGD - Background color + // hIST - Image histogram + // pHYs - Physical pixel dimensions + // sPLT - Suggested palette + // tIME - Image last-modification time +}; + +// Refer to http://www.w3.org/TR/PNG/#9Filters +enum PNGFilters { + kFilterNone = 0, + kFilterSub = 1, + kFilterUp = 2, + kFilterAverage = 3, + kFilterPaeth = 4 +}; + +PNGDecoder::PNGDecoder() : _compressedBuffer(0), _compressedBufferSize(0), + _transparentColorSpecified(false), _outputSurface(0) { +} + +PNGDecoder::~PNGDecoder() { + destroy(); +} + +void PNGDecoder::destroy() { + if (_outputSurface) { + _outputSurface->free(); + delete _outputSurface; + _outputSurface = 0; + } +} + +bool PNGDecoder::loadStream(Common::SeekableReadStream &stream) { + destroy(); + + uint32 chunkLength = 0, chunkType = 0; + _stream = &stream; + + // First, check the PNG signature + if (_stream->readUint32BE() != MKTAG(0x89, 'P', 'N', 'G')) { + delete _stream; + return false; + } + if (_stream->readUint32BE() != MKTAG(0x0d, 0x0a, 0x1a, 0x0a)) { + delete _stream; + return false; + } + + // Start reading chunks till we reach an IEND chunk + while (chunkType != kChunkIEND) { + // The chunk length does not include the type or CRC bytes + chunkLength = _stream->readUint32BE(); + chunkType = _stream->readUint32BE(); + + switch (chunkType) { + case kChunkIHDR: + readHeaderChunk(); + break; + case kChunkIDAT: + if (_compressedBufferSize == 0) { + _compressedBufferSize += chunkLength; + _compressedBuffer = (byte *)malloc(_compressedBufferSize); + _stream->read(_compressedBuffer, chunkLength); + } else { + // Expand the buffer + uint32 prevSize = _compressedBufferSize; + _compressedBufferSize += chunkLength; + byte *tmp = new byte[prevSize]; + memcpy(tmp, _compressedBuffer, prevSize); + free(_compressedBuffer); + _compressedBuffer = (byte *)malloc(_compressedBufferSize); + memcpy(_compressedBuffer, tmp, prevSize); + delete[] tmp; + _stream->read(_compressedBuffer + prevSize, chunkLength); + } + break; + case kChunkPLTE: // only available in indexed PNGs + if (_header.colorType != kIndexed) + error("A palette chunk has been found in a non-indexed PNG file"); + if (chunkLength % 3 != 0) + error("Palette chunk not divisible by 3"); + + _paletteEntries = chunkLength / 3; + _stream->read(_palette, _paletteEntries * 3); + memset(_paletteTransparency, 0xff, sizeof(_paletteTransparency)); + break; + case kChunkIEND: + // End of stream + break; + case kChunktRNS: + readTransparencyChunk(chunkLength); + break; + default: + // Skip the chunk content + _stream->skip(chunkLength); + break; + } + + if (chunkType != kChunkIEND) + _stream->skip(4); // skip the chunk CRC checksum + } + + // We no longer need the file stream, thus close it here + _stream = 0; + + // Unpack the compressed buffer + Common::MemoryReadStream *compData = new Common::MemoryReadStream(_compressedBuffer, _compressedBufferSize, DisposeAfterUse::YES); + _imageData = Common::wrapCompressedReadStream(compData); + + // Construct the final image + constructImage(); + + // Close the uncompressed stream, which will also delete the memory stream, + // and thus the original compressed buffer + delete _imageData; + + return true; +} + +/** + * Paeth predictor, used by PNG filter type 4 + * The parameters are of signed 16-bit integers, but should come + * from unsigned chars. The integers are only needed to make + * the paeth calculation correct. + * + * Taken from lodePNG, with a slight patch: + * http://www.atalasoft.com/cs/blogs/stevehawley/archive/2010/02/23/libpng-you-re-doing-it-wrong.aspx + */ +byte PNGDecoder::paethPredictor(int16 a, int16 b, int16 c) { + int16 pa = ABS(b - c); + int16 pb = ABS(a - c); + int16 pc = ABS(a + b - c - c); + + if (pa <= MIN(pb, pc)) + return (byte)a; + else if (pb <= pc) + return (byte)b; + else + return (byte)c; +} + +/** + * Unfilters a filtered PNG scan line. + * PNG filters are defined in: http://www.w3.org/TR/PNG/#9Filters + * Note that filters are always applied to bytes + * + * Taken from lodePNG + */ +void PNGDecoder::unfilterScanLine(byte *dest, const byte *scanLine, const byte *prevLine, uint16 byteWidth, byte filterType, uint16 length) { + uint16 i; + + switch (filterType) { + case kFilterNone: // no change + for (i = 0; i < length; i++) + dest[i] = scanLine[i]; + break; + case kFilterSub: // add the bytes to the left + for (i = 0; i < byteWidth; i++) + dest[i] = scanLine[i]; + for (i = byteWidth; i < length; i++) + dest[i] = scanLine[i] + dest[i - byteWidth]; + break; + case kFilterUp: // add the bytes of the above scanline + if (prevLine) { + for (i = 0; i < length; i++) + dest[i] = scanLine[i] + prevLine[i]; + } else { + for (i = 0; i < length; i++) + dest[i] = scanLine[i]; + } + break; + case kFilterAverage: // average value of the left and top left + if (prevLine) { + for (i = 0; i < byteWidth; i++) + dest[i] = scanLine[i] + prevLine[i] / 2; + for (i = byteWidth; i < length; i++) + dest[i] = scanLine[i] + ((dest[i - byteWidth] + prevLine[i]) / 2); + } else { + for (i = 0; i < byteWidth; i++) + dest[i] = scanLine[i]; + for (i = byteWidth; i < length; i++) + dest[i] = scanLine[i] + dest[i - byteWidth] / 2; + } + break; + case kFilterPaeth: // Paeth filter: http://www.w3.org/TR/PNG/#9Filter-type-4-Paeth + if (prevLine) { + for(i = 0; i < byteWidth; i++) + dest[i] = (scanLine[i] + prevLine[i]); // paethPredictor(0, prevLine[i], 0) is always prevLine[i] + for(i = byteWidth; i < length; i++) + dest[i] = (scanLine[i] + paethPredictor(dest[i - byteWidth], prevLine[i], prevLine[i - byteWidth])); + } else { + for(i = 0; i < byteWidth; i++) + dest[i] = scanLine[i]; + for(i = byteWidth; i < length; i++) + dest[i] = (scanLine[i] + dest[i - byteWidth]); // paethPredictor(dest[i - byteWidth], 0, 0) is always dest[i - byteWidth] + } + break; + default: + error("Unknown line filter"); + } + +} + +int PNGDecoder::getBytesPerPixel() const { + return (getNumColorChannels() * _header.bitDepth + 7) / 8; +} + +void PNGDecoder::constructImage() { + assert (_header.bitDepth != 0); + + int bytesPerPixel = getBytesPerPixel(); + int pitch = bytesPerPixel * _header.width; + byte *unfilteredSurface = new byte[pitch * _header.height]; + byte *dest = unfilteredSurface; + uint16 scanLineWidth = (_header.width * getNumColorChannels() * _header.bitDepth + 7) / 8; + byte *scanLine = new byte[scanLineWidth]; + byte *prevLine = 0; + + switch(_header.interlaceType) { + case kNonInterlaced: + for (uint16 y = 0; y < _header.height; y++) { + byte filterType = _imageData->readByte(); + _imageData->read(scanLine, scanLineWidth); + unfilterScanLine(dest, scanLine, prevLine, bytesPerPixel, filterType, scanLineWidth); + prevLine = dest; + dest += pitch; + } + break; + case kInterlaced: + // Theoretically, this shouldn't be needed, as interlacing is only + // useful for web images. Interlaced PNG images require more complex + // handling, so unless having support for such images is needed, there + // is no reason to add support for them. + error("TODO: Support for interlaced PNG images"); + break; + } + + delete[] scanLine; + + constructOutput(unfilteredSurface); + delete[] unfilteredSurface; +} + +Graphics::PixelFormat PNGDecoder::findPixelFormat() const { + // Try to find the best pixel format based on what we have here + // Which is basically 8bpp for paletted non-transparent + // and 32bpp for everything else + + switch (_header.colorType) { + case kIndexed: + if (!_transparentColorSpecified) + return Graphics::PixelFormat::createFormatCLUT8(); + // fall through + case kGrayScale: + case kTrueColor: + case kGrayScaleWithAlpha: + case kTrueColorWithAlpha: + // We'll go with standard RGBA 32-bit + return Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0); + } + + error("Unknown PNG color type"); + return Graphics::PixelFormat(); +} + +void PNGDecoder::constructOutput(const byte *surface) { + _outputSurface = new Graphics::Surface(); + _outputSurface->create(_header.width, _header.height, findPixelFormat()); + + const byte *src = surface; + byte a = 0xFF; + int bytesPerPixel = getBytesPerPixel(); + + if (_header.colorType != kIndexed) { + if (_header.colorType == kTrueColor || + _header.colorType == kTrueColorWithAlpha) { + if (bytesPerPixel != 3 && bytesPerPixel != 4) + error("Unsupported truecolor PNG format"); + } else if (_header.colorType == kGrayScale || + _header.colorType == kGrayScaleWithAlpha) { + if (bytesPerPixel != 1 && bytesPerPixel != 2) + error("Unsupported grayscale PNG format"); + } + + for (uint16 i = 0; i < _outputSurface->h; i++) { + for (uint16 j = 0; j < _outputSurface->w; j++) { + uint32 result = 0; + + switch (bytesPerPixel) { + case 1: // Grayscale + if (_transparentColorSpecified) + a = (src[0] == _transparentColor[0]) ? 0 : 0xFF; + result = _outputSurface->format.ARGBToColor(a, src[0], src[0], src[0]); + break; + case 2: // Grayscale + alpha + result = _outputSurface->format.ARGBToColor(src[1], src[0], src[0], src[0]); + break; + case 3: // RGB + if (_transparentColorSpecified) { + bool isTransparentColor = (src[0] == _transparentColor[0] && + src[1] == _transparentColor[1] && + src[2] == _transparentColor[2]); + a = isTransparentColor ? 0 : 0xFF; + } + + result = _outputSurface->format.ARGBToColor(a, src[0], src[1], src[2]); + break; + case 4: // RGBA + result = _outputSurface->format.ARGBToColor(src[3], src[0], src[1], src[2]); + break; + } + + *((uint32 *)_outputSurface->getBasePtr(j, i)) = result; + src += bytesPerPixel; + } + } + } else { + uint32 mask = (0xff >> (8 - _header.bitDepth)) << (8 - _header.bitDepth); + + // Convert the indexed surface to the target pixel format + for (uint16 i = 0; i < _outputSurface->h; i++) { + int data = 0; + int bitCount = 8; + const byte *src1 = src; + + for (uint16 j = 0; j < _outputSurface->w; j++) { + if (bitCount == 8) { + data = *src; + src++; + } + + byte index = (data & mask) >> (8 - _header.bitDepth); + data = (data << _header.bitDepth) & 0xff; + bitCount -= _header.bitDepth; + + if (bitCount == 0) + bitCount = 8; + + if (_transparentColorSpecified) { + byte r = _palette[index * 3 + 0]; + byte g = _palette[index * 3 + 1]; + byte b = _palette[index * 3 + 2]; + a = _paletteTransparency[index]; + *((uint32 *)_outputSurface->getBasePtr(j, i)) = _outputSurface->format.ARGBToColor(a, r, g, b); + } else { + *((byte *)_outputSurface->getBasePtr(j, i)) = index; + } + } + + src = src1 + _outputSurface->w; + } + } +} + +void PNGDecoder::readHeaderChunk() { + _header.width = _stream->readUint32BE(); + _header.height = _stream->readUint32BE(); + _header.bitDepth = _stream->readByte(); + if (_header.bitDepth > 8) + error("Only PNGs with a bit depth of 1-8 bits are supported (i.e. PNG24)"); + _header.colorType = (PNGColorType)_stream->readByte(); + _header.compressionMethod = _stream->readByte(); + // Compression methods: http://www.w3.org/TR/PNG/#10Compression + // Only compression method 0 (deflate) is documented and supported + if (_header.compressionMethod != 0) + error("Unknown PNG compression method: %d", _header.compressionMethod); + _header.filterMethod = _stream->readByte(); + // Filter methods: http://www.w3.org/TR/PNG/#9Filters + // Only filter method 0 is documented and supported + if (_header.filterMethod != 0) + error("Unknown PNG filter method: %d", _header.filterMethod); + _header.interlaceType = (PNGInterlaceType)_stream->readByte(); +} + +byte PNGDecoder::getNumColorChannels() const { + switch (_header.colorType) { + case kGrayScale: + return 1; // Gray + case kTrueColor: + return 3; // RGB + case kIndexed: + return 1; // Indexed + case kGrayScaleWithAlpha: + return 2; // Gray + Alpha + case kTrueColorWithAlpha: + return 4; // RGBA + default: + error("Unknown color type"); + } +} + +void PNGDecoder::readTransparencyChunk(uint32 chunkLength) { + _transparentColorSpecified = true; + + switch(_header.colorType) { + case kGrayScale: + _transparentColor[0] = _stream->readUint16BE(); + _transparentColor[1] = _transparentColor[0]; + _transparentColor[2] = _transparentColor[0]; + break; + case kTrueColor: + _transparentColor[0] = _stream->readUint16BE(); + _transparentColor[1] = _stream->readUint16BE(); + _transparentColor[2] = _stream->readUint16BE(); + break; + case kIndexed: + _stream->read(_paletteTransparency, chunkLength); + + // A transparency chunk may have less entries + // than the palette entries. The remaining ones + // are unmodified (set to 255). Check here: + // http://www.w3.org/TR/PNG/#11tRNS + break; + default: + error("Transparency chunk found in a PNG that has a separate transparency channel"); + } +} + +} // End of Graphics namespace diff --git a/graphics/decoders/png.h b/graphics/decoders/png.h new file mode 100644 index 0000000000..1da0bea1ab --- /dev/null +++ b/graphics/decoders/png.h @@ -0,0 +1,135 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/* + * PNG decoder used in engines: + * - sword25 + * Dependencies: + * - zlib + */ + +#ifndef GRAPHICS_PNG_H +#define GRAPHICS_PNG_H + +// PNG decoder, based on the W3C specs: +// http://www.w3.org/TR/PNG/ +// Parts of the code have been adapted from LodePNG, by Lode Vandevenne: +// http://members.gamedev.net/lode/projects/LodePNG/ + +// All the numbers are BE: http://www.w3.org/TR/PNG/#7Integers-and-byte-order + +// Note: At the moment, this decoder only supports non-interlaced images, and +// does not support truecolor/grayscale images with 16bit depth. +// +// Theoretically, interlaced images shouldn't be needed for games, as +// interlacing is only useful for images in websites. +// +// PNG images with 16bit depth (i.e. 48bit images) are quite rare, and can +// theoretically contain more than 16.7 millions of colors (the so-called "deep +// color" representation). In essence, each of the R, G, B and A components in +// them is specified with 2 bytes, instead of 1. However, the PNG specification +// always refers to color components with 1 byte each, so this part of the spec +// is a bit unclear. For now, these won't be supported, until a suitable sample +// is found. + +#include "common/scummsys.h" +#include "common/textconsole.h" +#include "graphics/decoders/image_decoder.h" + +namespace Common { +class SeekableReadStream; +} + +namespace Graphics { + +struct Surface; +struct PixelFormat; + +class PNGDecoder : public ImageDecoder { +public: + PNGDecoder(); + ~PNGDecoder(); + + bool loadStream(Common::SeekableReadStream &stream); + void destroy(); + const Graphics::Surface *getSurface() const { return _outputSurface; } + const byte *getPalette() const { return _palette; } + +private: + enum PNGColorType { + kGrayScale = 0, // bit depths: 1, 2, 4, 8, 16 + kTrueColor = 2, // bit depths: 8, 16 + kIndexed = 3, // bit depths: 1, 2, 4, 8 + kGrayScaleWithAlpha = 4, // bit depths: 8, 16 + kTrueColorWithAlpha = 6 // bit depths: 8, 16 + }; + + enum PNGInterlaceType { + kNonInterlaced = 0, + kInterlaced = 1 + }; + + struct PNGHeader { + uint32 width; + uint32 height; + byte bitDepth; + PNGColorType colorType; + byte compressionMethod; + byte filterMethod; + PNGInterlaceType interlaceType; + }; + + void readHeaderChunk(); + byte getNumColorChannels() const; + + void readPaletteChunk(); + void readTransparencyChunk(uint32 chunkLength); + + void constructImage(); + void unfilterScanLine(byte *dest, const byte *scanLine, const byte *prevLine, uint16 byteWidth, byte filterType, uint16 length); + byte paethPredictor(int16 a, int16 b, int16 c); + + // The original file stream + Common::SeekableReadStream *_stream; + // The unzipped image data stream + Common::SeekableReadStream *_imageData; + + PNGHeader _header; + + byte _palette[256 * 3]; // RGB + byte _paletteTransparency[256]; + uint16 _paletteEntries; + uint16 _transparentColor[3]; + bool _transparentColorSpecified; + + byte *_compressedBuffer; + uint32 _compressedBufferSize; + + Graphics::Surface *_outputSurface; + Graphics::PixelFormat findPixelFormat() const; + int getBytesPerPixel() const; + void constructOutput(const byte *surface); +}; + +} // End of namespace Graphics + +#endif // GRAPHICS_PNG_H -- cgit v1.2.3 From fce0e8c45b53018e5c9a2cab38c02a782872f22b Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Mon, 19 Mar 2012 21:54:37 -0400 Subject: GRAPHICS: Fix PICT 16bpp --- graphics/decoders/pict.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'graphics/decoders') diff --git a/graphics/decoders/pict.cpp b/graphics/decoders/pict.cpp index f8b2553ea0..4c21cffe38 100644 --- a/graphics/decoders/pict.cpp +++ b/graphics/decoders/pict.cpp @@ -365,7 +365,7 @@ void PICTDecoder::unpackBitsRect(Common::SeekableReadStream &stream, bool hasPal _outputSurface->create(width, height, PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0)); for (uint16 y = 0; y < _outputSurface->h; y++) for (uint16 x = 0; x < _outputSurface->w; x++) - WRITE_UINT16(_outputSurface->getBasePtr(x, y), READ_BE_UINT16(buffer + (y * _outputSurface->w + x) * 2)); + WRITE_UINT16(_outputSurface->getBasePtr(x, y), READ_UINT16(buffer + (y * _outputSurface->w + x) * 2)); break; case 3: // Convert from 24-bit (planar!) to whatever surface we need -- cgit v1.2.3 From e79f0bf717c31fb599a28ffb3e210ba7b0a300b0 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Mon, 19 Mar 2012 23:29:58 -0400 Subject: GRAPHICS: Fix regression caused by a bad rebase --- graphics/decoders/pict.cpp | 46 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) (limited to 'graphics/decoders') diff --git a/graphics/decoders/pict.cpp b/graphics/decoders/pict.cpp index 4c21cffe38..957342084e 100644 --- a/graphics/decoders/pict.cpp +++ b/graphics/decoders/pict.cpp @@ -512,18 +512,54 @@ void PICTDecoder::skipBitsRect(Common::SeekableReadStream &stream, bool hasPalet // 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) { - JPEGDecoder jpeg; - + // First, read all the fields from the opcode uint32 dataSize = stream.readUint32BE(); uint32 startPos = stream.pos(); - Common::SeekableSubReadStream jpegStream(&stream, stream.pos() + 156, stream.pos() + dataSize); + /* uint16 version = */ stream.readUint16BE(); + // Read in the display matrix + uint32 matrix[3][3]; + for (uint32 i = 0; i < 3; i++) + for (uint32 j = 0; j < 3; j++) + matrix[i][j] = stream.readUint32BE(); + + // We currently only support offseting images vertically from the matrix + uint16 xOffset = 0; + uint16 yOffset = matrix[2][1] >> 16; + + uint32 matteSize = stream.readUint32BE(); + stream.skip(8); // matte rect + /* uint16 transferMode = */ stream.readUint16BE(); + stream.skip(8); // src rect + /* uint32 accuracy = */ stream.readUint32BE(); + uint32 maskSize = stream.readUint32BE(); + + // Skip the matte and mask + stream.skip(matteSize + maskSize); + + // Now we've reached the image descriptor, so read the relevant data from that + uint32 idStart = stream.pos(); + uint32 idSize = stream.readUint32BE(); + stream.skip(40); // miscellaneous stuff + uint32 jpegSize = stream.readUint32BE(); + stream.skip(idSize - (stream.pos() - idStart)); // more useless stuff + + Common::SeekableSubReadStream jpegStream(&stream, stream.pos(), stream.pos() + jpegSize); + + JPEGDecoder jpeg; if (!jpeg.loadStream(jpegStream)) error("PICTDecoder::decodeCompressedQuickTime(): Could not decode JPEG data"); - _outputSurface = new Graphics::Surface(); - _outputSurface->copyFrom(*jpeg.getSurface()); + 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); } -- cgit v1.2.3