aboutsummaryrefslogtreecommitdiff
path: root/graphics/video
diff options
context:
space:
mode:
authorSven Hesse2010-08-08 00:42:59 +0000
committerSven Hesse2010-08-08 00:42:59 +0000
commitfecbdf60a98d2cbadfc26b406b7076ae74424066 (patch)
treee081286c38ab1b7b8b512fad139dccf8d14c50c7 /graphics/video
parent6172fe8ea7cf8046e5048e56a512ad0f847ac324 (diff)
downloadscummvm-rg350-fecbdf60a98d2cbadfc26b406b7076ae74424066.tar.gz
scummvm-rg350-fecbdf60a98d2cbadfc26b406b7076ae74424066.tar.bz2
scummvm-rg350-fecbdf60a98d2cbadfc26b406b7076ae74424066.zip
VIDEO/GOB: Implement IMD frame rendering
svn-id: r51867
Diffstat (limited to 'graphics/video')
-rw-r--r--graphics/video/coktel_decoder.cpp335
-rw-r--r--graphics/video/coktel_decoder.h13
2 files changed, 339 insertions, 9 deletions
diff --git a/graphics/video/coktel_decoder.cpp b/graphics/video/coktel_decoder.cpp
index ebb1c9492b..c8a934d5bb 100644
--- a/graphics/video/coktel_decoder.cpp
+++ b/graphics/video/coktel_decoder.cpp
@@ -121,6 +121,10 @@ const Common::List<Common::Rect> &CoktelDecoder::getDirtyRects() const {
return _dirtyRects;
}
+bool CoktelDecoder::hasPalette() const {
+ return (_features & kFeaturesPalette) != 0;
+}
+
bool CoktelDecoder::hasSound() const {
return _hasSound;
}
@@ -202,6 +206,235 @@ bool CoktelDecoder::hasDirtyPalette() const {
return (_features & kFeaturesPalette) && _paletteDirty;
}
+void CoktelDecoder::deLZ77(byte *dest, byte *src) {
+ int i;
+ byte buf[4370];
+ uint16 chunkLength;
+ uint32 frameLength;
+ uint16 bufPos1;
+ uint16 bufPos2;
+ uint16 tmp;
+ uint8 chunkBitField;
+ uint8 chunkCount;
+ bool mode;
+
+ frameLength = READ_LE_UINT32(src);
+ src += 4;
+
+ if ((READ_LE_UINT16(src) == 0x1234) && (READ_LE_UINT16(src + 2) == 0x5678)) {
+ src += 4;
+ bufPos1 = 273;
+ mode = 1; // 123Ch (cmp al, 12h)
+ } else {
+ bufPos1 = 4078;
+ mode = 0; // 275h (jnz +2)
+ }
+
+ memset(buf, 32, bufPos1);
+ chunkCount = 1;
+ chunkBitField = 0;
+
+ while (frameLength > 0) {
+ chunkCount--;
+ if (chunkCount == 0) {
+ tmp = *src++;
+ chunkCount = 8;
+ chunkBitField = tmp;
+ }
+ if (chunkBitField % 2) {
+ chunkBitField >>= 1;
+ buf[bufPos1] = *src;
+ *dest++ = *src++;
+ bufPos1 = (bufPos1 + 1) % 4096;
+ frameLength--;
+ continue;
+ }
+ chunkBitField >>= 1;
+
+ tmp = READ_LE_UINT16(src);
+ src += 2;
+ chunkLength = ((tmp & 0xF00) >> 8) + 3;
+
+ if ((mode && ((chunkLength & 0xFF) == 0x12)) ||
+ (!mode && (chunkLength == 0)))
+ chunkLength = *src++ + 0x12;
+
+ bufPos2 = (tmp & 0xFF) + ((tmp >> 4) & 0x0F00);
+ if (((tmp + chunkLength) >= 4096) ||
+ ((chunkLength + bufPos1) >= 4096)) {
+
+ for (i = 0; i < chunkLength; i++, dest++) {
+ *dest = buf[bufPos2];
+ buf[bufPos1] = buf[bufPos2];
+ bufPos1 = (bufPos1 + 1) % 4096;
+ bufPos2 = (bufPos2 + 1) % 4096;
+ }
+
+ } else if (((tmp + chunkLength) < bufPos1) ||
+ ((chunkLength + bufPos1) < bufPos2)) {
+
+ memcpy(dest, buf + bufPos2, chunkLength);
+ memmove(buf + bufPos1, buf + bufPos2, chunkLength);
+
+ dest += chunkLength;
+ bufPos1 += chunkLength;
+ bufPos2 += chunkLength;
+
+ } else {
+
+ for (i = 0; i < chunkLength; i++, dest++, bufPos1++, bufPos2++) {
+ *dest = buf[bufPos2];
+ buf[bufPos1] = buf[bufPos2];
+ }
+
+ }
+ frameLength -= chunkLength;
+
+ }
+}
+
+// A whole, completely filled block
+void CoktelDecoder::renderBlockWhole(const byte *src) {
+ Common::Rect &rect = _dirtyRects.back();
+ Common::Rect drawRect = rect;
+
+ drawRect.clip(_surface.w, _surface.h);
+
+ byte *dst = ((byte *) _surface.pixels) + (drawRect.top * _surface.pitch) + drawRect.left;
+ for (int i = 0; i < drawRect.height(); i++) {
+ memcpy(dst, src, drawRect.width());
+
+ src += rect.width();
+ dst += _surface.pitch;
+ }
+}
+
+// A quarter-wide whole, completely filled block
+void CoktelDecoder::renderBlockWhole4X(const byte *src) {
+ Common::Rect &rect = _dirtyRects.back();
+ Common::Rect drawRect = rect;
+
+ drawRect.clip(_surface.w, _surface.h);
+
+ byte *dst = ((byte *) _surface.pixels) + (drawRect.top * _surface.pitch) + drawRect.left;
+ for (int i = 0; i < drawRect.height(); i++) {
+ byte *dstRow = dst;
+ const byte *srcRow = src;
+
+ int16 count = drawRect.width();
+ while (count >= 0) {
+ memset(dstRow, *srcRow, MIN<int16>(count, 4));
+
+ count -= 4;
+ dstRow += 4;
+ srcRow += 1;
+ }
+
+ src += rect.width() / 4;
+ dst += _surface.pitch;
+ }
+}
+
+// A half-high whole, completely filled block
+void CoktelDecoder::renderBlockWhole2Y(const byte *src) {
+ warning("renderBlockWhole2Y");
+
+ Common::Rect &rect = _dirtyRects.back();
+ Common::Rect drawRect = rect;
+
+ drawRect.clip(_surface.w, _surface.h);
+
+ int16 height = drawRect.height();
+
+ byte *dst = ((byte *) _surface.pixels) + (drawRect.top * _surface.pitch) + drawRect.left;
+ while (height > 1) {
+ memcpy(dst , src, drawRect.width());
+ memcpy(dst + _surface.pitch, src, drawRect.width());
+
+ height -= 2;
+ src += rect.width();
+ dst += 2 * _surface.pitch;
+ }
+
+ if (height == 1)
+ memcpy(dst, src, drawRect.width());
+}
+
+// A sparse block
+void CoktelDecoder::renderBlockSparse(const byte *src) {
+ Common::Rect &rect = _dirtyRects.back();
+ Common::Rect drawRect = rect;
+
+ drawRect.clip(_surface.w, _surface.h);
+
+ byte *dst = ((byte *) _surface.pixels) + (drawRect.top * _surface.pitch) + drawRect.left;
+ for (int i = 0; i < drawRect.height(); i++) {
+ byte *dstRow = dst;
+ int16 pixWritten = 0;
+
+ while (pixWritten < rect.width()) {
+ int16 pixCount = *src++;
+
+ if (pixCount & 0x80) { // Data
+ int16 copyCount;
+
+ pixCount = MIN((pixCount & 0x7F) + 1, rect.width() - pixWritten);
+ copyCount = CLIP<int16>(drawRect.width() - pixWritten, 0, pixCount);
+ memcpy(dstRow, src, copyCount);
+
+ pixWritten += pixCount;
+ dstRow += pixCount;
+ src += pixCount;
+ } else { // "Hole"
+ pixWritten += pixCount + 1;
+ dstRow += pixCount + 1;
+ }
+
+ }
+
+ dst += _surface.pitch;
+ }
+}
+
+// A half-high sparse block
+void CoktelDecoder::renderBlockSparse2Y(const byte *src) {
+ warning("renderBlockSparse2Y");
+
+ Common::Rect &rect = _dirtyRects.back();
+ Common::Rect drawRect = rect;
+
+ drawRect.clip(_surface.w, _surface.h);
+
+ byte *dst = ((byte *) _surface.pixels) + (drawRect.top * _surface.pitch) + drawRect.left;
+ for (int i = 0; i < drawRect.height(); i += 2) {
+ byte *dstRow = dst;
+ int16 pixWritten = 0;
+
+ while (pixWritten < rect.width()) {
+ int16 pixCount = *src++;
+
+ if (pixCount & 0x80) { // Data
+ int16 copyCount;
+
+ pixCount = MIN((pixCount & 0x7F) + 1, rect.width() - pixWritten);
+ copyCount = CLIP<int16>(drawRect.width() - pixWritten, 0, pixCount);
+ memcpy(dstRow , src, pixCount);
+ memcpy(dstRow + _surface.pitch, src, pixCount);
+
+ pixWritten += pixCount;
+ dstRow += pixCount;
+ src += pixCount;
+ } else { // "Hole"
+ pixWritten += pixCount + 1;
+ dstRow += pixCount + 1;
+ }
+
+ }
+
+ dst += _surface.pitch;
+ }
+}
+
Common::Rational CoktelDecoder::getFrameRate() const {
return _frameRate;
}
@@ -471,7 +704,7 @@ bool IMDDecoder::seek(int32 frame, int whence, bool restart) {
return true;
} else {
- warning("Imd::seek(): Frame %d is not directly accessible", frame + 1);
+ warning("IMDDecoder::seek(): Frame %d is not directly accessible", frame + 1);
return false;
}
@@ -482,6 +715,37 @@ bool IMDDecoder::seek(int32 frame, int whence, bool restart) {
return true;
}
+void IMDDecoder::setXY(uint16 x, uint16 y) {
+ // Adjusting the standard coordinates
+ if (_stdX != -1) {
+ if (x != 0xFFFF)
+ _stdX = _stdX - _x + x;
+ if (y != 0xFFFF)
+ _stdY = _stdY - _y + y;
+ }
+
+ // Going through the coordinate table as well
+ if (_frameCoords) {
+ for (uint32 i = 0; i < _frameCount; i++) {
+ if (_frameCoords[i].left != -1) {
+ if (x != 0xFFFF) {
+ _frameCoords[i].left = _frameCoords[i].left - _x + x;
+ _frameCoords[i].right = _frameCoords[i].right - _x + x;
+ }
+ if (y != 0xFFFF) {
+ _frameCoords[i].top = _frameCoords[i].top - _y + y;
+ _frameCoords[i].bottom = _frameCoords[i].bottom - _y + y;
+ }
+ }
+ }
+ }
+
+ if (x != 0xFFFF)
+ _x = x;
+ if (y != 0xFFFF)
+ _y = y;
+}
+
bool IMDDecoder::load(Common::SeekableReadStream &stream) {
close();
@@ -559,7 +823,7 @@ bool IMDDecoder::loadCoordinates() {
if (_version >= 3) {
uint16 count = _stream->readUint16LE();
if (count > 1) {
- warning("IMD: More than one standard coordinate quad found (%d)", count );
+ warning("IMDDecoder::loadCoordinates(): More than one standard coordinate quad found (%d)", count);
return false;
}
@@ -733,7 +997,6 @@ Surface *IMDDecoder::decodeNextFrame() {
createSurface();
processFrame();
- renderFrame();
if (_curFrame == 0)
_startTime = g_system->getMillis();
@@ -753,8 +1016,6 @@ void IMDDecoder::processFrame() {
bool startSound = false;
do {
- calcFrameCoords(_curFrame);
-
cmd = _stream->readUint16LE();
if ((cmd & kCommandBreakMask) == kCommandBreak) {
@@ -819,12 +1080,15 @@ void IMDDecoder::processFrame() {
}
} else if (cmd == kCommandVideoData) {
+ calcFrameCoords(_curFrame);
+
videoData(_stream->readUint32LE() + 2);
- } else if (cmd != 0)
+ } else if (cmd != 0) {
+ calcFrameCoords(_curFrame);
+
videoData(cmd + 2);
- else
- _dirtyRects.pop_back();
+ }
} while (hasNextCmd);
@@ -862,7 +1126,60 @@ void IMDDecoder::videoData(uint32 size) {
}
void IMDDecoder::renderFrame() {
- // TODO
+ if (_dirtyRects.empty())
+ return;
+
+ Common::Rect &rect = _dirtyRects.back();
+
+ rect.clip(Common::Rect(_x, _y, _x + _width, _y + _height));
+ if (!rect.isValidRect() || rect.isEmpty()) {
+ _dirtyRects.pop_back();
+ return;
+ }
+
+ byte *dataPtr = _frameData;
+
+ uint8 type = *dataPtr++;
+
+ if (type & 0x10) { // Palette data
+ // One byte index
+ int index = *dataPtr++;
+ // 16 entries with each 3 bytes (RGB)
+ memcpy(_palette + index * 3, dataPtr, MIN((255 - index) * 3, 48));
+
+ dataPtr += 48;
+ type ^= 0x10;
+
+ _paletteDirty = true;
+ }
+
+ if (type & 0x80) {
+ // Frame data is compressed
+
+ type &= 0x7F;
+
+ if ((type == 2) && (rect.width() == _surface.w)) {
+ // Directly uncompress onto the video surface
+ deLZ77((byte *) _surface.pixels, dataPtr);
+ return;
+ }
+
+ deLZ77(_videoBuffer, dataPtr);
+
+ dataPtr = _videoBuffer;
+ }
+
+ // Evaluate the block type
+ if (type == 0x01)
+ renderBlockSparse (dataPtr);
+ else if (type == 0x02)
+ renderBlockWhole (dataPtr);
+ else if (type == 0x42)
+ renderBlockWhole4X (dataPtr);
+ else if ((type & 0x0F) == 0x02)
+ renderBlockWhole2Y (dataPtr);
+ else
+ renderBlockSparse2Y(dataPtr);
}
void IMDDecoder::nextSoundSlice(bool hasNextCmd) {
diff --git a/graphics/video/coktel_decoder.h b/graphics/video/coktel_decoder.h
index 1736132cf4..0464b8e31b 100644
--- a/graphics/video/coktel_decoder.h
+++ b/graphics/video/coktel_decoder.h
@@ -84,6 +84,8 @@ public:
/** Return a list of rectangles that changed in the last frame. */
const Common::List<Common::Rect> &getDirtyRects() const;
+ bool hasPalette() const;
+
bool hasSound() const;
bool isSoundEnabled() const;
bool isSoundPlaying() const;
@@ -159,8 +161,17 @@ protected:
void createSurface();
void freeSurface();
+ void deLZ77(byte *dest, byte *src);
+
+ void renderBlockWhole (const byte *src);
+ void renderBlockWhole4X (const byte *src);
+ void renderBlockWhole2Y (const byte *src);
+ void renderBlockSparse (const byte *src);
+ void renderBlockSparse2Y(const byte *src);
+
inline void unsignedToSigned(byte *buffer, int length);
+public:
// FixedRateVideoDecoder interface
Common::Rational getFrameRate() const;
};
@@ -202,6 +213,8 @@ public:
bool seek(int32 frame, int whence = SEEK_SET, bool restart = false);
+ void setXY(uint16 x, uint16 y);
+
// VideoDecoder interface
bool load(Common::SeekableReadStream &stream);