[Scummvm-git-logs] scummvm master -> 399e02e2a5faacfdc78e5863f994db1ee338d824
lephilousophe
noreply at scummvm.org
Tue Aug 30 09:02:52 UTC 2022
This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
399e02e2a5 CRYOMNI3D: Add HNM6 image and video codec
Commit: 399e02e2a5faacfdc78e5863f994db1ee338d824
https://github.com/scummvm/scummvm/commit/399e02e2a5faacfdc78e5863f994db1ee338d824
Author: Le Philousophe (lephilousophe at users.noreply.github.com)
Date: 2022-08-30T11:02:32+02:00
Commit Message:
CRYOMNI3D: Add HNM6 image and video codec
This also adds a Cryo APC decoder in shared code.
Changed paths:
A audio/decoders/apc.cpp
A audio/decoders/apc.h
A engines/cryomni3d/image/hnm.cpp
A engines/cryomni3d/image/hnm.h
A image/codecs/hnm.cpp
A image/codecs/hnm.h
audio/module.mk
engines/cryomni3d/cryomni3d.cpp
engines/cryomni3d/module.mk
engines/cryomni3d/versailles/dialogs_manager.cpp
image/module.mk
video/hnm_decoder.cpp
video/hnm_decoder.h
diff --git a/audio/decoders/apc.cpp b/audio/decoders/apc.cpp
new file mode 100644
index 00000000000..ae5acd7b775
--- /dev/null
+++ b/audio/decoders/apc.cpp
@@ -0,0 +1,185 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "common/inttypes.h"
+#include "common/ptr.h"
+#include "common/stream.h"
+#include "common/textconsole.h"
+#include "common/util.h"
+
+#include "audio/decoders/adpcm_intern.h"
+#include "audio/decoders/apc.h"
+#include "audio/decoders/raw.h"
+
+namespace Audio {
+
+class APCStreamImpl : public APCStream {
+public:
+ // These parameters are completely optional and only used in HNM videos to make sure data is consistent
+ APCStreamImpl(uint sampleRate = uint(-1), byte stereo = byte(-1));
+
+ bool init(Common::SeekableReadStream &header) override;
+
+ // AudioStream API
+ int readBuffer(int16 *buffer, const int numSamples) override { return _audStream->readBuffer(buffer, numSamples); }
+ bool isStereo() const override { return _audStream->isStereo(); }
+ int getRate() const override { return _audStream->getRate(); }
+ bool endOfData() const override { return _audStream->endOfData(); }
+ bool endOfStream() const override { return _audStream->endOfStream(); }
+
+ // PacketizedAudioStream API
+ void queuePacket(Common::SeekableReadStream *data) override;
+ void finish() override { _audStream->finish(); }
+
+private:
+ struct Status {
+ int32 last;
+ uint32 stepIndex;
+ int16 step;
+ };
+
+ FORCEINLINE static int16 decodeIMA(byte code, Status *status);
+
+ Common::ScopedPtr<QueuingAudioStream> _audStream;
+ byte _stereo;
+ uint32 _sampleRate;
+
+ Status _status[2];
+};
+
+APCStreamImpl::APCStreamImpl(uint32 sampleRate, byte stereo) :
+ _sampleRate(sampleRate), _stereo(stereo) {
+ if (_sampleRate != uint32(-1) && _stereo != byte(-1)) {
+ _audStream.reset(makeQueuingAudioStream(_sampleRate, _stereo));
+ }
+}
+
+bool APCStreamImpl::init(Common::SeekableReadStream &header) {
+ // Header size
+ if (header.size() < 32) {
+ return false;
+ }
+
+ // Read magic and version at once
+ byte magicVersion[12];
+ if (header.read(magicVersion, sizeof(magicVersion)) != sizeof(magicVersion)) {
+ return false;
+ }
+ if (memcmp(magicVersion, "CRYO_APC1.20", sizeof(magicVersion))) {
+ return false;
+ }
+
+ //uint32 num_samples = header.readUint32LE();
+ header.skip(4);
+
+ uint32 samplerate = header.readUint32LE();
+ if (_sampleRate != uint32(-1) && _sampleRate != samplerate) {
+ error("Samplerate mismatch");
+ return false;
+ }
+ _sampleRate = samplerate;
+
+ uint32 leftSample = header.readSint32LE();
+ uint32 rightSample = header.readSint32LE();
+
+ uint32 audioFlags = header.readUint32LE();
+ byte stereo = (audioFlags & 1);
+ if (_stereo != byte(-1) && _stereo != stereo) {
+ error("Channels mismatch");
+ return false;
+
+ }
+ _stereo = stereo;
+
+ _status[0].last = leftSample;
+ _status[1].last = rightSample;
+ _status[0].stepIndex = _status[1].stepIndex = 0;
+ _status[0].step = _status[1].step = Ima_ADPCMStream::_imaTable[0];
+
+ if (!_audStream) {
+ _audStream.reset(makeQueuingAudioStream(_sampleRate, _stereo));
+ }
+
+ return true;
+}
+
+void APCStreamImpl::queuePacket(Common::SeekableReadStream *data) {
+ Common::ScopedPtr<Common::SeekableReadStream> packet(data);
+
+ uint32 size = packet->size() - packet->pos();
+ if (size == 0) {
+ return;
+ }
+
+ // From 4-bits samples to 16-bits
+ int16 *outputBuffer = (int16 *)malloc(size * 4);
+ int16 *outputPtr = outputBuffer;
+
+ int channelOffset = (_stereo ? 1 : 0);
+
+ for (uint32 counter = size; counter > 0; counter--) {
+ byte word = packet->readByte();
+ *(outputPtr++) = decodeIMA((word >> 4) & 0x0f, _status);
+ *(outputPtr++) = decodeIMA((word >> 0) & 0x0f, _status + channelOffset);
+ }
+
+ byte flags = FLAG_16BITS;
+ if (_stereo) {
+ flags |= FLAG_STEREO;
+ }
+#ifdef SCUMM_LITTLE_ENDIAN
+ flags |= Audio::FLAG_LITTLE_ENDIAN;
+#endif
+ _audStream->queueBuffer((byte *)outputBuffer, size * 4, DisposeAfterUse::YES, flags);
+}
+
+int16 APCStreamImpl::decodeIMA(byte code, Status *status) {
+ int32 E = (2 * (code & 0x7) + 1) * status->step / 8;
+ int32 diff = (code & 0x08) ? -E : E;
+ // In Cryo APC data is only truncated and not CLIPed as expected
+ int16 samp = (status->last + diff);
+
+ int32 index = status->stepIndex + Ima_ADPCMStream::_stepAdjustTable[code];
+ index = CLIP<int32>(index, 0, ARRAYSIZE(Ima_ADPCMStream::_imaTable) - 1);
+
+ status->last = samp;
+ status->stepIndex = index;
+ status->step = Ima_ADPCMStream::_imaTable[index];
+
+ return samp;
+}
+
+PacketizedAudioStream *makeAPCStream(Common::SeekableReadStream &header) {
+ Common::ScopedPtr<APCStream> stream(new APCStreamImpl());
+ if (!stream->init(header)) {
+ return nullptr;
+ }
+
+ return stream.release();
+}
+
+APCStream *makeAPCStream(uint sampleRate, bool stereo) {
+ assert((sampleRate % 11025) == 0);
+ return new APCStreamImpl(sampleRate, stereo ? 1 : 0);
+}
+
+} // End of namespace Audio
+
diff --git a/audio/decoders/apc.h b/audio/decoders/apc.h
new file mode 100644
index 00000000000..1d8cac3fb77
--- /dev/null
+++ b/audio/decoders/apc.h
@@ -0,0 +1,61 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef AUDIO_DECODERS_APC_H
+#define AUDIO_DECODERS_APC_H
+
+#include "common/scummsys.h"
+#include "audio/audiostream.h"
+
+namespace Common {
+class SeekableReadStream;
+} // End of namespace Common
+
+namespace Audio {
+
+class APCStream : public PacketizedAudioStream {
+public:
+ virtual bool init(Common::SeekableReadStream &header) = 0;
+};
+
+/**
+ * Create a PacketizedAudioStream that decodes Cryo APC sound from stream
+ *
+ * @param header The stream containing the header
+ * queuePacket must be called after
+ * @return A new PacketizedAudioStream, or nullptr on error
+ */
+PacketizedAudioStream *makeAPCStream(Common::SeekableReadStream &header);
+
+/**
+ * Create a PacketizedAudioStream that decodes Cryo APC sound using predefined settings
+ * This is used by HNM6 video decoder and shouldn't be called elsewhere.
+ *
+ * @param sampleRate The sample rate of the stream
+ * @param stereo Whether the stream will be stereo
+ * @return A new APCStream
+ */
+APCStream *makeAPCStream(uint sampleRate, bool stereo);
+
+} // End of namespace Audio
+
+#endif
+
diff --git a/audio/module.mk b/audio/module.mk
index 5f670ab18ef..93af443687f 100644
--- a/audio/module.mk
+++ b/audio/module.mk
@@ -27,6 +27,7 @@ MODULE_OBJS := \
decoders/aac.o \
decoders/adpcm.o \
decoders/aiff.o \
+ decoders/apc.o \
decoders/asf.o \
decoders/flac.o \
decoders/g711.o \
diff --git a/engines/cryomni3d/cryomni3d.cpp b/engines/cryomni3d/cryomni3d.cpp
index 51975e5e6b1..3a4d0ab8f11 100644
--- a/engines/cryomni3d/cryomni3d.cpp
+++ b/engines/cryomni3d/cryomni3d.cpp
@@ -37,6 +37,7 @@
#include "cryomni3d/datstream.h"
#include "cryomni3d/image/hlz.h"
+#include "cryomni3d/image/hnm.h"
#include "video/hnm_decoder.h"
namespace CryOmni3D {
@@ -134,11 +135,15 @@ void CryOmni3DEngine::playHNM(const Common::String &filename, Audio::Mixer::Soun
const char *const extensions[] = { "hns", "hnm", "ubb", nullptr };
Common::String fname(prepareFileName(filename, extensions));
- byte *currentPalette = new byte[256 * 3];
- g_system->getPaletteManager()->grabPalette(currentPalette, 0, 256);
+ Graphics::PixelFormat screenFormat = g_system->getScreenFormat();
+ byte *currentPalette = nullptr;
+ if (screenFormat.bytesPerPixel == 1) {
+ currentPalette = new byte[256 * 3];
+ g_system->getPaletteManager()->grabPalette(currentPalette, 0, 256);
+ }
// Pass the ownership of currentPalette to HNMDecoder
- Video::VideoDecoder *videoDecoder = new Video::HNMDecoder(false, currentPalette);
+ Video::VideoDecoder *videoDecoder = new Video::HNMDecoder(screenFormat, false, currentPalette);
videoDecoder->setSoundType(soundType);
if (!videoDecoder->loadFile(fname)) {
diff --git a/engines/cryomni3d/image/hnm.cpp b/engines/cryomni3d/image/hnm.cpp
new file mode 100644
index 00000000000..696a7b2ae73
--- /dev/null
+++ b/engines/cryomni3d/image/hnm.cpp
@@ -0,0 +1,116 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "cryomni3d/image/hnm.h"
+
+#include "common/stream.h"
+#include "common/substream.h"
+#include "common/textconsole.h"
+#include "graphics/pixelformat.h"
+#include "graphics/surface.h"
+#include "image/codecs/codec.h"
+
+#include "image/codecs/hnm.h"
+
+namespace Image {
+
+HNMFileDecoder::HNMFileDecoder(const Graphics::PixelFormat &format) :
+ _format(format), _surface(nullptr), _codec(nullptr) {
+}
+
+HNMFileDecoder::~HNMFileDecoder() {
+ destroy();
+}
+
+void HNMFileDecoder::destroy() {
+ delete _codec;
+ _codec = nullptr;
+ _surface = nullptr;
+}
+
+bool HNMFileDecoder::loadStream(Common::SeekableReadStream &stream) {
+ destroy();
+
+ uint32 tag = stream.readUint32BE();
+
+ /* Only HNM6 for HNM images */
+ if (tag != MKTAG('H', 'N', 'M', '6')) {
+ return false;
+ }
+
+ //uint16 ukn = stream.readUint16BE();
+ //byte audioflags = stream.readByte();
+ //byte bpp = stream.readByte();
+ stream.skip(4);
+ uint16 width = stream.readUint16LE();
+ uint16 height = stream.readUint16LE();
+ //uint32 filesize = stream.readUint32LE();
+ //uint32 numframes = stream.readUint32LE();
+ //uint32 ukn2 = stream.readUint32LE();
+ //uint16 speed = stream.readUint16LE();
+ //uint16 maxbuffer = stream.readUint16LE();
+ //uint32 buffer_size = stream.readUint32LE();
+ //byte unknownStr[16];
+ //byte copyright[16];
+ //stream.read(unknownStr, sizeof(unknownStr));
+ //stream.read(copyright, sizeof(copyright));
+
+ stream.skip(52);
+
+ if (width == 0 || height == 0) {
+ return false;
+ }
+
+ // Read frame header
+ uint32 frameSize = stream.readUint32LE();
+ if (frameSize < 12) {
+ return false;
+ }
+ frameSize -= 4;
+
+ // Read chunk header
+ uint32 chunkSize = stream.readUint32LE();
+ uint16 chunkTag = stream.readUint16BE();
+ //uint16 chunkUkn = stream.readUint16LE();
+ stream.skip(2);
+
+ if (frameSize < chunkSize ||
+ chunkSize < 8 + 24) {
+ return false;
+ }
+
+ bool warp;
+ if (chunkTag == MKTAG16('I', 'W')) {
+ warp = true;
+ } else if (chunkTag == MKTAG16('I', 'X')) {
+ warp = false;
+ } else {
+ return false;
+ }
+
+ // buffer_size is not reliable on IW and we already have the real size of the image source
+ _codec = createHNM6Decoder(width, height, _format, chunkSize, false);
+ _codec->setWarpMode(warp);
+ _surface = _codec->decodeFrame(stream);
+ return true;
+}
+
+} // End of namespace Image
diff --git a/engines/cryomni3d/image/hnm.h b/engines/cryomni3d/image/hnm.h
new file mode 100644
index 00000000000..2b3af7784a7
--- /dev/null
+++ b/engines/cryomni3d/image/hnm.h
@@ -0,0 +1,59 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef CRYOMNI3D_IMAGE_HNM_H
+#define CRYOMNI3D_IMAGE_HNM_H
+
+#include "common/scummsys.h"
+#include "common/str.h"
+#include "graphics/pixelformat.h"
+#include "image/image_decoder.h"
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Graphics {
+struct Surface;
+}
+
+namespace Image {
+class HNM6Decoder;
+
+class HNMFileDecoder : public ImageDecoder {
+public:
+ HNMFileDecoder(const Graphics::PixelFormat &format);
+ ~HNMFileDecoder() override;
+
+ // ImageDecoder API
+ void destroy() override;
+ bool loadStream(Common::SeekableReadStream &stream) override;
+ const Graphics::Surface *getSurface() const override { return _surface; }
+
+private:
+ Graphics::PixelFormat _format;
+ HNM6Decoder *_codec;
+ const Graphics::Surface *_surface;
+};
+
+} // End of namespace Image
+
+#endif
diff --git a/engines/cryomni3d/module.mk b/engines/cryomni3d/module.mk
index 9d2c7ded04a..d6875ae1ea9 100644
--- a/engines/cryomni3d/module.mk
+++ b/engines/cryomni3d/module.mk
@@ -4,6 +4,7 @@ MODULE_OBJS = \
fonts/cryoextfont.o \
fonts/cryofont.o \
image/hlz.o \
+ image/hnm.o \
cryomni3d.o \
datstream.o \
dialogs_manager.o \
diff --git a/engines/cryomni3d/versailles/dialogs_manager.cpp b/engines/cryomni3d/versailles/dialogs_manager.cpp
index 0b3c656bdcc..2a7f83083a3 100644
--- a/engines/cryomni3d/versailles/dialogs_manager.cpp
+++ b/engines/cryomni3d/versailles/dialogs_manager.cpp
@@ -104,7 +104,7 @@ void Versailles_DialogsManager::playDialog(const Common::String &video, const Co
}
soundFName = _engine->prepareFileName(soundFName, "wav");
- Video::HNMDecoder *videoDecoder = new Video::HNMDecoder(true);
+ Video::HNMDecoder *videoDecoder = new Video::HNMDecoder(g_system->getScreenFormat(), true);
if (!videoDecoder->loadFile(videoFName)) {
warning("Failed to open movie file %s/%s", video.c_str(), videoFName.c_str());
@@ -355,7 +355,7 @@ uint Versailles_DialogsManager::askPlayerQuestions(const Common::String &video,
void Versailles_DialogsManager::loadFrame(const Common::String &video) {
Common::String videoFName(_engine->prepareFileName(video, "hnm"));
- Video::HNMDecoder *videoDecoder = new Video::HNMDecoder();
+ Video::HNMDecoder *videoDecoder = new Video::HNMDecoder(g_system->getScreenFormat());
if (!videoDecoder->loadFile(videoFName)) {
warning("Failed to open movie file %s/%s", video.c_str(), videoFName.c_str());
diff --git a/image/codecs/hnm.cpp b/image/codecs/hnm.cpp
new file mode 100644
index 00000000000..d9bf1a9a74c
--- /dev/null
+++ b/image/codecs/hnm.cpp
@@ -0,0 +1,1404 @@
+/* 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "image/codecs/hnm.h"
+
+#include "common/stream.h"
+#include "common/textconsole.h"
+#include "common/util.h"
+
+#include "graphics/surface.h"
+
+#include "common/debug.h"
+
+// This define enables code which generate bit accurate images in RGB565 mode
+//#define BITEXACT
+
+namespace Image {
+
+namespace HNM6 {
+
+struct BitBuffer {
+ BitBuffer() :
+ _buffer(nullptr), _size(0), _pos(0), _reg(0), _bits(0) { }
+
+ inline void reset(byte *buffer, uint32 size);
+ FORCEINLINE byte next();
+
+private:
+ inline void loadReg();
+
+ byte *_buffer;
+ uint32 _size;
+ uint32 _pos;
+
+ uint32 _reg;
+ uint32 _bits;
+};
+
+struct MotionBuffer {
+ MotionBuffer() : _buffer(nullptr), _size(0), _pos(0) { }
+
+ inline void reset(byte *buffer, uint32 size);
+ FORCEINLINE uint16 next();
+
+private:
+ byte *_buffer;
+ uint32 _size;
+ uint32 _pos;
+};
+
+struct ShortMotionBuffer {
+ ShortMotionBuffer() :
+ _buffer(nullptr), _size(0), _pos(0), _other(0) { }
+
+ inline void reset(byte *buffer, uint32 size);
+ FORCEINLINE uint16 next();
+
+private:
+ inline uint16 loadReg();
+
+ byte *_buffer;
+ uint32 _size;
+ uint32 _pos;
+
+ uint16 _other;
+};
+
+struct JPEGBuffer {
+ JPEGBuffer() :
+ _buffer(nullptr), _size(0), _pos(0), _other(0) { }
+
+ inline void reset(byte *buffer, uint32 size);
+ FORCEINLINE byte next();
+
+private:
+ inline byte loadReg();
+
+ byte *_buffer;
+ uint32 _size;
+ uint32 _pos;
+
+ byte _other;
+};
+
+void BitBuffer::reset(byte *buffer, uint32 size) {
+ _buffer = buffer;
+ _size = size;
+ _pos = 0;
+ //_reg = 0; // Useless
+ _bits = 0;
+}
+
+byte BitBuffer::next() {
+ if (!_bits) {
+ loadReg();
+ }
+ byte ret = _reg >> 31;
+ _reg <<= 1;
+ _bits--;
+ return ret;
+}
+
+void BitBuffer::loadReg() {
+ if (_bits) {
+ error("BUG: Loading register while still loaded");
+ }
+ if (_pos + 4 > _size) {
+ error("Can't feed bitbuffer register: not enough data");
+ }
+ _reg = READ_LE_UINT32(_buffer + _pos);
+ _pos += sizeof(uint32);
+ _bits = sizeof(uint32) * 8;
+}
+
+void MotionBuffer::reset(byte *buffer, uint32 size) {
+ _buffer = buffer;
+ _size = size;
+ _pos = 0;
+}
+
+uint16 MotionBuffer::next() {
+ if (_pos + 2 > _size) {
+ error("Can't get motion word: not enough data");
+ }
+ uint16 ret = READ_LE_UINT16(_buffer + _pos);
+ _pos += sizeof(uint16);
+ return ret;
+}
+
+void ShortMotionBuffer::reset(byte *buffer, uint32 size) {
+ _buffer = buffer;
+ _size = size;
+ _pos = 0;
+ _other = 0;
+}
+
+uint16 ShortMotionBuffer::next() {
+ if (_other) {
+ uint16 ret = _other & 0xfff;
+ _other = 0;
+ return ret;
+ }
+
+ return loadReg();
+}
+
+uint16 ShortMotionBuffer::loadReg() {
+ if (_other) {
+ error("BUG: Loading register while still loaded");
+ }
+ if (_pos + 2 > _size) {
+ error("Can't feed shortmo register: not enough data");
+ }
+ if (_pos + 3 > _size) {
+ // We are at the end: load the last 2 bytes for a last word
+ uint16 ret = READ_LE_UINT16(_buffer + _pos);
+ _pos += sizeof(uint16);
+ return ret & 0xfff;
+ }
+ uint32 fullword = READ_LE_UINT16(_buffer + _pos);
+ fullword |= *(_buffer + _pos + 2) << 16;
+ _pos += 3 * sizeof(byte);
+
+ // 0x8000 is a marker of validity
+ _other = ((fullword >> 12) & 0xfff) | 0x8000;
+ return fullword & 0xfff;
+}
+
+void JPEGBuffer::reset(byte *buffer, uint32 size) {
+ _buffer = buffer;
+ _size = size;
+ _pos = 0;
+ _other = 0;
+}
+
+byte JPEGBuffer::next() {
+ if (_other) {
+ byte ret = _other & 0xf;
+ _other = 0;
+ return ret;
+ }
+
+ return loadReg();
+}
+
+byte JPEGBuffer::loadReg() {
+ if (_other) {
+ error("BUG: Loading register while still loaded");
+ }
+ if (_pos > _size) {
+ error("Can't feed JPEG register: not enough data");
+ }
+
+ byte fullword = *(_buffer + _pos);
+ _pos++;
+
+ // 0x80 is a marker of validity
+ _other = (fullword >> 4) | 0x80;
+ return fullword & 0xf;
+}
+
+static void YUVtoRGB(Graphics::Surface ¤t, uint x, uint y, int32 *coeffs) {
+#define CR(x) ((16 * (x - 256) + 8) / 10)
+#define CB(x) ((x - 256) / 3)
+ Graphics::PixelFormat &format = current.format;
+ for (uint ry = 0; ry < 8; ry++) {
+ void *linePtr = current.getBasePtr(x, y + ry);
+ for (uint rx = 0; rx < 8; rx++, coeffs++) {
+ int32 cy = coeffs[0];
+ int32 cu = coeffs[64];
+ int32 cv = coeffs[128];
+
+ cy = cy >> 4;
+
+ int32 cr = CR((cv >> 4) + 256);
+ int32 cb = CB((cu >> 4) + 256);
+
+ byte r = CLIP<int32>(cy + 128 + cr, 0, 255);
+#ifdef BITEXACT
+ // In original code G is clipped a little more
+ byte g = CLIP<int32>(cy + 128 - (cr >> 1) - cb, 0, 251);
+#else
+ byte g = CLIP<int32>(cy + 128 - (cr >> 1) - cb, 0, 255);
+#endif
+ byte b = CLIP<int32>(cy + 128 + (cu >> 3), 0, 255);
+
+#ifdef BITEXACT
+ // Original shifts R and B by 3 and G by 2
+ r &= 0xf8;
+ g &= 0xfc;
+ b &= 0xf8;
+#endif
+
+ if (format.bytesPerPixel == 2) {
+ ((uint16 *)linePtr)[rx] = format.RGBToColor(r, g, b);
+ } else if (format.bytesPerPixel == 4) {
+ ((uint32 *)linePtr)[rx] = format.RGBToColor(r, g, b);
+ }
+ }
+ }
+#undef CR
+#undef CB
+}
+
+/**
+ * This is an optimized Arai, Agui & Nakajima implementation
+ * Only the rows and columns set with values are taken into account
+ * The order of the IDCT being equivalent, the mask is used to
+ * determine where to start
+ */
+#define AAN_STRIDE(inout, stride, inc, mask) do { \
+ int32 *x = inout; \
+ byte mask_ = mask; \
+ for(uint i = 0; i < 8 && mask_; \
+ i++, x += inc, mask_ >>= 1) { \
+ int32 x2n6 = x[2*stride] - x[6*stride]; \
+ int32 x2p6 = x[2*stride] + x[6*stride]; \
+ int32 x0n4 = x[0*stride] - x[4*stride]; \
+ int32 x0p4 = x[0*stride] + x[4*stride]; \
+ \
+ int32 x5n3 = x[5*stride] - x[3*stride]; \
+ int32 x3p5 = x[3*stride] + x[5*stride]; \
+ int32 x1n7 = x[1*stride] - x[7*stride]; \
+ int32 x1p7 = x[1*stride] + x[7*stride]; \
+ \
+ int32 tmp0 = ((3* x2n6) >> 1) - x2p6; \
+ int32 tmp1 = (3 * (x1p7 - x3p5)) >> 1; \
+ int32 tmp2 = 30 * (x5n3 + x1n7); \
+ int32 tmp3 = (17 * x1n7 - tmp2) >> 4; \
+ int32 tmp4 = (tmp2 - 40 * x5n3) >> 4; \
+ int32 tmp5 = tmp4 - x3p5 - x1p7; \
+ \
+ x[0*stride] = x1p7 + x3p5 + x0p4 + x2p6; \
+ x[7*stride] = x0p4 + x2p6 - x1p7 - x3p5; \
+ x[1*stride] = tmp0 + x0n4 + tmp4 - x3p5 - x1p7; \
+ x[6*stride] = tmp0 + x0n4 - tmp4 + x3p5 + x1p7; \
+ x[2*stride] = x0n4 - tmp0 + tmp1 - tmp5; \
+ x[5*stride] = x0n4 - tmp0 - tmp1 + tmp5; \
+ x[3*stride] = x0p4 - x2p6 - tmp1 + tmp5 - tmp3; \
+ x[4*stride] = x0p4 - x2p6 + tmp1 - tmp5 + tmp3; \
+ } \
+} while(0)
+
+static void aanidct(int32 *inout, uint16 mask) {
+ if (mask == 0x0101) {
+ // Simple case: 1 coefficient in the top left corner: it means constant
+ for (uint i = 1; i < 64; i++) {
+ inout[i] = inout[0];
+ }
+ return;
+ }
+
+#ifdef BITEXACT
+ // Original decoder has a bug where the masks are compared
+ // with a sign compare. This means that 0x80 < 0x1.
+ // The AAN order is then not optimal.
+ int8 rowmask, colmask;
+#else
+ uint8 rowmask, colmask;
+#endif
+ rowmask = mask >> 8;
+ colmask = mask & 0xff;
+
+ if (rowmask < colmask) {
+ // Inside row
+ AAN_STRIDE(inout, 1, 8, rowmask);
+ if (rowmask == 0x01) {
+ // Simple case: Only one line filled, copy to others
+ for (uint i = 1; i < 8; i++) {
+ memcpy(inout + 8 * i, inout, sizeof(*inout) * 8);
+ }
+ } else {
+ AAN_STRIDE(inout, 8, 1, 0xff);
+ }
+ } else {
+ // Inside column
+ AAN_STRIDE(inout, 8, 1, colmask);
+ if (colmask == 0x01) {
+ // Simple case: Only one column filled, copy to others
+ for (uint i = 0; i < 8; i++) {
+ // NlogN memset
+ inout[8 * i + 1] = inout[8 * i + 0];
+ memcpy(inout + 8 * i + 2, inout + 8 * i, sizeof(*inout) * 2);
+ memcpy(inout + 8 * i + 4, inout + 8 * i, sizeof(*inout) * 4);
+ }
+ } else {
+ AAN_STRIDE(inout, 1, 8, 0xff);
+ }
+ }
+}
+#undef AAN_STRIDE
+
+template<int sx, int sy, typename PixelType>
+static void doMotion(byte xform, Graphics::Surface &dst, const Graphics::Surface &src,
+ int srx, int sry, int dx, int dy) {
+ int bpp = sizeof(PixelType);
+ int stride = dst.pitch;
+ const byte *srcP;
+ byte *dstP;
+#define COPY_PIXEL() *((PixelType *)dstP) = *((const PixelType *)srcP)
+
+ switch (xform) {
+ case 0: // Copy
+ srcP = (const byte *)src.getBasePtr(srx, sry);
+ dstP = (byte *)dst.getBasePtr(dx, dy);
+ for (int y = 0; y < sy; y++) {
+ memmove(dstP, srcP, bpp * sx);
+ dstP += stride;
+ srcP += stride;
+ }
+ break;
+ case 1: // Horizontal Flip
+ srcP = (const byte *)src.getBasePtr(srx + sx - 1, sry);
+ dstP = (byte *)dst.getBasePtr(dx, dy);
+ for (int y = 0; y < sy; y++) {
+ for (int x = 0; x < sx; x++) {
+ COPY_PIXEL();
+ dstP += bpp;
+ srcP -= bpp;
+ }
+ dstP += stride - sx * bpp;
+ srcP += stride + sx * bpp;
+ }
+ break;
+ case 2: // Vertical Flip
+ srcP = (const byte *)src.getBasePtr(srx, sry + sy - 1);
+ dstP = (byte *)dst.getBasePtr(dx, dy);
+ for (int y = 0; y < sy; y++) {
+ memmove(dstP, srcP, bpp * sx);
+ dstP += stride;
+ srcP -= stride;
+ }
+ break;
+ case 3: // Cross Flip
+ srcP = (const byte *)src.getBasePtr(srx + sx - 1, sry + sy - 1);
+ dstP = (byte *)dst.getBasePtr(dx, dy);
+ for (int y = 0; y < sy; y++) {
+ for (int x = 0; x < sx; x++) {
+ COPY_PIXEL();
+ dstP += bpp;
+ srcP -= bpp;
+ }
+ dstP += stride - sx * bpp;
+ srcP -= stride - sx * bpp;
+ }
+ break;
+ case 4: // Forward Flip
+ srcP = (const byte *)src.getBasePtr(srx + sy - 1, sry + sx - 1);
+ dstP = (byte *)dst.getBasePtr(dx, dy);
+ for (int y = 0; y < sy; y++) {
+ for (int x = 0; x < sx; x++) {
+ COPY_PIXEL();
+ dstP += bpp;
+ srcP -= stride;
+ }
+ dstP += stride - sx * bpp;
+ srcP += sx * stride - bpp;
+ }
+ break;
+ case 5: // Forward Rotate
+ srcP = (const byte *)src.getBasePtr(srx, sry + sx - 1);
+ dstP = (byte *)dst.getBasePtr(dx, dy);
+ for (int y = 0; y < sy; y++) {
+ for (int x = 0; x < sx; x++) {
+ COPY_PIXEL();
+ dstP += bpp;
+ srcP -= stride;
+ }
+ dstP += stride - sx * bpp;
+ srcP += sx * stride + bpp;
+ }
+ break;
+ case 6: // Backward Rotate
+ srcP = (const byte *)src.getBasePtr(srx + sy - 1, sry);
+ dstP = (byte *)dst.getBasePtr(dx, dy);
+ for (int y = 0; y < sy; y++) {
+ for (int x = 0; x < sx; x++) {
+ COPY_PIXEL();
+ dstP += bpp;
+ srcP += stride;
+ }
+ dstP += stride - sx * bpp;
+ srcP -= sx * stride + bpp;
+ }
+ break;
+ case 7: // Backward Flip
+ srcP = (const byte *)src.getBasePtr(srx, sry);
+ dstP = (byte *)dst.getBasePtr(dx, dy);
+ for (int y = 0; y < sy; y++) {
+ for (int x = 0; x < sx; x++) {
+ COPY_PIXEL();
+ dstP += bpp;
+ srcP += stride;
+ }
+ dstP += stride - sx * bpp;
+ srcP -= sx * stride - bpp;
+ }
+ break;
+ default:
+ error("BUG: Invalid motion transform");
+ }
+#undef COPY_PIXEL
+}
+
+template<int sx, int sy>
+FORCEINLINE static void doMotion(byte xform, Graphics::Surface &dst, const Graphics::Surface &src,
+ int srx, int sry, int dx, int dy) {
+ if (dst.format.bytesPerPixel == 2) {
+ doMotion<sx, sy, uint16>(xform, dst, src, srx, sry, dx, dy);
+ } else if (dst.format.bytesPerPixel == 4) {
+ doMotion<sx, sy, uint32>(xform, dst, src, srx, sry, dx, dy);
+ }
+}
+
+/* Classic JPEG quantization tables */
+static const int32 LUMA_QUANT_TABLE[] = {
+ 16, 11, 10, 16, 24, 40, 51, 61,
+ 12, 12, 14, 19, 26, 58, 60, 55,
+ 14, 13, 16, 24, 40, 57, 69, 56,
+ 14, 17, 22, 29, 51, 87, 80, 62,
+ 18, 22, 37, 56, 68, 109, 103, 77,
+ 24, 35, 55, 64, 81, 104, 113, 92,
+ 49, 64, 78, 87, 103, 121, 120, 101,
+ 72, 92, 95, 98, 112, 100, 103, 99
+};
+
+static const int32 CHROMA_QUANT_TABLE[] = {
+ 17, 18, 24, 47, 99, 99, 99, 99,
+ 18, 21, 26, 66, 99, 99, 99, 99,
+ 24, 26, 56, 99, 99, 99, 99, 99,
+ 47, 66, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99
+};
+
+/* Factors for the Arai, Agui & Nakajima IDCT */
+static const int32 AAN_FACTORS[] = {
+ 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520,
+ 22725, 31521, 29692, 26722, 22725, 17855, 12299, 6270,
+ 21407, 29692, 27969, 25172, 21407, 16819, 11585, 5906,
+ 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315,
+ 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520,
+ 12873, 17855, 16819, 15137, 12873, 10114, 6967, 3552,
+ 8867, 12299, 11585, 10426, 8867, 6967, 4799, 2446,
+ 4520, 6270, 5906, 5315, 4520, 3552, 2446, 1247
+};
+
+/* Classic JPEG zig-zag encoding table */
+static const byte ZIGZAG[] = {
+ 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
+};
+
+/* These masks are used to tell which rows (high order) and columns (low order) have values.
+ * This allow to skip some operations in IDCT. */
+static const uint16 MASKS[] = {
+ 0x0101, 0x0102, 0x0201, 0x0401, 0x0202, 0x0104, 0x0108, 0x0204,
+ 0x0402, 0x0801, 0x1001, 0x0802, 0x0404, 0x0208, 0x0110, 0x0120,
+ 0x0210, 0x0408, 0x0804, 0x1002, 0x2001, 0x4001, 0x2002, 0x1004,
+ 0x0808, 0x0410, 0x0220, 0x0140, 0x0180, 0x0240, 0x0420, 0x0810,
+ 0x1008, 0x2004, 0x4002, 0x8001, 0x8002, 0x4004, 0x2008, 0x1010,
+ 0x0820, 0x0440, 0x0280, 0x0480, 0x0840, 0x1020, 0x2010, 0x4008,
+ 0x8004, 0x8008, 0x4010, 0x2020, 0x1040, 0x0880, 0x1080, 0x2040,
+ 0x4020, 0x8010, 0x8020, 0x4040, 0x2080, 0x4080, 0x8040, 0x8080
+};
+
+static const int8 S2[] = {
+ 1, 1, 1, 2, 1, -2, 1, -1,
+ 2, 1, 2, 2, 2, -2, 2, -1,
+ -2, 1, -2, 2, -2, -2, -2, -1,
+ -1, 1, -1, 2, -1, -2, -1, -1
+};
+
+static const int8 S4[] = {
+ 1, 2, 3, 4, 5, 6, 7, 8,
+ -8, -7, -6, -5, -4, -3, -2, -1
+};
+
+STATIC_ASSERT(ARRAYSIZE(LUMA_QUANT_TABLE) == 64, "invalid luma table size");
+STATIC_ASSERT(ARRAYSIZE(CHROMA_QUANT_TABLE) == 64, "invalid chromaa table size");
+STATIC_ASSERT(ARRAYSIZE(AAN_FACTORS) == 64, "invalid AAN factors table size");
+STATIC_ASSERT(ARRAYSIZE(ZIGZAG) == 64, "invalid zigzag table size");
+STATIC_ASSERT(ARRAYSIZE(S2) == 16 * 2, "invalid S2 table size");
+STATIC_ASSERT(ARRAYSIZE(S4) == 16, "invalid S4 table size");
+
+static const int HEADER_SIZE = 6 * sizeof(uint32);
+
+/**
+ * HNM6 image decoder interface.
+ *
+ * Used by HNM6 image and video format.
+ */
+class DecoderImpl : public HNM6Decoder {
+public:
+ DecoderImpl(uint16 width, uint16 height, const Graphics::PixelFormat &format,
+ uint32 bufferSize, bool videoMode = false);
+ ~DecoderImpl() override;
+
+ const Graphics::Surface *decodeFrame(Common::SeekableReadStream &stream) override;
+
+#ifdef HNM_DEBUG
+ uint32 _frameNr;
+#endif
+
+private:
+ uint32 _bufferSize;
+ byte *_buffer;
+ Graphics::Surface _surface;
+ Graphics::Surface _surfaceOld;
+
+ bool keyframe;
+ HNM6::BitBuffer _bitbuf;
+ HNM6::MotionBuffer _motion;
+ HNM6::ShortMotionBuffer _shortmo;
+ HNM6::JPEGBuffer _jpeg;
+
+ int32 coeffs[3 * 64];
+ int32 luma_quant_table[64];
+ int32 chroma_quant_table[64];
+
+ // Simple functions and called at one place only
+ FORCEINLINE void reset(byte *buffer, uint32 bit_start, uint32 motion_start,
+ uint32 shortmotion_start, uint32 jpeg_start, uint32 end,
+ int32 quality);
+ // Just a wrapper
+ FORCEINLINE void decode(Graphics::Surface ¤t, Graphics::Surface &old);
+ // Just a loop wrapper
+ FORCEINLINE void decodeIWkf(Graphics::Surface ¤t);
+ void decodeIWkf88(Graphics::Surface ¤t, uint x, uint y);
+ void decodeIWkf44(Graphics::Surface ¤t, uint x, uint y);
+ // Just a loop wrapper
+ FORCEINLINE void decodeIXkf(Graphics::Surface ¤t);
+ inline void decodeIXkf88(Graphics::Surface ¤t, uint x, uint y);
+ // Simple functions and called at one place only
+ FORCEINLINE void decodeIXkf48(Graphics::Surface ¤t, uint x, uint y);
+ FORCEINLINE void decodeIXkf84(Graphics::Surface ¤t, uint x, uint y);
+ void decodeIXkf44(Graphics::Surface ¤t, uint x, uint y);
+ // Simple functions and called at one place only
+ FORCEINLINE void decodeIXkf24(Graphics::Surface ¤t, uint x, uint y);
+ FORCEINLINE void decodeIXkf42(Graphics::Surface ¤t, uint x, uint y);
+ // Just a loop wrapper
+ FORCEINLINE void decodeIXif(Graphics::Surface ¤t, Graphics::Surface &previous);
+ void decodeIXif88(Graphics::Surface ¤t, Graphics::Surface &previous, uint x, uint y);
+ // Simple functions and called at one place only
+ FORCEINLINE void decodeIXif48(Graphics::Surface ¤t, Graphics::Surface &previous,
+ uint x, uint y);
+ FORCEINLINE void decodeIXif84(Graphics::Surface ¤t, Graphics::Surface &previous,
+ uint x, uint y);
+ void decodeIXif44(Graphics::Surface ¤t, Graphics::Surface &previous, uint x, uint y);
+ // Simple functions and called at one place only
+ FORCEINLINE void decodeIXif24(Graphics::Surface ¤t, Graphics::Surface &previous,
+ uint x, uint y);
+ FORCEINLINE void decodeIXif42(Graphics::Surface ¤t, Graphics::Surface &previous,
+ uint x, uint y);
+ void decodeIXif22(Graphics::Surface ¤t, Graphics::Surface &previous, uint x, uint y);
+ // Simple function and called at 3 places only
+ FORCEINLINE void renderKeyblock(Graphics::Surface ¤t, uint x, uint y);
+ void renderPlane(int32 *dst, int32 *quant_table);
+ // Time-critical parsing functions
+ template<int sx, int sy>
+ FORCEINLINE void renderIWmotion(Graphics::Surface ¤t, uint x, uint y);
+ FORCEINLINE void renderIWshortmo(Graphics::Surface ¤t, uint x, uint y);
+ template<int sx, int sy, bool small = false>
+ FORCEINLINE void renderIXkfMotion(Graphics::Surface ¤t, uint x, uint y);
+ template<int sx, int sy>
+ FORCEINLINE void renderSkip(Graphics::Surface ¤t, Graphics::Surface &previous,
+ uint x, uint y);
+ template<int sx, int sy, bool small = false>
+ FORCEINLINE void renderIXifMotion(Graphics::Surface ¤t, Graphics::Surface &previous,
+ uint x, uint y);
+ template<int sx, int sy>
+ FORCEINLINE void renderIXifShortmo(Graphics::Surface ¤t, Graphics::Surface &previous,
+ uint x, uint y);
+
+};
+
+void DecoderImpl::reset(byte *buffer, uint32 bit_start, uint32 motion_start,
+ uint32 shortmotion_start, uint32 jpeg_start, uint32 end,
+ int32 quality) {
+ _bitbuf.reset(buffer + bit_start, motion_start - bit_start);
+ _motion.reset(buffer + motion_start, shortmotion_start - motion_start);
+ _shortmo.reset(buffer + shortmotion_start, jpeg_start - shortmotion_start);
+ _jpeg.reset(buffer + jpeg_start, end - jpeg_start);
+
+ assert(!_warpMode || quality > 0);
+ keyframe = quality < 0;
+
+ quality = ABS(quality);
+ quality = CLIP<int32>(quality, 1, 100);
+
+ uint32 qf;
+ if (quality >= 50) {
+ qf = 200 - 2 * quality;
+ } else {
+ qf = 5000 / quality;
+ }
+
+ for (uint i = 0; i < 64; i++) {
+ luma_quant_table[i] = (CLIP<int32>(
+ (LUMA_QUANT_TABLE[ZIGZAG[i]] * qf + 50) / 100,
+ 8, 255) *
+ AAN_FACTORS[ZIGZAG[i]]) >> 13;
+ }
+ for (uint i = 0; i < 64; i++) {
+ chroma_quant_table[i] = (CLIP<int32>(
+ (CHROMA_QUANT_TABLE[ZIGZAG[i]] * qf + 50) / 100,
+ 8, 255) *
+ AAN_FACTORS[ZIGZAG[i]]) >> 13;
+ }
+}
+
+void DecoderImpl::decode(Graphics::Surface ¤t, Graphics::Surface &old) {
+ assert((current.w & 0x7) == 0 && (current.h & 0x7) == 0);
+ if (_warpMode) {
+ decodeIWkf(current);
+ } else if (keyframe) {
+ decodeIXkf(current);
+ } else {
+ assert((old.w & 0x7) == 0 && (old.h & 0x7) == 0);
+ assert(old.getPixels() != nullptr);
+ decodeIXif(current, old);
+ }
+}
+
+void DecoderImpl::decodeIWkf(Graphics::Surface ¤t) {
+ // WARP decoder
+ for (int16 y = 0; y < current.h; y += 8) {
+ for (int16 x = 0; x < current.w; x += 8) {
+ decodeIWkf88(current, x, y);
+ }
+ }
+}
+
+void DecoderImpl::decodeIWkf88(Graphics::Surface ¤t, uint x, uint y) {
+ byte bit = _bitbuf.next();
+ if (bit) { // 1
+ bit = _bitbuf.next();
+ if (bit) { // 11 : Keyblock
+ renderKeyblock(current, x, y);
+ } else { // 10 : Motion
+ renderIWmotion<8, 8>(current, x, y);
+ }
+ } else { // 0 : Cross-cut
+ decodeIWkf44(current, x + 0, y + 0);
+ decodeIWkf44(current, x + 4, y + 0);
+ decodeIWkf44(current, x + 0, y + 4);
+ decodeIWkf44(current, x + 4, y + 4);
+ }
+}
+
+void DecoderImpl::decodeIWkf44(Graphics::Surface ¤t, uint x, uint y) {
+ byte bit = _bitbuf.next();
+ if (bit) { // 1 : Double cross-cut
+ renderIWshortmo(current, x + 0, y + 0);
+ renderIWshortmo(current, x + 2, y + 0);
+ renderIWshortmo(current, x + 0, y + 2);
+ renderIWshortmo(current, x + 2, y + 2);
+ } else { // 0 : Motion
+ renderIWmotion<4, 4>(current, x, y);
+ }
+}
+
+void DecoderImpl::decodeIXkf(Graphics::Surface ¤t) {
+ // Standard decoder for keyframes
+ for (int16 y = 0; y < current.h; y += 8) {
+ for (int16 x = 0; x < current.w; x += 8) {
+ decodeIXkf88(current, x, y);
+ }
+ }
+}
+
+void DecoderImpl::decodeIXkf88(Graphics::Surface ¤t, uint x, uint y) {
+ byte bit = _bitbuf.next();
+ if (bit) { // 1
+ bit = _bitbuf.next();
+ if (bit) { // 11
+ bit = _bitbuf.next();
+ if (bit) { // 111: Motion
+ renderIXkfMotion<8, 8>(current, x, y);
+ } else { // 110: Keyblock
+ renderKeyblock(current, x, y);
+ }
+ } else { // 10: Cross-cut
+ decodeIXkf44(current, x + 0, y + 0);
+ decodeIXkf44(current, x + 4, y + 0);
+ decodeIXkf44(current, x + 0, y + 4);
+ decodeIXkf44(current, x + 4, y + 4);
+ }
+ } else { // 0
+ bit = _bitbuf.next();
+ if (bit) { // 01: V-cut
+ decodeIXkf48(current, x + 0, y + 0);
+ decodeIXkf48(current, x + 4, y + 0);
+ } else { // 00: H-cut
+ decodeIXkf84(current, x + 0, y + 0);
+ decodeIXkf84(current, x + 0, y + 4);
+ }
+ }
+}
+
+void DecoderImpl::decodeIXkf48(Graphics::Surface ¤t, uint x, uint y) {
+ byte bit = _bitbuf.next();
+ if (bit) { // 1: Motion
+ renderIXkfMotion<4, 8>(current, x, y);
+ } else { // 0: Cross-cut
+ decodeIXkf44(current, x + 0, y + 0);
+ decodeIXkf44(current, x + 0, y + 4);
+ }
+}
+
+void DecoderImpl::decodeIXkf84(Graphics::Surface ¤t, uint x, uint y) {
+ byte bit = _bitbuf.next();
+ if (bit) { // 1: Motion
+ renderIXkfMotion<8, 4>(current, x, y);
+ } else { // 0: Cross-cut
+ decodeIXkf44(current, x + 0, y + 0);
+ decodeIXkf44(current, x + 4, y + 0);
+ }
+}
+
+void DecoderImpl::decodeIXkf44(Graphics::Surface ¤t, uint x, uint y) {
+ byte bit = _bitbuf.next();
+ if (bit) { // 1
+ bit = _bitbuf.next();
+ if (bit) { // 11: Double cross-cut
+ // 2x2 blocks are always motion small
+ renderIXkfMotion<2, 2, true>(current, x + 0, y + 0);
+ renderIXkfMotion<2, 2, true>(current, x + 2, y + 0);
+ renderIXkfMotion<2, 2, true>(current, x + 0, y + 2);
+ renderIXkfMotion<2, 2, true>(current, x + 2, y + 2);
+ } else { // 10: DV-cut
+ decodeIXkf24(current, x + 0, y + 0);
+ decodeIXkf24(current, x + 2, y + 0);
+ }
+ } else { // 0
+ bit = _bitbuf.next();
+ if (bit) { // 01: DH-cut
+ decodeIXkf42(current, x + 0, y + 0);
+ decodeIXkf42(current, x + 0, y + 2);
+ } else { // 00: Motion
+ renderIXkfMotion<4, 4>(current, x, y);
+ }
+ }
+}
+
+void DecoderImpl::decodeIXkf24(Graphics::Surface ¤t, uint x, uint y) {
+ byte bit = _bitbuf.next();
+ if (bit) { // 1: Double cross-cut
+ // 2x2 blocks are always motion small
+ renderIXkfMotion<2, 2, true>(current, x + 0, y + 0);
+ renderIXkfMotion<2, 2, true>(current, x + 0, y + 2);
+ } else { // 0: Motion small
+ renderIXkfMotion<2, 4, true>(current, x, y);
+ }
+}
+
+void DecoderImpl::decodeIXkf42(Graphics::Surface ¤t, uint x, uint y) {
+ byte bit = _bitbuf.next();
+ if (bit) { // 1: Double cross-cut
+ // 2x2 blocks are always motion small
+ renderIXkfMotion<2, 2, true>(current, x + 0, y + 0);
+ renderIXkfMotion<2, 2, true>(current, x + 2, y + 0);
+ } else { // 0: Motion small
+ renderIXkfMotion<4, 2, true>(current, x, y);
+ }
+}
+
+void DecoderImpl::decodeIXif(Graphics::Surface ¤t, Graphics::Surface &previous) {
+ // Standard decoder for keyframes
+ for (int16 y = 0; y < current.h; y += 8) {
+ for (int16 x = 0; x < current.w; x += 8) {
+ decodeIXif88(current, previous, x, y);
+ }
+ }
+}
+
+void DecoderImpl::decodeIXif88(Graphics::Surface ¤t, Graphics::Surface &previous,
+ uint x, uint y) {
+ byte bit = _bitbuf.next();
+ if (bit) { // 1
+ bit = _bitbuf.next();
+ if (bit) { // 11
+ bit = _bitbuf.next();
+ if (bit) { // 111
+ bit = _bitbuf.next();
+ if (bit) { // 1111: Unknown
+ assert(false);
+ } else { // 1110: Cross-cut
+ decodeIXif44(current, previous, x + 0, y + 0);
+ decodeIXif44(current, previous, x + 4, y + 0);
+ decodeIXif44(current, previous, x + 0, y + 4);
+ decodeIXif44(current, previous, x + 4, y + 4);
+ }
+ } else { // 110: Skip
+ renderSkip<8, 8>(current, previous, x, y);
+ }
+ } else { // 10
+ bit = _bitbuf.next();
+ if (bit) { // 101: V-cut
+ decodeIXif48(current, previous, x + 0, y + 0);
+ decodeIXif48(current, previous, x + 4, y + 0);
+ } else { // 100: H-cut
+ decodeIXif84(current, previous, x + 0, y + 0);
+ decodeIXif84(current, previous, x + 0, y + 4);
+ }
+ }
+ } else { // 0
+ bit = _bitbuf.next();
+ if (bit) { // 01
+ bit = _bitbuf.next();
+ if (bit) { // 011: Keyblock
+ renderKeyblock(current, x, y);
+ } else { // 010: Motion
+ renderIXifMotion<8, 8>(current, previous, x, y);
+ }
+ } else { // 00: Short motion
+ renderIXifShortmo<8, 8>(current, previous, x, y);
+ }
+ }
+}
+
+void DecoderImpl::decodeIXif48(Graphics::Surface ¤t, Graphics::Surface &previous,
+ uint x, uint y) {
+ byte bit = _bitbuf.next();
+ if (bit) { // 1
+ bit = _bitbuf.next();
+ if (bit) { // 11: Skip
+ renderSkip<4, 8>(current, previous, x, y);
+ } else { // 10: Cross-cut
+ decodeIXif44(current, previous, x + 0, y + 0);
+ decodeIXif44(current, previous, x + 0, y + 4);
+ }
+ } else { // 0
+ bit = _bitbuf.next();
+ if (bit) { // 01: Motion
+ renderIXifMotion<4, 8>(current, previous, x, y);
+ } else { // 00: Short motion
+ renderIXifShortmo<4, 8>(current, previous, x, y);
+ }
+ }
+}
+
+void DecoderImpl::decodeIXif84(Graphics::Surface ¤t, Graphics::Surface &previous,
+ uint x, uint y) {
+ byte bit = _bitbuf.next();
+ if (bit) { // 1
+ bit = _bitbuf.next();
+ if (bit) { // 11: Skip
+ renderSkip<8, 4>(current, previous, x, y);
+ } else { // 10: Cross-cut
+ decodeIXif44(current, previous, x + 0, y + 0);
+ decodeIXif44(current, previous, x + 4, y + 0);
+ }
+ } else { // 0
+ bit = _bitbuf.next();
+ if (bit) { // 01: Motion
+ renderIXifMotion<8, 4>(current, previous, x, y);
+ } else { // 00: Short motion
+ renderIXifShortmo<8, 4>(current, previous, x, y);
+ }
+ }
+}
+
+void DecoderImpl::decodeIXif44(Graphics::Surface ¤t, Graphics::Surface &previous,
+ uint x, uint y) {
+ byte bit = _bitbuf.next();
+ if (bit) { // 1
+ bit = _bitbuf.next();
+ if (bit) { // 11
+ bit = _bitbuf.next();
+ if (bit) { // 111: Double cross-cut
+ decodeIXif22(current, previous, x + 0, y + 0);
+ decodeIXif22(current, previous, x + 2, y + 0);
+ decodeIXif22(current, previous, x + 0, y + 2);
+ decodeIXif22(current, previous, x + 2, y + 2);
+ } else { // 110: DV-cut
+ decodeIXif24(current, previous, x + 0, y + 0);
+ decodeIXif24(current, previous, x + 2, y + 0);
+ }
+ } else { // 10
+ bit = _bitbuf.next();
+ if (bit) { // 101: DH-cut
+ decodeIXif42(current, previous, x + 0, y + 0);
+ decodeIXif42(current, previous, x + 0, y + 2);
+ } else { // 100: Skip
+ renderSkip<4, 4>(current, previous, x, y);
+ }
+ }
+ } else { // 0
+ bit = _bitbuf.next();
+ if (bit) { // 01: Motion
+ renderIXifMotion<4, 4>(current, previous, x, y);
+ } else { // 00: Short motion
+ renderIXifShortmo<4, 4>(current, previous, x, y);
+ }
+ }
+}
+
+void DecoderImpl::decodeIXif24(Graphics::Surface ¤t, Graphics::Surface &previous,
+ uint x, uint y) {
+ byte bit = _bitbuf.next();
+ if (bit) { // 1
+ bit = _bitbuf.next();
+ if (bit) { // 11
+ bit = _bitbuf.next();
+ if (bit) { // 111: Double cross-cut
+ decodeIXif22(current, previous, x + 0, y + 0);
+ decodeIXif22(current, previous, x + 0, y + 2);
+ } else { // 110: Skip
+ renderSkip<2, 4>(current, previous, x, y);
+ }
+ } else { // 10: Motion small
+ renderIXifMotion<2, 4, true>(current, previous, x, y);
+ }
+ } else { // 0: Short motion
+ renderIXifShortmo<2, 4>(current, previous, x, y);
+ }
+}
+
+void DecoderImpl::decodeIXif42(Graphics::Surface ¤t, Graphics::Surface &previous,
+ uint x, uint y) {
+ byte bit = _bitbuf.next();
+ if (bit) { // 1
+ bit = _bitbuf.next();
+ if (bit) { // 11
+ bit = _bitbuf.next();
+ if (bit) { // 111: Double cross-cut
+ decodeIXif22(current, previous, x + 0, y + 0);
+ decodeIXif22(current, previous, x + 2, y + 0);
+ } else { // 110: Skip
+ renderSkip<4, 2>(current, previous, x, y);
+ }
+ } else { // 10: Motion small
+ renderIXifMotion<4, 2, true>(current, previous, x, y);
+ }
+ } else { // 0: Short motion
+ renderIXifShortmo<4, 2>(current, previous, x, y);
+ }
+}
+
+void DecoderImpl::decodeIXif22(Graphics::Surface ¤t, Graphics::Surface &previous,
+ uint x, uint y) {
+ byte bit = _bitbuf.next();
+ if (bit) { // 1
+ bit = _bitbuf.next();
+ if (bit) { // 11: Skip
+ renderSkip<2, 2>(current, previous, x, y);
+ } else { // 10: Motion small
+ renderIXifMotion<2, 2, true>(current, previous, x, y);
+ }
+ } else { // 0: Short motion
+ renderIXifShortmo<2, 2>(current, previous, x, y);
+ }
+}
+
+void DecoderImpl::renderKeyblock(Graphics::Surface ¤t, uint x, uint y) {
+ renderPlane(coeffs, luma_quant_table);
+ renderPlane(coeffs + 64, chroma_quant_table);
+ renderPlane(coeffs + 128, chroma_quant_table);
+ HNM6::YUVtoRGB(current, x, y, coeffs);
+}
+
+void DecoderImpl::renderPlane(int32 *dst, int32 *quant_table) {
+ uint i = 0;
+ // This mask is used to optimize the IDCT
+ uint16 colrow_mask = 0;
+ int8 b;
+ memset(dst, 0, 64 * sizeof(*dst));
+
+#define WRITE_COEFF_0(y) do { i += y; } while(0)
+#define WRITE_COEFF(x) do { \
+ dst[ZIGZAG[i]] = (x) * quant_table[i]; \
+ colrow_mask |= MASKS[i]; \
+ i++; \
+} while(0)
+
+ b = _jpeg.next() << 4;
+ b |= _jpeg.next();
+ WRITE_COEFF(b);
+ while (i < 64) {
+ b = _jpeg.next();
+ switch (b) {
+ case 0: // Finish with 0
+ i = 64;
+ break;
+ case 1:
+ WRITE_COEFF_0(5);
+ break;
+ case 2:
+ WRITE_COEFF_0(1);
+ WRITE_COEFF(1);
+ break;
+ case 3:
+ WRITE_COEFF_0(1);
+ WRITE_COEFF(-1);
+ break;
+ case 4:
+ WRITE_COEFF_0(2);
+ WRITE_COEFF(1);
+ break;
+ case 5:
+ WRITE_COEFF_0(2);
+ WRITE_COEFF(-1);
+ break;
+ case 6:
+ WRITE_COEFF_0(3);
+ WRITE_COEFF(1);
+ break;
+ case 7:
+ WRITE_COEFF_0(3);
+ WRITE_COEFF(-1);
+ break;
+ case 8: {
+ b = _jpeg.next();
+ byte z = (b >> 2) & 0x3;
+ byte t = b & 0x3;
+ z++;
+ t += 2;
+ WRITE_COEFF_0(z);
+ for (; t > 1; t -= 2) {
+ b = _jpeg.next();
+ const int8 *p = S2 + 2 * b;
+ WRITE_COEFF(p[0]);
+ WRITE_COEFF(p[1]);
+ }
+ if (t) {
+ b = _jpeg.next();
+ const int8 *p = S2 + 2 * b;
+ WRITE_COEFF(p[0]);
+ t--;
+ }
+ break;
+ }
+ case 9: {
+ b = _jpeg.next();
+ byte z = (b >> 2) & 0x3;
+ byte t = b & 0x3;
+ z++;
+ t++;
+ WRITE_COEFF_0(z);
+ for (; t > 0; t--) {
+ b = _jpeg.next();
+ WRITE_COEFF(S4[b]);
+ }
+ break;
+ }
+ case 10:
+ b = _jpeg.next();
+ WRITE_COEFF(S2[2 * b]);
+ WRITE_COEFF(S2[2 * b + 1]);
+ break;
+ case 13:
+ b = _jpeg.next();
+ WRITE_COEFF(S4[b]);
+ // fall through
+ case 12:
+ b = _jpeg.next();
+ WRITE_COEFF(S4[b]);
+ // fall through
+ case 11:
+ b = _jpeg.next();
+ WRITE_COEFF(S4[b]);
+ break;
+ case 14:
+ WRITE_COEFF_0(1);
+ break;
+ case 15:
+ b = _jpeg.next() << 4;
+ b |= _jpeg.next();
+ WRITE_COEFF(b);
+ break;
+ default:
+ error("BUG: JPEG control word out of range");
+ break;
+ }
+ }
+#undef WRITE_COEFF_0
+#undef WRITE_COEFF
+
+ HNM6::aanidct(dst, colrow_mask);
+}
+
+// These macros are used in motion rendering
+// Wrap the x coordinate around the line
+#define WRAP_LINE(x, w) do { \
+ if (x < 0) { \
+ x += w; \
+ } else if (x >= w) { \
+ x -= w; \
+ } \
+} while(0)
+// Convert a coordinate to its block (8x8) one
+#define BALIGN(v) ((v) & ~0x7)
+
+template<int sx, int sy>
+void DecoderImpl::renderIWmotion(Graphics::Surface ¤t, uint x, uint y) {
+ uint16 moword = _motion.next();
+ byte xform = _bitbuf.next() << 2;
+ xform |= _bitbuf.next() << 1;
+ xform |= moword >> 15;
+
+ int offy = (sx == 8) ? 0 : 4;
+ int srx = BALIGN(x) + 128 - ((moword >> 7) & 0xFF);
+ int sry = BALIGN(y) + offy - (moword & 0x7F);
+ WRAP_LINE(srx, current.w);
+ HNM6::doMotion<sx, sy>(xform, current, current, srx, sry, x, y);
+}
+
+void DecoderImpl::renderIWshortmo(Graphics::Surface ¤t, uint x, uint y) {
+ uint16 moword = _shortmo.next();
+ byte xform = (moword >> 1) & 7;
+
+ uint16 index = (moword & 0x1) << 8 | (moword & 0xF0) | (moword >> 8);
+
+ int xmotion, ymotion;
+ if (index < 12 * 8) {
+ xmotion = -2 - index % 12;
+ ymotion = 6 - index / 12;
+ } else {
+ index -= 12 * 8;
+ xmotion = 19 - index % 32;
+ ymotion = -2 - index / 32;
+ if (ymotion <= -8) {
+ ymotion--;
+ }
+ }
+
+ // If srx goes beyond the line it will end up on previous or next line
+ // This is expected.
+ // For this to work, lines must be contiguous.
+ int srx = BALIGN(x) + xmotion;
+ int sry = BALIGN(y) + ymotion;
+
+ HNM6::doMotion<2, 2>(xform, current, current, srx, sry, x, y);
+}
+
+template<int sx, int sy, bool small = false>
+void DecoderImpl::renderIXkfMotion(Graphics::Surface ¤t, uint x, uint y) {
+ uint16 moword = _motion.next();
+
+ byte xform;
+ int srx, sry;
+
+ if (small) {
+ xform = (moword >> 12) & 0x7;
+ srx = BALIGN(x) + 63 - (moword & 0x7F);
+ sry = BALIGN(y) + 6 - ((moword >> 7) & 0x1F);
+ // If srx goes beyond the line it will end up on previous or next line
+ // This is expected.
+ // For this to work, lines must be contiguous.
+ } else {
+ xform = _bitbuf.next() << 2;
+ xform |= _bitbuf.next() << 1;
+ xform |= moword >> 15;
+
+ int offy = (sx == 8 && sy == 8) ? 0 : 4;
+ srx = BALIGN(x) + 128 - ((moword >> 7) & 0xFF);
+ sry = BALIGN(y) + offy - (moword & 0x7F);
+ WRAP_LINE(srx, current.w);
+ }
+
+ HNM6::doMotion<sx, sy>(xform, current, current, srx, sry, x, y);
+}
+
+template<int sx, int sy>
+void DecoderImpl::renderSkip(Graphics::Surface ¤t, Graphics::Surface &previous,
+ uint x, uint y) {
+ // Use already coded copy transform
+ doMotion<sx, sy>(0, current, previous, x, y, x, y);
+}
+
+template<int sx, int sy, bool small = false>
+void DecoderImpl::renderIXifMotion(Graphics::Surface ¤t, Graphics::Surface &previous,
+ uint x, uint y) {
+ uint16 moword = _motion.next();
+
+ byte xform;
+ int srx, sry;
+
+ bool use_current = (moword & 0x8000) != 0;
+
+ if (small) {
+ xform = (moword >> 12) & 0x7;
+ if (use_current) {
+ srx = BALIGN(x) + 63 - (moword & 0x7F);
+ sry = BALIGN(y) + 6 - ((moword >> 7) & 0x1F);
+ } else {
+ srx = BALIGN(x) + 31 - (moword & 0x3F);
+ sry = BALIGN(y) + 31 - ((moword >> 6) & 0x3F);
+ }
+ // If srx goes beyond the line it will end up on previous or next line
+ // This is expected.
+ // For this to work, lines must be contiguous.
+ } else {
+ xform = _bitbuf.next() << 2;
+ xform |= _bitbuf.next() << 1;
+ xform |= _bitbuf.next();
+
+ int offy = use_current ? ((sx == 8 && sy == 8) ? 0 : 4) : 64;
+ srx = BALIGN(x) + 128 - ((moword >> 7) & 0xFF);
+ sry = BALIGN(y) + offy - (moword & 0x7F);
+ WRAP_LINE(srx, current.w);
+ }
+
+ HNM6::doMotion<sx, sy>(xform, current, use_current ? current : previous, srx, sry, x, y);
+}
+
+template<int sx, int sy>
+void DecoderImpl::renderIXifShortmo(Graphics::Surface ¤t, Graphics::Surface &previous,
+ uint x, uint y) {
+ uint16 somoword = _shortmo.next();
+
+ int ox, oy;
+ if (somoword == 0) {
+ ox = 1;
+ oy = 0;
+ } else {
+ somoword--;
+ int span = 1;
+ while (somoword >= 8 * span) {
+ somoword -= 8 * span;
+ span++;
+ }
+ int baseX = -span + 1;
+ int baseY = -span + 1;
+
+ int side_sz = (span * 8) / 4;
+ byte side = somoword / side_sz;
+ int side_pos = somoword % side_sz;
+
+ int offX, offY;
+ switch (side) {
+ case 0:
+ offX = 0;
+ offY = side_pos;
+ break;
+ case 1:
+ offX = side_pos + 1;
+ offY = side_sz - 1;
+ break;
+ case 2:
+ offX = side_sz;
+ offY = side_sz - 1 - side_pos - 1;
+ break;
+ case 3:
+ offX = side_sz - 1 - side_pos;
+ offY = -1;
+ break;
+ default:
+ error("BUG: Invalid side in short motion");
+ }
+ ox = baseX + offX;
+ oy = baseY + offY;
+ }
+
+ // This is NOT aligned on block coordinates
+ // If srx goes beyond the line it will end up on previous or next line
+ // This is expected.
+ // For this to work, lines must be contiguous.
+ int srx = x + ox;
+ int sry = y + oy;
+
+ // Use already coded copy transform
+ HNM6::doMotion<sx, sy>(0, current, previous, srx, sry, x, y);
+}
+
+#undef WRAP_LINE
+#undef BALIGN
+
+DecoderImpl::DecoderImpl(uint16 width, uint16 height, const Graphics::PixelFormat &format,
+ uint32 bufferSize, bool videoMode) :
+ HNM6Decoder(width, height, format, videoMode), _bufferSize(bufferSize) {
+ if (format.bytesPerPixel != 2 && format.bytesPerPixel != 4) {
+ error("Unsupported bpp");
+ }
+
+ if (bufferSize < HEADER_SIZE) {
+ error("Invalid buffer size");
+ }
+ _buffer = new byte[bufferSize];
+ _surface.create(_width, _height, _format);
+ if (videoMode) {
+ _surfaceOld.create(_width, _height, _format);
+ }
+}
+
+DecoderImpl::~DecoderImpl() {
+ delete [] _buffer;
+ _surface.free();
+ _surfaceOld.free();
+}
+
+const Graphics::Surface *DecoderImpl::decodeFrame(Common::SeekableReadStream &stream) {
+ // Switch both surfaces
+ void *oldPixels = _surfaceOld.getPixels();
+ if (oldPixels) {
+ _surfaceOld.setPixels(_surface.getPixels());
+ _surface.setPixels(oldPixels);
+ }
+
+ if (stream.read(_buffer, HEADER_SIZE) != HEADER_SIZE) {
+ error("Invalid header");
+ }
+
+ int32 quality = READ_LE_UINT32(_buffer + 0 * 4);
+ uint32 bit_start = READ_LE_UINT32(_buffer + 1 * 4);
+ uint32 motion_start = READ_LE_UINT32(_buffer + 2 * 4);
+ uint32 shortmotion_start = READ_LE_UINT32(_buffer + 3 * 4);
+ uint32 jpeg_start = READ_LE_UINT32(_buffer + 4 * 4);
+ uint32 end = READ_LE_UINT32(_buffer + 5 * 4);
+
+ if (bit_start > end ||
+ motion_start > end ||
+ shortmotion_start > end ||
+ jpeg_start > end ||
+ end > _bufferSize) {
+ error("Invalid header offsets");
+ }
+
+ if (stream.read(_buffer + HEADER_SIZE, end - HEADER_SIZE) != end - HEADER_SIZE) {
+ error("Invalid chunk length");
+ }
+
+ reset(_buffer, bit_start, motion_start,
+ shortmotion_start, jpeg_start, end, quality);
+ decode(_surface, _surfaceOld);
+
+ return &_surface;
+}
+
+} // End of namespace HNM6
+
+HNM6Decoder *createHNM6Decoder(uint16 width, uint16 height, const Graphics::PixelFormat &format,
+ uint32 bufferSize, bool videoMode) {
+ return new HNM6::DecoderImpl(width, height, format, bufferSize, videoMode);
+}
+
+} // End of namespace Image
diff --git a/image/codecs/hnm.h b/image/codecs/hnm.h
new file mode 100644
index 00000000000..2f8adacfab9
--- /dev/null
+++ b/image/codecs/hnm.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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef IMAGE_CODECS_HNM6_H
+#define IMAGE_CODECS_HNM6_H
+
+#include "image/codecs/codec.h"
+
+namespace Image {
+
+/**
+ * HNM6 image decoder interface.
+ *
+ * Used by HNM6 image and video formats.
+ */
+class HNM6Decoder : public Codec {
+public:
+ uint16 getWidth() const { return _width; }
+ uint16 getHeight() const { return _height; }
+ Graphics::PixelFormat getPixelFormat() const override { return _format; }
+
+ void setWarpMode(bool warpMode) { assert(!warpMode || !_videoMode); _warpMode = warpMode; }
+protected:
+ HNM6Decoder(uint16 width, uint16 height, const Graphics::PixelFormat &format,
+ bool videoMode = false) : Codec(),
+ _width(width), _height(height), _format(format), _videoMode(videoMode), _warpMode(false) { }
+
+ uint16 _width, _height;
+ Graphics::PixelFormat _format;
+ bool _warpMode, _videoMode;
+};
+
+HNM6Decoder *createHNM6Decoder(uint16 width, uint16 height, const Graphics::PixelFormat &format,
+ uint32 bufferSize, bool videoMode = false);
+
+} // End of namespace Image
+
+#endif
diff --git a/image/module.mk b/image/module.mk
index 1cfff5c405a..fa78a362761 100644
--- a/image/module.mk
+++ b/image/module.mk
@@ -15,6 +15,7 @@ MODULE_OBJS := \
codecs/cinepak.o \
codecs/codec.o \
codecs/hlz.o \
+ codecs/hnm.o \
codecs/indeo3.o \
codecs/indeo4.o \
codecs/indeo5.o \
diff --git a/video/hnm_decoder.cpp b/video/hnm_decoder.cpp
index 44cee2e5f51..9211081a18f 100644
--- a/video/hnm_decoder.cpp
+++ b/video/hnm_decoder.cpp
@@ -27,16 +27,23 @@
#include "common/textconsole.h"
#include "audio/decoders/raw.h"
+#include "audio/decoders/apc.h"
-#include "video/hnm_decoder.h"
#include "image/codecs/hlz.h"
+#include "image/codecs/hnm.h"
+
+#include "video/hnm_decoder.h"
namespace Video {
-// When no sound display a frame every 80ms
-HNMDecoder::HNMDecoder(bool loop, byte *initialPalette) : _regularFrameDelayMs(80),
- _videoTrack(nullptr), _audioTrack(nullptr), _stream(nullptr),
- _loop(loop), _initialPalette(initialPalette), _dataBuffer(nullptr), _dataBufferAlloc(0) {
+HNMDecoder::HNMDecoder(const Graphics::PixelFormat &format, bool loop,
+ byte *initialPalette) : _regularFrameDelayMs(uint32(-1)),
+ _videoTrack(nullptr), _audioTrack(nullptr), _stream(nullptr), _format(format),
+ _loop(loop), _initialPalette(initialPalette), _alignedChunks(false),
+ _dataBuffer(nullptr), _dataBufferAlloc(0) {
+ if (initialPalette && format.bytesPerPixel >= 2) {
+ error("Invalid pixel format while initial palette is set");
+ }
}
HNMDecoder::~HNMDecoder() {
@@ -50,17 +57,31 @@ HNMDecoder::~HNMDecoder() {
bool HNMDecoder::loadStream(Common::SeekableReadStream *stream) {
close();
+ // Take the ownership of stream immediately
+ _stream = stream;
+
uint32 tag = stream->readUint32BE();
- /* For now, only HNM4 and UBB2, HNM6 in the future */
+ /* All HNM versions are supported */
if (tag != MKTAG('H', 'N', 'M', '4') &&
- tag != MKTAG('U', 'B', 'B', '2')) {
+ tag != MKTAG('U', 'B', 'B', '2') &&
+ tag != MKTAG('H', 'N', 'M', '6')) {
close();
return false;
}
- //uint32 ukn = stream->readUint32BE();
- stream->skip(4);
+ byte audioflags = 0;
+ uint16 soundBits = 0, soundFormat = 0;
+ if (tag == MKTAG('H', 'N', 'M', '6')) {
+ //uint16 ukn6_1 = stream->readUint16LE();
+ stream->skip(2);
+ audioflags = stream->readByte();
+ //byte bpp = stream->readByte();
+ stream->skip(1);
+ } else {
+ //uint32 ukn = stream->readUint32BE();
+ stream->skip(4);
+ }
uint16 width = stream->readUint16LE();
uint16 height = stream->readUint16LE();
//uint32 filesize = stream->readUint32LE();
@@ -68,8 +89,15 @@ bool HNMDecoder::loadStream(Common::SeekableReadStream *stream) {
uint32 frameCount = stream->readUint32LE();
//uint32 tabOffset = stream->readUint32LE();
stream->skip(4);
- uint16 soundBits = stream->readUint16LE();
- uint16 soundFormat = stream->readUint16LE();
+ if (tag == MKTAG('H', 'N', 'M', '4') ||
+ tag == MKTAG('U', 'B', 'B', '2')) {
+ soundBits = stream->readUint16LE();
+ soundFormat = stream->readUint16LE();
+ } else {
+ //uint16 ukn6_2 = stream->readUint16LE();
+ //uint16 ukn6_3 = stream->readUint16LE();
+ stream->skip(4);
+ }
uint32 frameSize = stream->readUint32LE();
byte unknownStr[16];
@@ -84,10 +112,26 @@ bool HNMDecoder::loadStream(Common::SeekableReadStream *stream) {
// When no audio use a factor of 1 for audio timestamp
uint32 audioSampleRate = 1;
+ // When no sound display a frame every X ms if not overriden
+ if (_regularFrameDelayMs == uint32(-1)) {
+ if (tag == MKTAG('H', 'N', 'M', '4') ||
+ tag == MKTAG('U', 'B', 'B', '2')) {
+ // For HNM4&5 it's every 80 ms
+ _regularFrameDelayMs = 80;
+ } else if (tag == MKTAG('H', 'N', 'M', '6')) {
+ // For HNM6 it's every 66 ms
+ _regularFrameDelayMs = 66;
+ }
+ }
_videoTrack = nullptr;
_audioTrack = nullptr;
if (tag == MKTAG('H', 'N', 'M', '4')) {
+ if (_format.bytesPerPixel >= 2) {
+ // Bad constructor used
+ close();
+ return false;
+ }
if (soundFormat == 2 && soundBits != 0) {
// HNM4 is Mono 22050Hz
_audioTrack = new DPCMAudioTrack(soundFormat, soundBits, 22050, false, getSoundType());
@@ -97,6 +141,11 @@ bool HNMDecoder::loadStream(Common::SeekableReadStream *stream) {
_regularFrameDelayMs, audioSampleRate,
_initialPalette);
} else if (tag == MKTAG('U', 'B', 'B', '2')) {
+ if (_format.bytesPerPixel >= 2) {
+ // Bad constructor used
+ close();
+ return false;
+ }
if (soundFormat == 2 && soundBits == 0) {
// UBB2 is Stereo 22050Hz
_audioTrack = new DPCMAudioTrack(soundFormat, 16, 22050, true, getSoundType());
@@ -105,6 +154,21 @@ bool HNMDecoder::loadStream(Common::SeekableReadStream *stream) {
_videoTrack = new HNM5VideoTrack(width, height, frameSize, frameCount,
_regularFrameDelayMs, audioSampleRate,
_initialPalette);
+ } else if (tag == MKTAG('H', 'N', 'M', '6')) {
+ if (_format.bytesPerPixel < 2) {
+ // Bad constructor used or bad format given
+ close();
+ return false;
+ }
+ _alignedChunks = true;
+ if (audioflags & 1) {
+ byte stereo = (audioflags >> 7) & 1;
+ audioSampleRate = ((audioflags >> 4) & 6) * 11025;
+ _audioTrack = new APCAudioTrack(audioSampleRate, stereo, getSoundType());
+ }
+ _videoTrack = new HNM6VideoTrack(width, height, frameSize, frameCount,
+ _regularFrameDelayMs, audioSampleRate,
+ _format);
} else {
// We should never be here
close();
@@ -115,8 +179,6 @@ bool HNMDecoder::loadStream(Common::SeekableReadStream *stream) {
addTrack(_audioTrack);
}
- _stream = stream;
-
return true;
}
@@ -183,9 +245,11 @@ void HNMDecoder::readNextPacket() {
error("Chunk has a bogus size");
}
- if (chunkType == MKTAG16('S', 'D')) {
+ if (chunkType == MKTAG16('S', 'D') ||
+ chunkType == MKTAG16('A', 'A') ||
+ chunkType == MKTAG16('B', 'B')) {
if (_audioTrack) {
- audioNumSamples = _audioTrack->decodeSound(data_p, chunkSize - 8);
+ audioNumSamples = _audioTrack->decodeSound(chunkType, data_p, chunkSize - 8);
} else {
warning("Got audio data without an audio track");
}
@@ -193,6 +257,10 @@ void HNMDecoder::readNextPacket() {
_videoTrack->decodeChunk(data_p, chunkSize - 8, chunkType, flags);
}
+ if (_alignedChunks) {
+ chunkSize = ((chunkSize + 3) / 4) * 4;
+ }
+
data_p += (chunkSize - 8);
superchunkRemaining -= chunkSize;
}
@@ -206,27 +274,6 @@ HNMDecoder::HNMVideoTrack::HNMVideoTrack(uint32 frameCount,
restart();
}
-void HNMDecoder::HNMVideoTrack::newFrame(uint32 frameDelay) {
- // Frame done
- _curFrame++;
-
- // Add frameDelay if we had no sound
- // We can't rely on a detection in the header as some soundless HNM indicate they have
- if (frameDelay == uint32(-1)) {
- _nextFrameStartTime = _nextFrameStartTime.addMsecs(_regularFrameDelayMs);
- return;
- }
-
- // HNM decoders use sound double buffering to pace the frames
- // First frame is loaded in first buffer, second frame in second buffer
- // It's only for third frame that we wait for the first buffer to be free
- // It's presentation time is then delayed by the number of sound samples in the first frame
- if (_lastFrameDelaySamps) {
- _nextFrameStartTime = _nextFrameStartTime.addFrames(_lastFrameDelaySamps);
- }
- _lastFrameDelaySamps = frameDelay;
-}
-
HNMDecoder::HNM45VideoTrack::HNM45VideoTrack(uint32 width, uint32 height, uint32 frameSize,
uint32 frameCount, uint32 regularFrameDelayMs, uint32 audioSampleRate,
const byte *initialPalette) :
@@ -260,6 +307,27 @@ HNMDecoder::HNM45VideoTrack::~HNM45VideoTrack() {
_frameBufferP = nullptr;
}
+void HNMDecoder::HNM45VideoTrack::newFrame(uint32 frameDelay) {
+ // Frame done
+ _curFrame++;
+
+ // Add regular frame delay if we had no sound
+ // We can't rely on a detection in the header as some soundless HNM indicate they have
+ if (frameDelay == uint32(-1)) {
+ _nextFrameStartTime = _nextFrameStartTime.addMsecs(_regularFrameDelayMs);
+ return;
+ }
+
+ // HNM decoders use sound double buffering to pace the frames
+ // First frame is loaded in first buffer, second frame in second buffer
+ // It's only for third frame that we wait for the first buffer to be free
+ // It's presentation time is then delayed by the number of sound samples in the first frame
+ if (_lastFrameDelaySamps) {
+ _nextFrameStartTime = _nextFrameStartTime.addFrames(_lastFrameDelaySamps);
+ }
+ _lastFrameDelaySamps = frameDelay;
+}
+
void HNMDecoder::HNM45VideoTrack::decodePalette(byte *data, uint32 size) {
while (true) {
if (size < 2) {
@@ -972,8 +1040,67 @@ void HNMDecoder::HNM5VideoTrack::decodeFrame(byte *data, uint32 size) {
_surface.setPixels(_frameBufferC);
}
+HNMDecoder::HNM6VideoTrack::HNM6VideoTrack(uint32 width, uint32 height, uint32 frameSize,
+ uint32 frameCount, uint32 regularFrameDelayMs, uint32 audioSampleRate,
+ const Graphics::PixelFormat &format) :
+ HNMVideoTrack(frameCount, regularFrameDelayMs, audioSampleRate),
+ _decoder(Image::createHNM6Decoder(width, height, format, frameSize, true)),
+ _surface(nullptr) {
+}
+
+HNMDecoder::HNM6VideoTrack::~HNM6VideoTrack() {
+ delete _decoder;
+}
+
+uint16 HNMDecoder::HNM6VideoTrack::getWidth() const {
+ return _decoder->getWidth();
+}
+
+uint16 HNMDecoder::HNM6VideoTrack::getHeight() const {
+ return _decoder->getHeight();
+}
+
+Graphics::PixelFormat HNMDecoder::HNM6VideoTrack::getPixelFormat() const {
+ return _decoder->getPixelFormat();
+}
+
+void HNMDecoder::HNM6VideoTrack::decodeChunk(byte *data, uint32 size,
+ uint16 chunkType, uint16 flags) {
+ if (chunkType == MKTAG16('I', 'X') ||
+ chunkType == MKTAG16('I', 'W')) {
+ Common::MemoryReadStream stream(data, size);
+ _surface = _decoder->decodeFrame(stream);
+ } else {
+ error("HNM6: Got %d chunk: size %d", chunkType, size);
+ }
+}
+
+void HNMDecoder::HNM6VideoTrack::newFrame(uint32 frameDelay) {
+ // Frame done
+ _curFrame++;
+
+ // In HNM6 first frame contains the sound for the 32 following frames (pre-buffering)
+ // Then other frames contain constant size sound chunks except for the 32 last ones
+ if (!_lastFrameDelaySamps) {
+ // Add regular frame delay if we had no sound
+ if (frameDelay == uint32(-1)) {
+ _nextFrameStartTime = _nextFrameStartTime.addMsecs(_regularFrameDelayMs);
+ return;
+ }
+
+ assert((frameDelay & 31) == 0);
+ _lastFrameDelaySamps = frameDelay / 32;
+ _nextFrameStartTime = _nextFrameStartTime.addFrames(_lastFrameDelaySamps);
+ } else {
+ if (frameDelay != uint32(-1)) {
+ assert(frameDelay == _lastFrameDelaySamps);
+ }
+ _nextFrameStartTime = _nextFrameStartTime.addFrames(_lastFrameDelaySamps);
+ }
+}
+
HNMDecoder::DPCMAudioTrack::DPCMAudioTrack(uint16 format, uint16 bits, uint sampleRate, bool stereo,
- Audio::Mixer::SoundType soundType) : AudioTrack(soundType), _audioStream(nullptr),
+ Audio::Mixer::SoundType soundType) : HNMAudioTrack(soundType), _audioStream(nullptr),
_gotLUT(false), _lastSampleL(0), _lastSampleR(0), _sampleRate(sampleRate), _stereo(stereo) {
if (bits != 16) {
error("Unsupported audio bits");
@@ -989,7 +1116,7 @@ HNMDecoder::DPCMAudioTrack::~DPCMAudioTrack() {
delete _audioStream;
}
-uint32 HNMDecoder::DPCMAudioTrack::decodeSound(byte *data, uint32 size) {
+uint32 HNMDecoder::DPCMAudioTrack::decodeSound(uint16 chunkType, byte *data, uint32 size) {
if (!_gotLUT) {
if (size < 256 * sizeof(*_lut)) {
error("Invalid first sound chunk");
@@ -1051,4 +1178,39 @@ uint32 HNMDecoder::DPCMAudioTrack::decodeSound(byte *data, uint32 size) {
return numSamples;
}
+HNMDecoder::APCAudioTrack::APCAudioTrack(uint sampleRate, byte stereo,
+ Audio::Mixer::SoundType soundType) : HNMAudioTrack(soundType),
+ _audioStream(Audio::makeAPCStream(sampleRate, stereo)) {
+}
+
+HNMDecoder::APCAudioTrack::~APCAudioTrack() {
+ delete _audioStream;
+}
+
+Audio::AudioStream *HNMDecoder::APCAudioTrack::getAudioStream() const {
+ return _audioStream;
+}
+
+uint32 HNMDecoder::APCAudioTrack::decodeSound(uint16 chunkType, byte *data, uint32 size) {
+ if (chunkType == MKTAG16('A', 'A')) {
+ Common::MemoryReadStream *stream = new Common::MemoryReadStream(data, size);
+ if (!_audioStream->init(*stream)) {
+ error("Invalid APC stream");
+ }
+ // Remove header for sample counting
+ size -= stream->pos();
+ _audioStream->queuePacket(stream);
+ // stream is freed now
+ stream = nullptr;
+ } else if (chunkType == MKTAG16('B', 'B')) {
+ _audioStream->queuePacket(new Common::MemoryReadStream(data, size));
+ }
+ // 2 4-bit samples in one byte
+ uint32 numSamples = size;
+ if (!_audioStream->isStereo()) {
+ numSamples *= 2;
+ }
+ return numSamples;
+}
+
} // End of namespace Video
diff --git a/video/hnm_decoder.h b/video/hnm_decoder.h
index b69f83f28a0..29361fbf732 100644
--- a/video/hnm_decoder.h
+++ b/video/hnm_decoder.h
@@ -22,16 +22,24 @@
#ifndef VIDEO_HNM_DECODER_H
#define VIDEO_HNM_DECODER_H
+#include "audio/audiostream.h"
#include "common/rational.h"
-#include "graphics/pixelformat.h"
-#include "video/video_decoder.h"
#include "graphics/surface.h"
-#include "audio/audiostream.h"
+#include "video/video_decoder.h"
+
+
+namespace Audio {
+class APCStream;
+}
namespace Common {
class SeekableReadStream;
}
+namespace Image {
+class HNM6Decoder;
+}
+
namespace Video {
/**
@@ -43,7 +51,7 @@ namespace Video {
*/
class HNMDecoder : public VideoDecoder {
public:
- HNMDecoder(bool loop = false, byte *initialPalette = nullptr);
+ HNMDecoder(const Graphics::PixelFormat &format, bool loop = false, byte *initialPalette = nullptr);
~HNMDecoder() override;
bool loadStream(Common::SeekableReadStream *stream) override;
void readNextPacket() override;
@@ -63,8 +71,8 @@ private:
uint32 getNextFrameStartTime() const override { return _nextFrameStartTime.msecs(); }
void restart() { _lastFrameDelaySamps = 0; }
- void newFrame(uint32 frameDelay);
+ virtual void newFrame(uint32 frameDelay) = 0;
virtual void decodeChunk(byte *data, uint32 size,
uint16 chunkType, uint16 flags) = 0;
@@ -87,6 +95,8 @@ private:
const byte *getPalette() const override { _dirtyPalette = false; return _palette; }
bool hasDirtyPalette() const override { return _dirtyPalette; }
+ virtual void newFrame(uint32 frameDelay) override;
+
protected:
HNM45VideoTrack(uint32 width, uint32 height, uint32 frameSize, uint32 frameCount,
uint32 regularFrameDelayMs, uint32 audioSampleRate,
@@ -142,13 +152,41 @@ private:
void decodeFrame(byte *data, uint32 size);
};
- class DPCMAudioTrack : public AudioTrack {
+ class HNM6VideoTrack : public HNMVideoTrack {
+ public:
+ HNM6VideoTrack(uint32 width, uint32 height, uint32 frameSize, uint32 frameCount,
+ uint32 regularFrameDelayMs, uint32 audioSampleRate,
+ const Graphics::PixelFormat &format);
+ ~HNM6VideoTrack() override;
+
+ uint16 getWidth() const override;
+ uint16 getHeight() const override;
+ Graphics::PixelFormat getPixelFormat() const override;
+ const Graphics::Surface *decodeNextFrame() override { return _surface; }
+
+ virtual void newFrame(uint32 frameDelay) override;
+ /** Decode a video chunk. */
+ void decodeChunk(byte *data, uint32 size,
+ uint16 chunkType, uint16 flags) override;
+ private:
+ Image::HNM6Decoder *_decoder;
+ const Graphics::Surface *_surface;
+ };
+
+ class HNMAudioTrack : public AudioTrack {
+ public:
+ HNMAudioTrack(Audio::Mixer::SoundType soundType) : AudioTrack(soundType) {}
+
+ virtual uint32 decodeSound(uint16 chunkType, byte *data, uint32 size) = 0;
+ };
+
+ class DPCMAudioTrack : public HNMAudioTrack {
public:
DPCMAudioTrack(uint16 format, uint16 bits, uint sampleRate, bool stereo,
Audio::Mixer::SoundType soundType);
~DPCMAudioTrack() override;
- uint32 decodeSound(byte *data, uint32 size);
+ uint32 decodeSound(uint16 chunkType, byte *data, uint32 size) override;
protected:
Audio::AudioStream *getAudioStream() const override { return _audioStream; }
private:
@@ -161,15 +199,30 @@ private:
bool _stereo;
};
+ class APCAudioTrack : public HNMAudioTrack {
+ public:
+ APCAudioTrack(uint sampleRate, byte stereo,
+ Audio::Mixer::SoundType soundType);
+ ~APCAudioTrack() override;
+
+ uint32 decodeSound(uint16 chunkType, byte *data, uint32 size) override;
+ protected:
+ Audio::AudioStream *getAudioStream() const override;
+ private:
+ Audio::APCStream *_audioStream;
+ };
+
+ Graphics::PixelFormat _format;
bool _loop;
byte *_initialPalette;
uint32 _regularFrameDelayMs;
// These two pointer are owned by VideoDecoder
HNMVideoTrack *_videoTrack;
- DPCMAudioTrack *_audioTrack;
+ HNMAudioTrack *_audioTrack;
Common::SeekableReadStream *_stream;
+ bool _alignedChunks;
byte *_dataBuffer;
uint32 _dataBufferAlloc;
};
More information about the Scummvm-git-logs
mailing list