aboutsummaryrefslogtreecommitdiff
path: root/engines/groovie
diff options
context:
space:
mode:
authorJordi Vilalta Prat2009-01-07 22:35:35 +0000
committerJordi Vilalta Prat2009-01-07 22:35:35 +0000
commitf07d962734e294a9536917710211fde04cff688c (patch)
tree3c4ab8ce415b7499dc5ce84a36ee70a25384be03 /engines/groovie
parent9752501a07bd6e295a71cd0db72f7a06f14d0fa6 (diff)
downloadscummvm-rg350-f07d962734e294a9536917710211fde04cff688c.tar.gz
scummvm-rg350-f07d962734e294a9536917710211fde04cff688c.tar.bz2
scummvm-rg350-f07d962734e294a9536917710211fde04cff688c.zip
Complete the ROQ video decoding (there are still a few minor glitches)
svn-id: r35778
Diffstat (limited to 'engines/groovie')
-rw-r--r--engines/groovie/roq.cpp482
-rw-r--r--engines/groovie/roq.h37
2 files changed, 399 insertions, 120 deletions
diff --git a/engines/groovie/roq.cpp b/engines/groovie/roq.cpp
index 0acf035b14..60f64f15a8 100644
--- a/engines/groovie/roq.cpp
+++ b/engines/groovie/roq.cpp
@@ -23,6 +23,9 @@
*
*/
+// ROQ video player based on this specification by Dr. Tim Ferguson:
+// http://www.csse.monash.edu.au/~timf/videocodec/idroq.txt
+
#include "groovie/groovie.h"
#include "groovie/roq.h"
@@ -31,10 +34,74 @@
namespace Groovie {
ROQPlayer::ROQPlayer(GroovieEngine *vm) :
- VideoPlayer(vm) {
+#ifdef DITHER
+ _dither(NULL),
+#endif
+ VideoPlayer(vm), _codingTypeCount(0) {
+
+ // Create the work surfaces
+ _currBuf = new Graphics::Surface();
+ _prevBuf = new Graphics::Surface();
+
+ byte pal[256 * 4];
+#ifdef DITHER
+ byte pal3[256 * 3];
+ // Initialize to a black palette
+ for (int i = 0; i < 256 * 3; i++) {
+ pal3[i] = 0;
+ }
+
+ // Build a basic color palette
+ for (int r = 0; r < 4; r++) {
+ for (int g = 0; g < 4; g++) {
+ for (int b = 0; b < 4; b++) {
+ byte col = (r << 4) | (g << 2) | (b << 0);
+ pal3[3 * col + 0] = r << 6;
+ pal3[3 * col + 1] = g << 6;
+ pal3[3 * col + 2] = b << 6;
+ }
+ }
+ }
+
+ // Initialize the dithering algorithm
+ _paletteLookup = new Graphics::PaletteLUT(8, Graphics::PaletteLUT::kPaletteYUV);
+ _paletteLookup->setPalette(pal3, Graphics::PaletteLUT::kPaletteRGB, 8);
+ for (int i = 0; (i < 64) && !_vm->shouldQuit(); i++) {
+ debug("Groovie::ROQ: Building palette table: %02d/63", i);
+ _paletteLookup->buildNext();
+ }
+
+ // Prepare the palette to show
+ for (int i = 0; i < 256; i++) {
+ pal[(i * 4) + 0] = pal3[(i * 3) + 0];
+ pal[(i * 4) + 1] = pal3[(i * 3) + 1];
+ pal[(i * 4) + 2] = pal3[(i * 3) + 2];
+ }
+#else
+ // Set a grayscale palette
+ for (int i = 0; i < 256; i++) {
+ pal[(i * 4) + 0] = i;
+ pal[(i * 4) + 1] = i;
+ pal[(i * 4) + 2] = i;
+ }
+#endif
+
+ _syst->setPalette(pal, 0, 256);
}
ROQPlayer::~ROQPlayer() {
+ // Free the buffers
+ _currBuf->free();
+ delete _currBuf;
+ _prevBuf->free();
+ delete _prevBuf;
+ _showBuf.free();
+
+#ifdef DITHER
+ // Free the dithering algorithm
+ delete _dither;
+ delete _paletteLookup;
+#endif
}
uint16 ROQPlayer::loadInternal() {
@@ -46,12 +113,57 @@ uint16 ROQPlayer::loadInternal() {
if (!readBlockHeader(blockHeader)) {
return 0;
}
- if (blockHeader.type != 0x1084 || blockHeader.size != 0 || blockHeader.param != 0) {
+
+ // Verify the file signature
+ if (blockHeader.type != 0x1084) {
return 0;
}
- // Hardcoded FPS
- return 25;
+ // Clear the dirty flag
+ _dirty = true;
+
+ if ((blockHeader.size == 0) && (blockHeader.param == 0)) {
+ // Set the offset scaling to 2
+ _offScale = 2;
+
+ // Hardcoded FPS
+ return 25;
+ } else if (blockHeader.size == (uint32)-1) {
+ // Set the offset scaling to 1
+ _offScale = 1;
+
+ // In this case the block parameter is the framerate
+ return blockHeader.param;
+ } else {
+ warning("Groovie::ROQ: Invalid header with size=%d and param=%d", blockHeader.size, blockHeader.param);
+ return 0;
+ }
+}
+
+void ROQPlayer::buildShowBuf() {
+#ifdef DITHER
+ // Start a new frame dithering
+ _dither->newFrame();
+#endif
+
+ for (int line = 0; line < _showBuf.h; line++) {
+ byte *out = (byte *)_showBuf.getBasePtr(0, line);
+ byte *in = (byte *)_prevBuf->getBasePtr(0, line / _scale);
+ for (int x = 0; x < _showBuf.w; x++) {
+#ifdef DITHER
+ *out = _dither->dither(*in, *(in + 1), *(in + 2), x);
+#else
+ // Just use the luminancy component
+ *out = *in;
+#endif
+ out++;
+ if (!(x % _scale))
+ in += _prevBuf->bytesPerPixel;
+ }
+#ifdef DITHER
+ _dither->nextLine();
+#endif
+ }
}
bool ROQPlayer::playFrameInternal() {
@@ -63,11 +175,22 @@ bool ROQPlayer::playFrameInternal() {
endframe = processBlock();
}
+ if (_dirty) {
+ // Build the show buffer from the previous (back) buffer
+ buildShowBuf();
+ }
+
// Wait until the current frame can be shown
waitFrame();
- // Update the screen
- _syst->updateScreen();
+ if (_dirty) {
+ // Update the screen
+ _syst->copyRectToScreen((byte *)_showBuf.getBasePtr(0, 0), _showBuf.w, 0, (_syst->getHeight() - _showBuf.h) / 2, _showBuf.pitch, _showBuf.h);
+ _syst->updateScreen();
+
+ // Clear the dirty flag
+ _dirty = false;
+ }
// Return whether the video has ended
return _file->eos();
@@ -96,6 +219,14 @@ bool ROQPlayer::processBlock() {
return true;
}
+ // Calculate where the block should end
+ int32 endpos = _file->pos() + blockHeader.size;
+
+ // Detect the end of the video
+ if (_file->eos()) {
+ return false;
+ }
+
bool ok = true;
bool endframe = false;
switch (blockHeader.type) {
@@ -105,16 +236,22 @@ bool ROQPlayer::processBlock() {
case 0x1002: // Quad codebook definition
ok = processBlockQuadCodebook(blockHeader);
break;
- case 0x1011: // Quad vector quantised video frame
+ case 0x1011: { // Quad vector quantised video frame
ok = processBlockQuadVector(blockHeader);
+ _dirty = true;
endframe = true;
+
+ // Swap buffers
+ Graphics::Surface *tmp = _prevBuf;
+ _prevBuf = _currBuf;
+ _currBuf = tmp;
break;
+ }
case 0x1012: // Still image (JPEG)
ok = processBlockStill(blockHeader);
- endframe = true;
break;
case 0x1013: // Hang
- //warning("Groovie::ROQ: Hang block (skipped)");
+ assert(blockHeader.size == 0 && blockHeader.param == 0);
break;
case 0x1020: // Mono sound samples
ok = processBlockSoundMono(blockHeader);
@@ -123,13 +260,18 @@ bool ROQPlayer::processBlock() {
ok = processBlockSoundStereo(blockHeader);
break;
case 0x1030: // Audio container
+ endpos = _file->pos();
ok = processBlockAudioContainer(blockHeader);
break;
default:
- error("Groovie::ROQ: Unknown block type: 0x%04X", blockHeader.type);
+ warning("Groovie::ROQ: Unknown block type: 0x%04X", blockHeader.type);
ok = false;
+ _file->skip(blockHeader.size);
}
+ if (endpos != _file->pos())
+ warning("Groovie::ROQ: BLOCK %04x Should have ended at %d, and has ended at %d", blockHeader.type, endpos, _file->pos());
+
// End the frame when the graphics have been modified or when there's an error
return endframe || !ok;
}
@@ -139,30 +281,42 @@ bool ROQPlayer::processBlockInfo(ROQBlockHeader &blockHeader) {
// Verify the block header
if (blockHeader.type != 0x1001 || blockHeader.size != 8 || blockHeader.param != 0) {
+ warning("Groovie::ROQ: BlockInfo size=%d param=%d", blockHeader.size, blockHeader.param);
return false;
}
- uint16 tmp;
- tmp = _file->readUint16LE();
- debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "w = %d\n", tmp);
- if (tmp != 640) {
+ // Read the information
+ uint16 width = _file->readUint16LE();
+ uint16 height = _file->readUint16LE();
+ uint16 unk1 = _file->readUint16LE();
+ uint16 unk2 = _file->readUint16LE();
+ if (unk1 != 8 || unk2 != 4) {
+ warning("Groovie::ROQ: unk1 = %d, unk2 = %d", unk1, unk2);
return false;
}
- tmp = _file->readUint16LE();
- debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "h = %d\n", tmp);
- if (tmp != 320) {
- return false;
- }
- tmp = _file->readUint16LE();
- debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "unk1 = %d\n", tmp);
- if (tmp != 8) {
- return false;
- }
- tmp = _file->readUint16LE();
- debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "unk2 = %d\n", tmp);
- if (tmp != 4) {
- return false;
+
+ // If the size of the image has changed, resize the buffers
+ if ((width != _currBuf->w) || (height != _currBuf->h)) {
+ // Calculate the maximum scale that fits the screen
+ _scale = MIN(_syst->getWidth() / width, _syst->getHeight() / height);
+
+ // Free the previous surfaces
+ _currBuf->free();
+ _prevBuf->free();
+ _showBuf.free();
+
+ // Allocate new buffers
+ _currBuf->create(width, height, 3);
+ _prevBuf->create(width, height, 3);
+ _showBuf.create(width * _scale, height * _scale, 1);
+
+#ifdef DITHER
+ // Reset the dithering algorithm with the new width
+ delete _dither;
+ _dither = new Graphics::SierraLight(width, _paletteLookup);
+#endif
}
+
return true;
}
@@ -177,122 +331,118 @@ bool ROQPlayer::processBlockQuadCodebook(ROQBlockHeader &blockHeader) {
// Get the number of 4x4 pixel blocks
_num4blocks = blockHeader.param & 0xFF;
- if (_num4blocks == 0 && (blockHeader.size > (uint32)_num2blocks * 6)) {
+ if ((_num4blocks == 0) && (blockHeader.size > (uint32)_num2blocks * 6)) {
_num4blocks = 256;
}
- _file->skip(_num2blocks * 6);
- _file->skip(_num4blocks * 4);
+ // Read the 2x2 codebook
+ for (int i = 0; i < _num2blocks; i++) {
+ // Read the 4 Y components and the subsampled Cb and Cr
+ _file->read(&_codebook2[i * 6], 6);
+ }
+
+ // Read the 4x4 codebook
+ _file->read(_codebook4, _num4blocks * 4);
return true;
}
bool ROQPlayer::processBlockQuadVector(ROQBlockHeader &blockHeader) {
debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::ROQ: Processing quad vector block");
- _file->skip(blockHeader.size);
- return true;
// Get the mean motion vectors
- //byte Mx = blockHeader.param >> 8;
- //byte My = blockHeader.param & 0xFF;
-
- int32 ends =_file->pos() + blockHeader.size;
- int numblocks = (640 / 8) * (320 / 8);
- for (int j = 0; j < numblocks && ends > _file->pos(); j++) {
- debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "doing block %d/%d\n", j, numblocks);
- uint16 codingType = _file->readUint16LE();
- for (int i = 0; i < 8; i++) {
- switch (codingType >> 14) {
- case 0: // MOT: Skip block
- //printf("coding type 0\n");
- break;
- case 1: { // FCC: Copy an existing block
- //printf("coding type 1\n");
- byte argument;
- argument = _file->readByte();
- //byte Dx = Mx + (argument >> 4);
- //byte Dy = My + (argument & 0x0F);
- // Dx = X + 8 - (argument >> 4) - Mx
- // Dy = Y + 8 - (argument & 0x0F) - My
- break;
- }
- case 2: { // SLD: Quad vector quantisation
- //printf("coding type 2\n");
- byte argument = _file->readByte();
- if (argument > _num4blocks) {
- //error("invalid 4x4 block %d of %d", argument, _num4blocks);
+ int8 Mx = blockHeader.param >> 8;
+ int8 My = blockHeader.param & 0xFF;
+
+ // Calculate where the block should end
+ int32 endpos =_file->pos() + blockHeader.size;
+
+ // Reset the coding types
+ _codingTypeCount = 0;
+
+ // Traverse the image in 16x16 macroblocks
+ for (int macroY = 0; macroY < _currBuf->h; macroY += 16) {
+ for (int macroX = 0; macroX < _currBuf->w; macroX += 16) {
+ // Traverse the macroblock in 8x8 blocks
+ for (int blockY = 0; blockY < 16; blockY += 8) {
+ for (int blockX = 0; blockX < 16; blockX += 8) {
+ processBlockQuadVectorBlock(macroX + blockX, macroY + blockY, Mx, My);
}
- // Upsample the 4x4 pixel block
- break;
- }
- case 3: // CCC:
- //printf("coding type 3:\n");
- processBlockQuadVectorSub(blockHeader);
- break;
}
- codingType <<= 2;
}
}
- debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Should have ended at %d, and has ended at %d\n", ends, _file->pos());
+
+ // HACK: Skip the remaining bytes
+ int32 skipBytes = endpos -_file->pos();
+ if (skipBytes > 0) {
+ _file->skip(skipBytes);
+ if (skipBytes != 2) {
+ warning("Groovie::ROQ: Skipped %d bytes", skipBytes);
+ }
+ }
return true;
}
-bool ROQPlayer::processBlockQuadVectorSub(ROQBlockHeader &blockHeader) {
- debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::ROQ: Processing quad vector sub block");
-
- // Get the mean motion vectors
- //byte Mx = blockHeader.param >> 8;
- //byte My = blockHeader.param & 0xFF;
-
- uint16 codingType = _file->readUint16LE();
- for (int i = 0; i < 4; i++) {
- switch (codingType >> 14) {
- case 0: // MOT: Skip block
- //printf("coding type 0\n");
- break;
- case 1: { // FCC: Copy an existing block
- //printf("coding type 1\n");
- byte argument;
- argument = _file->readByte();
- //byte Dx = Mx + (argument >> 4);
- //byte Dy = My + (argument & 0x0F);
- // Dx = X + 8 - (argument >> 4) - Mx
- // Dy = Y + 8 - (argument & 0x0F) - My
- break;
- }
- case 2: { // SLD: Quad vector quantisation
- //printf("coding type 2\n");
- byte argument = _file->readByte();
- if (argument > _num2blocks) {
- //error("invalid 2x2 block: %d of %d", argument, _num2blocks);
+void ROQPlayer::processBlockQuadVectorBlock(int baseX, int baseY, int8 Mx, int8 My) {
+ uint16 codingType = getCodingType();
+ switch (codingType) {
+ case 0: // MOT: Skip block
+ break;
+ case 1: { // FCC: Copy an existing block
+ byte argument = _file->readByte();
+ int16 DDx = 8 - (argument >> 4);
+ int16 DDy = 8 - (argument & 0x0F);
+ copy(8, baseX, baseY, DDx - Mx, DDy - My);
+ break;
+ }
+ case 2: // SLD: Quad vector quantisation
+ // Upsample the 4x4 pixel block
+ paint8(_file->readByte(), baseX, baseY);
+ break;
+ case 3: // CCC:
+ // Traverse the block in 4x4 sub-blocks
+ for (int subBlockY = 0; subBlockY < 8; subBlockY += 4) {
+ for (int subBlockX = 0; subBlockX < 8; subBlockX += 4) {
+ processBlockQuadVectorBlockSub(baseX + subBlockX, baseY + subBlockY, Mx, My);
}
- break;
- }
- case 3:
- //printf("coding type 3\n");
- _file->readByte();
- _file->readByte();
- _file->readByte();
- _file->readByte();
- break;
}
- codingType <<= 2;
+ break;
+ }
+}
+
+void ROQPlayer::processBlockQuadVectorBlockSub(int baseX, int baseY, int8 Mx, int8 My) {
+ debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::ROQ: Processing quad vector sub block");
+
+ uint16 codingType = getCodingType();
+ switch (codingType) {
+ case 0: // MOT: Skip block
+ break;
+ case 1: { // FCC: Copy an existing block
+ byte argument = _file->readByte();
+ int16 DDx = 8 - (argument >> 4);
+ int16 DDy = 8 - (argument & 0x0F);
+ copy(4, baseX, baseY, DDx - Mx, DDy - My);
+ break;
+ }
+ case 2: // SLD: Quad vector quantisation
+ paint4(_file->readByte(), baseX, baseY);
+ break;
+ case 3:
+ paint2(_file->readByte(), baseX , baseY);
+ paint2(_file->readByte(), baseX + 2, baseY);
+ paint2(_file->readByte(), baseX , baseY + 2);
+ paint2(_file->readByte(), baseX + 2, baseY + 2);
+ break;
}
- return true;
}
bool ROQPlayer::processBlockStill(ROQBlockHeader &blockHeader) {
debugC(5, kGroovieDebugVideo | kGroovieDebugAll, "Groovie::ROQ: Processing still (JPEG) block");
- //Common::ReadStream *jpegData = new Common::SubReadStream(_file, blockHeader.size);
- //Graphics::JPEG jpegFrame;
- //jpegFrame.read(jpegData);
- /*
- Common::File save;
- save.open("dump.jpg", Common::File::kFileWriteMode);
- save.write(data, blockHeader.size);
- save.close();
- */
- error("JPEG!");
+
+ warning("Groovie::ROQ: JPEG frame (unimplemented)");
+ memset(_prevBuf->getBasePtr(0, 0), 0, _prevBuf->w * _prevBuf->h * _prevBuf->bytesPerPixel);
+
+ _file->skip(blockHeader.size);
return true;
}
@@ -400,4 +550,98 @@ bool ROQPlayer::processBlockAudioContainer(ROQBlockHeader &blockHeader) {
return true;
}
+byte ROQPlayer::getCodingType() {
+ _codingType <<= 2;
+ if (!_codingTypeCount) {
+ _codingType = _file->readUint16LE();
+ _codingTypeCount = 8;
+ }
+
+ _codingTypeCount--;
+ return (_codingType >> 14);
+}
+
+void ROQPlayer::paint2(byte i, int destx, int desty) {
+ if (i > _num2blocks) {
+ error("Groovie::ROQ: Invalid 2x2 block %d (%d available)", i, _num2blocks);
+ }
+
+ byte *block = &_codebook2[i * 6];
+ byte u = block[4];
+ byte v = block[5];
+
+ byte *ptr = (byte *)_currBuf->getBasePtr(destx, desty);
+ for (int y = 0; y < 2; y++) {
+ for (int x = 0; x < 2; x++) {
+ *ptr = *block;
+ *(ptr + 1) = u;
+ *(ptr + 2) = v;
+ ptr += 3;
+ block++;
+ }
+ ptr += _currBuf->pitch - 6;
+ }
+}
+
+void ROQPlayer::paint4(byte i, int destx, int desty) {
+ if (i > _num4blocks) {
+ error("Groovie::ROQ: Invalid 4x4 block %d (%d available)", i, _num4blocks);
+ }
+
+ byte *block4 = &_codebook4[i * 4];
+ for (int origy = 0; origy < 4; origy += 2) {
+ for (int origx = 0; origx < 4; origx += 2) {
+ paint2(*block4, destx + origx, desty + origy);
+ block4++;
+ }
+ }
+}
+
+void ROQPlayer::paint8(byte i, int destx, int desty) {
+ if (i > _num4blocks) {
+ error("Groovie::ROQ: Invalid 4x4 block %d (%d available)", i, _num4blocks);
+ }
+
+ byte *block4 = &_codebook4[i * 4];
+ for (int y4 = 0; y4 < 2; y4++) {
+ for (int x4 = 0; x4 < 2; x4++) {
+ byte *block2 = &_codebook2[(*block4) * 6];
+ byte u = block2[4];
+ byte v = block2[5];
+ block4++;
+ for (int y2 = 0; y2 < 2; y2++) {
+ for (int x2 = 0; x2 < 2; x2++) {
+ for (int repy = 0; repy < 2; repy++) {
+ for (int repx = 0; repx < 2; repx++) {
+ byte *ptr = (byte *)_currBuf->getBasePtr(destx + x4*4 + x2*2 + repx, desty + y4*4 + y2*2 + repy);
+ *ptr = *block2;
+ *(ptr + 1) = u;
+ *(ptr + 2) = v;
+ }
+ }
+ block2++;
+ }
+ }
+ }
+ }
+}
+
+void ROQPlayer::copy(byte size, int destx, int desty, int offx, int offy) {
+ offx *= _offScale / _scale;
+ offy *= _offScale / _scale;
+
+ // Get the beginning of the first line
+ byte *dst = (byte *)_currBuf->getBasePtr(destx, desty);
+ byte *src = (byte *)_prevBuf->getBasePtr(destx + offx, desty + offy);
+
+ for (int i = 0; i < size; i++) {
+ // Copy the current line
+ memcpy(dst, src, size * _currBuf->bytesPerPixel);
+
+ // Move to the beginning of the next line
+ dst += _currBuf->pitch;
+ src += _currBuf->pitch;
+ }
+}
+
} // End of Groovie namespace
diff --git a/engines/groovie/roq.h b/engines/groovie/roq.h
index 215974e3bd..b96b195ae7 100644
--- a/engines/groovie/roq.h
+++ b/engines/groovie/roq.h
@@ -28,6 +28,12 @@
#include "groovie/player.h"
+//#define DITHER
+
+#ifdef DITHER
+#include "graphics/dither.h"
+#endif
+
namespace Groovie {
class GroovieEngine;
@@ -54,14 +60,43 @@ private:
bool processBlockInfo(ROQBlockHeader &blockHeader);
bool processBlockQuadCodebook(ROQBlockHeader &blockHeader);
bool processBlockQuadVector(ROQBlockHeader &blockHeader);
- bool processBlockQuadVectorSub(ROQBlockHeader &blockHeader);
+ void processBlockQuadVectorBlock(int baseX, int baseY, int8 Mx, int8 My);
+ void processBlockQuadVectorBlockSub(int baseX, int baseY, int8 Mx, int8 My);
bool processBlockStill(ROQBlockHeader &blockHeader);
bool processBlockSoundMono(ROQBlockHeader &blockHeader);
bool processBlockSoundStereo(ROQBlockHeader &blockHeader);
bool processBlockAudioContainer(ROQBlockHeader &blockHeader);
+ void paint2(byte i, int destx, int desty);
+ void paint4(byte i, int destx, int desty);
+ void paint8(byte i, int destx, int desty);
+ void copy(byte size, int destx, int desty, int offx, int offy);
+
+ // Block coding type
+ byte getCodingType();
+ uint16 _codingType;
+ byte _codingTypeCount;
+
+ // Codebooks
uint16 _num2blocks;
uint16 _num4blocks;
+ byte _codebook2[256 * 6];
+ byte _codebook4[256 * 4];
+
+ // Buffers
+ Graphics::Surface *_currBuf;
+ Graphics::Surface *_prevBuf;
+ Graphics::Surface _showBuf;
+ void buildShowBuf();
+ byte _scale;
+ byte _offScale;
+ bool _dirty;
+
+#ifdef DITHER
+ // Dithering
+ Graphics::PaletteLUT *_paletteLookup;
+ Graphics::SierraLight *_dither;
+#endif
};
} // End of Groovie namespace