mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-24 02:36:27 +00:00
967 lines
29 KiB
C++
967 lines
29 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* Low level graphics interface.
|
|
*/
|
|
|
|
#include "tinsel/graphics.h"
|
|
#include "tinsel/handle.h" // LockMem()
|
|
#include "tinsel/object.h"
|
|
#include "tinsel/palette.h"
|
|
#include "tinsel/scene.h"
|
|
#include "tinsel/tinsel.h"
|
|
#include "tinsel/scn.h"
|
|
|
|
#include "common/textconsole.h"
|
|
|
|
namespace Tinsel {
|
|
|
|
//----------------- LOCAL DEFINES --------------------
|
|
|
|
// Defines used in graphic drawing
|
|
#define CHARPTR_OFFSET 16
|
|
#define CHAR_WIDTH 4
|
|
#define CHAR_HEIGHT 4
|
|
|
|
extern uint8 g_transPalette[MAX_COLORS];
|
|
|
|
//----------------- SUPPORT FUNCTIONS ---------------------
|
|
|
|
/**
|
|
* PSX Block list unwinder.
|
|
* Chunk type 0x0003 (CHUNK_CHARPTR) in PSX version of DW 1 & 2 is compressed (original code
|
|
* calls the compression PJCRLE), thus we need to decompress it before passing data to drawing functions
|
|
*/
|
|
uint8* psxPJCRLEUnwinder(uint16 imageWidth, uint16 imageHeight, uint8 *srcIdx) {
|
|
uint32 remainingBlocks = 0;
|
|
|
|
uint16 compressionType = 0; // Kind of decompression to apply
|
|
uint16 controlBits = 0; // Control bits used to calculate the number of decompressed indexes
|
|
uint16 baseIndex = 0; // Base index to be repeated / incremented.
|
|
|
|
uint16 controlData;
|
|
|
|
uint8* dstIdx = NULL;
|
|
uint8* destinationBuffer = NULL;
|
|
|
|
if (!imageWidth || !imageHeight)
|
|
return NULL;
|
|
|
|
// Calculate needed index numbers, align width and height not next multiple of four
|
|
imageWidth = (imageWidth % 4) ? ((imageWidth / 4) + 1) * 4 : imageWidth;
|
|
imageHeight = (imageHeight % 4) ? ((imageHeight / 4) + 1) * 4 : imageHeight;
|
|
destinationBuffer = (uint8 *)malloc((imageWidth * imageHeight) / 8);
|
|
dstIdx = destinationBuffer;
|
|
remainingBlocks = (imageWidth * imageHeight) / 16;
|
|
|
|
while (remainingBlocks) { // Repeat until all blocks are decompressed
|
|
if (!controlBits) {
|
|
controlData = READ_16(srcIdx);
|
|
srcIdx += 2;
|
|
|
|
// If bit 15 of controlData is enabled, compression data is type 1.
|
|
// else if controlData has bit 16 enabled, compression type is 2,
|
|
// else there is no compression.
|
|
if (controlData & 0x4000)
|
|
compressionType = 2;
|
|
else if (controlData & 0x8000)
|
|
compressionType = 1;
|
|
else
|
|
compressionType = 0;
|
|
|
|
// Fetch control bits from controlData
|
|
controlBits = controlData & 0x3FFF;
|
|
|
|
// If there is compression, we need to fetch an index
|
|
// to be treated as "base" for compression.
|
|
if (compressionType != 0) {
|
|
controlData = READ_16(srcIdx);
|
|
srcIdx += 2;
|
|
baseIndex = controlData;
|
|
}
|
|
}
|
|
|
|
uint32 decremTiles; // Number of blocks that will be decompressed
|
|
if (remainingBlocks < controlBits) {
|
|
controlBits = controlBits - remainingBlocks;
|
|
decremTiles = remainingBlocks;
|
|
} else {
|
|
decremTiles = controlBits;
|
|
controlBits = 0;
|
|
}
|
|
|
|
// Decrement remaining blocks
|
|
remainingBlocks -= decremTiles;
|
|
|
|
// Manage different compressions
|
|
switch (compressionType) {
|
|
case 0: // No compression, plain copy of indexes
|
|
while (decremTiles) {
|
|
WRITE_LE_UINT16(dstIdx, READ_16(srcIdx));
|
|
srcIdx += 2;
|
|
dstIdx += 2;
|
|
decremTiles--;
|
|
}
|
|
break;
|
|
case 1: // Compression type 1, repeat a base index
|
|
while (decremTiles) {
|
|
WRITE_LE_UINT16(dstIdx, baseIndex);
|
|
dstIdx += 2;
|
|
decremTiles--;
|
|
}
|
|
break;
|
|
case 2: // Compression type 2, increment a base index
|
|
while (decremTiles) {
|
|
WRITE_LE_UINT16(dstIdx, baseIndex);
|
|
baseIndex++;
|
|
dstIdx += 2;
|
|
decremTiles--;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// End.
|
|
return destinationBuffer;
|
|
}
|
|
|
|
/**
|
|
* Straight rendering of uncompressed data
|
|
*/
|
|
static void t0WrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping) {
|
|
int yClip = 0;
|
|
|
|
if (applyClipping) {
|
|
// Adjust the height down to skip any bottom clipping
|
|
pObj->height -= pObj->botClip;
|
|
yClip = pObj->topClip;
|
|
}
|
|
|
|
// Vertical loop
|
|
for (int y = 0; y < pObj->height; ++y) {
|
|
// Get the start of the next line output
|
|
uint8 *tempDest = destP;
|
|
|
|
int leftClip = applyClipping ? pObj->leftClip : 0;
|
|
int rightClip = applyClipping ? pObj->rightClip : 0;
|
|
|
|
// Horizontal loop
|
|
for (int x = 0; x < pObj->width; ) {
|
|
uint32 numBytes = READ_UINT32(srcP);
|
|
srcP += sizeof(uint32);
|
|
bool repeatFlag = (numBytes & 0x80000000L) != 0;
|
|
numBytes &= 0x7fffffff;
|
|
|
|
uint clipAmount = MIN((int)numBytes & 0xff, leftClip);
|
|
leftClip -= clipAmount;
|
|
x += clipAmount;
|
|
|
|
if (repeatFlag) {
|
|
// Repeat of a given color
|
|
uint8 color = (numBytes >> 8) & 0xff;
|
|
int runLength = (numBytes & 0xff) - clipAmount;
|
|
|
|
int rptLength = MAX(MIN(runLength, pObj->width - rightClip - x), 0);
|
|
if (yClip == 0) {
|
|
if (color != 0)
|
|
memset(tempDest, color, rptLength);
|
|
tempDest += rptLength;
|
|
}
|
|
|
|
x += runLength;
|
|
} else {
|
|
// Copy a specified sequence length of pixels
|
|
srcP += clipAmount;
|
|
|
|
int runLength = numBytes - clipAmount;
|
|
int rptLength = MAX(MIN(runLength, pObj->width - rightClip - x), 0);
|
|
if (yClip == 0) {
|
|
memmove(tempDest, srcP, rptLength);
|
|
tempDest += rptLength;
|
|
}
|
|
|
|
int overflow = (numBytes % 4) == 0 ? 0 : 4 - (numBytes % 4);
|
|
x += runLength;
|
|
srcP += runLength + overflow;
|
|
}
|
|
}
|
|
|
|
// Move to next line
|
|
if (yClip > 0)
|
|
--yClip;
|
|
else
|
|
destP += SCREEN_WIDTH;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Straight rendering with transparency support, Mac variant
|
|
*/
|
|
static void MacDrawTiles(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping) {
|
|
int yClip = 0;
|
|
|
|
if (applyClipping) {
|
|
// Adjust the height down to skip any bottom clipping
|
|
pObj->height -= pObj->botClip;
|
|
yClip = pObj->topClip;
|
|
}
|
|
|
|
// Simple RLE-like scheme: the two first bytes of each data chunk determine
|
|
// if bytes should be repeated or copied.
|
|
// Example: 10 00 00 20 will repeat byte 0x0 0x10 times, and will copy 0x20
|
|
// bytes from the input stream afterwards
|
|
|
|
// Vertical loop
|
|
for (int y = 0; y < pObj->height; ++y) {
|
|
// Get the start of the next line output
|
|
uint8 *tempDest = destP;
|
|
|
|
int leftClip = applyClipping ? pObj->leftClip : 0;
|
|
int rightClip = applyClipping ? pObj->rightClip : 0;
|
|
|
|
// Horizontal loop
|
|
for (int x = 0; x < pObj->width; ) {
|
|
byte repeatBytes = *srcP++;
|
|
|
|
if (repeatBytes) {
|
|
uint clipAmount = MIN<int>(repeatBytes, leftClip);
|
|
leftClip -= clipAmount;
|
|
x += clipAmount;
|
|
|
|
// Repeat of a given color
|
|
byte color = *srcP++;
|
|
int runLength = repeatBytes - clipAmount;
|
|
int rptLength = MAX(MIN(runLength, pObj->width - rightClip - x), 0);
|
|
if (yClip == 0) {
|
|
if (color != 0)
|
|
memset(tempDest, color, rptLength);
|
|
tempDest += rptLength;
|
|
}
|
|
|
|
x += runLength;
|
|
} else {
|
|
// Copy a specified sequence length of pixels
|
|
byte copyBytes = *srcP++;
|
|
|
|
uint clipAmount = MIN<int>(copyBytes, leftClip);
|
|
leftClip -= clipAmount;
|
|
x += clipAmount;
|
|
srcP += clipAmount;
|
|
|
|
int runLength = copyBytes - clipAmount;
|
|
int rptLength = MAX(MIN(runLength, pObj->width - rightClip - x), 0);
|
|
if (yClip == 0) {
|
|
memmove(tempDest, srcP, rptLength);
|
|
tempDest += rptLength;
|
|
}
|
|
|
|
int overflow = (copyBytes & 1);
|
|
x += runLength;
|
|
srcP += runLength + overflow;
|
|
}
|
|
} // horizontal loop
|
|
|
|
// Move to next line
|
|
if (yClip > 0)
|
|
--yClip;
|
|
else
|
|
destP += SCREEN_WIDTH;
|
|
} // vertical loop
|
|
}
|
|
|
|
|
|
/**
|
|
* Straight rendering with transparency support, PSX variant supporting also 4-BIT clut data
|
|
*/
|
|
static void PsxDrawTiles(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping, bool fourBitClut, uint32 psxSkipBytes, byte *psxMapperTable, bool transparency) {
|
|
// Set up the offset between destination blocks
|
|
int rightClip = applyClipping ? pObj->rightClip : 0;
|
|
Common::Rect boxBounds;
|
|
|
|
if (applyClipping) {
|
|
// Adjust the height down to skip any bottom clipping
|
|
pObj->height -= pObj->botClip;
|
|
|
|
// Make adjustment for the top clipping row
|
|
srcP += sizeof(uint16) * ((pObj->width + 3) >> 2) * (pObj->topClip >> 2);
|
|
pObj->height -= pObj->topClip;
|
|
pObj->topClip %= 4;
|
|
}
|
|
|
|
// Vertical loop
|
|
while (pObj->height > 0) {
|
|
// Get the start of the next line output
|
|
uint8 *tempDest = destP;
|
|
|
|
// Get the line width, and figure out which row range within the 4 row high blocks
|
|
// will be displayed if clipping is to be taken into account
|
|
int width = pObj->width;
|
|
|
|
if (!applyClipping) {
|
|
// No clipping, so so set box bounding area for drawing full 4x4 pixel blocks
|
|
boxBounds.top = 0;
|
|
boxBounds.bottom = 3;
|
|
boxBounds.left = 0;
|
|
} else {
|
|
// Handle any possible clipping at the top of the char block.
|
|
// We already handled topClip partially at the beginning of this function.
|
|
// Hence the only non-zero values it can assume at this point are 1,2,3,
|
|
// and that only during the very first iteration (i.e. when the top char
|
|
// block is drawn only partially). In particular, we set topClip to zero,
|
|
// as all following blocks are not to be top clipped.
|
|
boxBounds.top = pObj->topClip;
|
|
pObj->topClip = 0;
|
|
|
|
boxBounds.bottom = MIN(boxBounds.top + pObj->height - 1, 3);
|
|
|
|
// Handle any possible clipping at the start of the line
|
|
boxBounds.left = pObj->leftClip;
|
|
if (boxBounds.left >= 4) {
|
|
srcP += sizeof(uint16) * (boxBounds.left >> 2);
|
|
width -= boxBounds.left & 0xfffc;
|
|
boxBounds.left %= 4;
|
|
}
|
|
|
|
width -= boxBounds.left;
|
|
}
|
|
|
|
// Horizontal loop
|
|
while (width > rightClip) {
|
|
boxBounds.right = MIN(boxBounds.left + width - rightClip - 1, 3);
|
|
assert(boxBounds.bottom >= boxBounds.top);
|
|
assert(boxBounds.right >= boxBounds.left);
|
|
|
|
int16 indexVal = READ_LE_UINT16(srcP);
|
|
srcP += sizeof(uint16);
|
|
|
|
// Draw a 4x4 block based on the opcode as in index into the block list
|
|
// In case we have a 4-bit CLUT image, blocks are 2x4 bytes, then expanded into 4x4
|
|
const uint8 *p;
|
|
if (fourBitClut)
|
|
p = (uint8 *)pObj->charBase + psxSkipBytes + (indexVal << 3);
|
|
else
|
|
p = (uint8 *)pObj->charBase + psxSkipBytes + (indexVal << 4);
|
|
|
|
p += boxBounds.top * (fourBitClut ? sizeof(uint16) : sizeof(uint32));
|
|
for (int yp = boxBounds.top; yp <= boxBounds.bottom; ++yp, p += (fourBitClut ? sizeof(uint16) : sizeof(uint32))) {
|
|
if (!fourBitClut) {
|
|
if (!transparency)
|
|
Common::copy(p + boxBounds.left, p + boxBounds.right + 1, tempDest + (SCREEN_WIDTH * (yp - boxBounds.top)));
|
|
else
|
|
for (int xp = boxBounds.left; xp <= boxBounds.right; ++xp) {
|
|
if (*(p + xp))
|
|
*(tempDest + SCREEN_WIDTH * (yp - boxBounds.top) + (xp - boxBounds.left)) = *(p + xp);
|
|
}
|
|
} else {
|
|
for (int xp = boxBounds.left; xp <= boxBounds.right; ++xp) {
|
|
// Extract pixel value from byte
|
|
byte pixValue = (*(p + (xp / 2)) & ((xp % 2) ? 0xf0 : 0x0f)) >> ((xp % 2) ? 4 : 0);
|
|
if (pixValue || !transparency)
|
|
*(tempDest + SCREEN_WIDTH * (yp - boxBounds.top) + (xp - boxBounds.left)) = psxMapperTable[pixValue];
|
|
}
|
|
}
|
|
}
|
|
|
|
tempDest += boxBounds.right - boxBounds.left + 1;
|
|
width -= 3 - boxBounds.left + 1;
|
|
|
|
// None of the remaining horizontal blocks should be left clipped
|
|
boxBounds.left = 0;
|
|
}
|
|
|
|
// If there is any width remaining, there must be a right edge clipping
|
|
if (width >= 0)
|
|
srcP += sizeof(uint16) * ((width + 3) >> 2);
|
|
|
|
// Move to next line line
|
|
pObj->height -= boxBounds.bottom - boxBounds.top + 1;
|
|
destP += (boxBounds.bottom - boxBounds.top + 1) * SCREEN_WIDTH;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Straight rendering with transparency support
|
|
*/
|
|
static void WrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping) {
|
|
// Set up the offset between destination blocks
|
|
int rightClip = applyClipping ? pObj->rightClip : 0;
|
|
Common::Rect boxBounds;
|
|
|
|
if (applyClipping) {
|
|
// Adjust the height down to skip any bottom clipping
|
|
pObj->height -= pObj->botClip;
|
|
|
|
// Make adjustment for the top clipping row
|
|
srcP += sizeof(uint16) * ((pObj->width + 3) >> 2) * (pObj->topClip >> 2);
|
|
pObj->height -= pObj->topClip;
|
|
pObj->topClip %= 4;
|
|
}
|
|
|
|
// Vertical loop
|
|
while (pObj->height > 0) {
|
|
// Get the start of the next line output
|
|
uint8 *tempDest = destP;
|
|
|
|
// Get the line width, and figure out which row range within the 4 row high blocks
|
|
// will be displayed if clipping is to be taken into account
|
|
int width = pObj->width;
|
|
|
|
if (!applyClipping) {
|
|
// No clipping, so so set box bounding area for drawing full 4x4 pixel blocks
|
|
boxBounds.top = 0;
|
|
boxBounds.bottom = 3;
|
|
boxBounds.left = 0;
|
|
} else {
|
|
// Handle any possible clipping at the top of the char block.
|
|
// We already handled topClip partially at the beginning of this function.
|
|
// Hence the only non-zero values it can assume at this point are 1,2,3,
|
|
// and that only during the very first iteration (i.e. when the top char
|
|
// block is drawn only partially). In particular, we set topClip to zero,
|
|
// as all following blocks are not to be top clipped.
|
|
boxBounds.top = pObj->topClip;
|
|
pObj->topClip = 0;
|
|
|
|
boxBounds.bottom = MIN(boxBounds.top + pObj->height - 1, 3);
|
|
|
|
// Handle any possible clipping at the start of the line
|
|
boxBounds.left = pObj->leftClip;
|
|
if (boxBounds.left >= 4) {
|
|
srcP += sizeof(uint16) * (boxBounds.left >> 2);
|
|
width -= boxBounds.left & 0xfffc;
|
|
boxBounds.left %= 4;
|
|
}
|
|
|
|
width -= boxBounds.left;
|
|
}
|
|
|
|
// Horizontal loop
|
|
while (width > rightClip) {
|
|
boxBounds.right = MIN(boxBounds.left + width - rightClip - 1, 3);
|
|
assert(boxBounds.bottom >= boxBounds.top);
|
|
assert(boxBounds.right >= boxBounds.left);
|
|
|
|
int16 indexVal = READ_LE_UINT16(srcP);
|
|
srcP += sizeof(uint16);
|
|
|
|
if (indexVal >= 0) {
|
|
// Draw a 4x4 block based on the opcode as in index into the block list
|
|
const uint8 *p = (uint8 *)pObj->charBase + (indexVal << 4);
|
|
p += boxBounds.top * sizeof(uint32);
|
|
for (int yp = boxBounds.top; yp <= boxBounds.bottom; ++yp, p += sizeof(uint32)) {
|
|
Common::copy(p + boxBounds.left, p + boxBounds.right + 1, tempDest + (SCREEN_WIDTH * (yp - boxBounds.top)));
|
|
}
|
|
|
|
} else {
|
|
// Draw a 4x4 block with transparency support
|
|
indexVal &= 0x7fff;
|
|
|
|
// If index is zero, then skip drawing the block completely
|
|
if (indexVal > 0) {
|
|
// Use the index along with the object's translation offset
|
|
const uint8 *p = (uint8 *)pObj->charBase + ((pObj->transOffset + indexVal) << 4);
|
|
|
|
// Loop through each pixel - only draw a pixel if it's non-zero
|
|
p += boxBounds.top * sizeof(uint32);
|
|
for (int yp = boxBounds.top; yp <= boxBounds.bottom; ++yp) {
|
|
p += boxBounds.left;
|
|
for (int xp = boxBounds.left; xp <= boxBounds.right; ++xp, ++p) {
|
|
if (*p)
|
|
*(tempDest + SCREEN_WIDTH * (yp - boxBounds.top) + (xp - boxBounds.left)) = *p;
|
|
}
|
|
p += 3 - boxBounds.right;
|
|
}
|
|
}
|
|
}
|
|
|
|
tempDest += boxBounds.right - boxBounds.left + 1;
|
|
width -= 3 - boxBounds.left + 1;
|
|
|
|
// None of the remaining horizontal blocks should be left clipped
|
|
boxBounds.left = 0;
|
|
}
|
|
|
|
// If there is any width remaining, there must be a right edge clipping
|
|
if (width >= 0)
|
|
srcP += sizeof(uint16) * ((width + 3) >> 2);
|
|
|
|
// Move to next line line
|
|
pObj->height -= boxBounds.bottom - boxBounds.top + 1;
|
|
destP += (boxBounds.bottom - boxBounds.top + 1) * SCREEN_WIDTH;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tinsel 2 Straight rendering with transparency support
|
|
*/
|
|
static void t2WrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping, bool horizFlipped) {
|
|
// Setup for correct clipping of object edges
|
|
int yClip = applyClipping ? pObj->topClip : 0;
|
|
if (applyClipping)
|
|
pObj->height -= pObj->botClip;
|
|
int numBytes;
|
|
int clipAmount;
|
|
|
|
// WORKAROUND: One of the mortician frames has several corrupt bytes in the Russian version
|
|
if ((pObj->hBits == 2517583660UL) && (_vm->getLanguage() == Common::RU_RUS)) {
|
|
uint8 correctBytes[5] = {0xA3, 0x00, 0x89, 0xC0, 0xA6};
|
|
Common::copy(&correctBytes[0], &correctBytes[5], srcP);
|
|
}
|
|
|
|
for (int y = 0; y < pObj->height; ++y) {
|
|
// Get the position to start writing out from
|
|
uint8 *tempP = !horizFlipped ? destP :
|
|
destP + (pObj->width - pObj->leftClip - pObj->rightClip) - 1;
|
|
int leftClip = applyClipping ? pObj->leftClip : 0;
|
|
int rightClip = applyClipping ? pObj->rightClip : 0;
|
|
if (horizFlipped)
|
|
SWAP(leftClip, rightClip);
|
|
|
|
int x = 0;
|
|
while (x < pObj->width) {
|
|
// Get the next opcode
|
|
numBytes = *srcP++;
|
|
if (numBytes & 0x80) {
|
|
// Run length following
|
|
numBytes &= 0x7f;
|
|
clipAmount = MIN(numBytes, leftClip);
|
|
leftClip -= clipAmount;
|
|
x+= clipAmount;
|
|
|
|
int runLength = numBytes - clipAmount;
|
|
uint8 color = *srcP++;
|
|
|
|
if ((yClip == 0) && (runLength > 0) && (color != 0)) {
|
|
runLength = MIN(runLength, pObj->width - rightClip - x);
|
|
|
|
if (runLength > 0) {
|
|
// Non-transparent run length
|
|
color += pObj->constant;
|
|
if (horizFlipped)
|
|
Common::fill(tempP - runLength + 1, tempP + 1, color);
|
|
else
|
|
Common::fill(tempP, tempP + runLength, color);
|
|
}
|
|
}
|
|
|
|
if (horizFlipped)
|
|
tempP -= runLength;
|
|
else
|
|
tempP += runLength;
|
|
|
|
x += numBytes - clipAmount;
|
|
|
|
} else {
|
|
// Dump a length of pixels
|
|
clipAmount = MIN(numBytes, leftClip);
|
|
leftClip -= clipAmount;
|
|
srcP += clipAmount;
|
|
int runLength = numBytes - clipAmount;
|
|
x += numBytes - runLength;
|
|
|
|
for (int xp = 0; xp < runLength; ++xp) {
|
|
if ((yClip > 0) || (x >= (pObj->width - rightClip)))
|
|
++srcP;
|
|
else if (horizFlipped)
|
|
*tempP-- = pObj->constant + *srcP++;
|
|
else
|
|
*tempP++ = pObj->constant + *srcP++;
|
|
++x;
|
|
}
|
|
}
|
|
}
|
|
assert(x == pObj->width);
|
|
|
|
if (yClip > 0)
|
|
--yClip;
|
|
else
|
|
destP += SCREEN_WIDTH;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fill the destination area with a constant color
|
|
*/
|
|
static void WrtConst(DRAWOBJECT *pObj, uint8 *destP, bool applyClipping) {
|
|
if (applyClipping) {
|
|
pObj->height -= pObj->topClip + pObj->botClip;
|
|
pObj->width -= pObj->leftClip + pObj->rightClip;
|
|
|
|
if (pObj->width <= 0)
|
|
return;
|
|
}
|
|
|
|
// Loop through any remaining lines
|
|
while (pObj->height > 0) {
|
|
Common::fill(destP, destP + pObj->width, pObj->constant);
|
|
|
|
--pObj->height;
|
|
destP += SCREEN_WIDTH;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Translates the destination surface within the object's bounds using the transparency
|
|
* lookup table from transpal.cpp (the contents of which have been moved into palette.cpp)
|
|
*/
|
|
static void WrtTrans(DRAWOBJECT *pObj, uint8 *destP, bool applyClipping) {
|
|
if (applyClipping) {
|
|
pObj->height -= pObj->topClip + pObj->botClip;
|
|
pObj->width -= pObj->leftClip + pObj->rightClip;
|
|
|
|
if (pObj->width <= 0)
|
|
return;
|
|
}
|
|
|
|
// Set up the offset between destination lines
|
|
int lineOffset = SCREEN_WIDTH - pObj->width;
|
|
|
|
// Loop through any remaining lines
|
|
while (pObj->height > 0) {
|
|
for (int i = 0; i < pObj->width; ++i, ++destP)
|
|
*destP = g_transPalette[*destP];
|
|
|
|
--pObj->height;
|
|
destP += lineOffset;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Copies an uncompressed block of data straight to the screen
|
|
*/
|
|
static void WrtAll(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping) {
|
|
int objWidth = pObj->width;
|
|
|
|
if (applyClipping) {
|
|
srcP += (pObj->topClip * pObj->width) + pObj->leftClip;
|
|
|
|
pObj->height -= pObj->topClip + pObj->botClip;
|
|
pObj->width -= pObj->leftClip + pObj->rightClip;
|
|
|
|
if (pObj->width <= 0)
|
|
return;
|
|
}
|
|
|
|
for (int y = 0; y < pObj->height; ++y) {
|
|
Common::copy(srcP, srcP + pObj->width, destP);
|
|
srcP += objWidth;
|
|
destP += SCREEN_WIDTH;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Renders a packed data stream with a variable sized palette
|
|
*/
|
|
static void PackedWrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP,
|
|
bool applyClipping, bool horizFlipped, int packingType) {
|
|
uint8 numColors = 0;
|
|
uint8 *colorTable = NULL;
|
|
int topClip = 0;
|
|
int xOffset = 0;
|
|
int numBytes, color;
|
|
int v;
|
|
|
|
if (_vm->getLanguage() == Common::RU_RUS) {
|
|
// WORKAROUND: One of the mortician frames has several corrupt bytes in the Russian version
|
|
if (pObj->hBits == 2517583393UL) {
|
|
uint8 correctBytes[5] = {0x00, 0x00, 0x17, 0x01, 0x00};
|
|
Common::copy(&correctBytes[0], &correctBytes[5], srcP + 267);
|
|
}
|
|
// WORKAROUND: One of Dibbler's frames in the end sequence has corrupt bytes in the Russian version
|
|
if (pObj->hBits == 33651742) {
|
|
uint8 correctBytes[40] = {
|
|
0x06, 0xc0, 0xd6, 0xc1, 0x09, 0xce, 0x0d, 0x24, 0x02, 0x12, 0x01, 0x00, 0x00, 0x23, 0x21, 0x32,
|
|
0x12, 0x00, 0x00, 0x20, 0x01, 0x11, 0x32, 0x12, 0x01, 0x00, 0x00, 0x1b, 0x02, 0x11, 0x34, 0x11,
|
|
0x00, 0x00, 0x18, 0x01, 0x11, 0x35, 0x21, 0x01
|
|
};
|
|
Common::copy(&correctBytes[0], &correctBytes[40], srcP);
|
|
}
|
|
}
|
|
|
|
if (applyClipping) {
|
|
pObj->height -= pObj->botClip;
|
|
topClip = pObj->topClip;
|
|
}
|
|
|
|
if (packingType == 3) {
|
|
// Variable colors
|
|
numColors = *srcP++;
|
|
colorTable = srcP;
|
|
srcP += numColors;
|
|
}
|
|
|
|
for (int y = 0; y < pObj->height; ++y) {
|
|
// Get the position to start writing out from
|
|
uint8 *tempP = !horizFlipped ? destP :
|
|
destP + (pObj->width - pObj->leftClip - pObj->rightClip) - 1;
|
|
int leftClip = applyClipping ? pObj->leftClip : 0;
|
|
int rightClip = applyClipping ? pObj->rightClip : 0;
|
|
if (horizFlipped)
|
|
SWAP(leftClip, rightClip);
|
|
bool eolFlag = false;
|
|
|
|
// Get offset for first pixels in next line
|
|
xOffset = *srcP++;
|
|
|
|
int x = 0;
|
|
while (x < pObj->width) {
|
|
// Get next run size and color to use
|
|
for (;;) {
|
|
if (xOffset > 0) {
|
|
x += xOffset;
|
|
|
|
// Reduce offset amount by any remaining left clipping
|
|
v = MIN(xOffset, leftClip);
|
|
xOffset -= v;
|
|
leftClip -= v;
|
|
|
|
if (horizFlipped) tempP -= xOffset; else tempP += xOffset;
|
|
xOffset = 0;
|
|
}
|
|
|
|
v = *srcP++;
|
|
numBytes = v & 0xf; // No. bytes 1-15
|
|
if (packingType == 3)
|
|
color = colorTable[v >> 4];
|
|
else
|
|
color = pObj->baseCol + (v >> 4);
|
|
|
|
if (numBytes != 0)
|
|
break;
|
|
|
|
numBytes = *srcP++;
|
|
if (numBytes >= 16)
|
|
break;
|
|
|
|
xOffset = numBytes + v;
|
|
if (xOffset == 0) {
|
|
// End of line encountered
|
|
eolFlag = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (eolFlag)
|
|
break;
|
|
|
|
// Apply clipping on byte sequence
|
|
v = MIN(numBytes, leftClip);
|
|
leftClip -= v;
|
|
numBytes -= v;
|
|
x += v;
|
|
|
|
while (numBytes-- > 0) {
|
|
if ((topClip == 0) && (x < (pObj->width - rightClip))) {
|
|
*tempP = color;
|
|
if (horizFlipped) --tempP; else ++tempP;
|
|
}
|
|
++x;
|
|
}
|
|
}
|
|
assert(x <= pObj->width);
|
|
|
|
if (!eolFlag) {
|
|
// Assert that the next bytes signal a line end
|
|
uint8 d = *srcP++;
|
|
assert((d & 0xf) == 0);
|
|
d = *srcP++;
|
|
assert(d == 0);
|
|
}
|
|
|
|
if (topClip > 0)
|
|
--topClip;
|
|
else
|
|
destP += SCREEN_WIDTH;
|
|
}
|
|
}
|
|
|
|
//----------------- MAIN FUNCTIONS ---------------------
|
|
|
|
/**
|
|
* Clears both the screen surface buffer and screen to the specified value
|
|
*/
|
|
void ClearScreen() {
|
|
byte blackColorIndex = (!TinselV1Mac) ? 0 : 255;
|
|
void *pDest = _vm->screen().getPixels();
|
|
memset(pDest, blackColorIndex, SCREEN_WIDTH * SCREEN_HEIGHT);
|
|
g_system->fillScreen(blackColorIndex);
|
|
g_system->updateScreen();
|
|
}
|
|
|
|
/**
|
|
* Updates the screen surface within the following rectangle
|
|
*/
|
|
void UpdateScreenRect(const Common::Rect &pClip) {
|
|
int yOffset = TinselV2 ? (g_system->getHeight() - SCREEN_HEIGHT) / 2 : 0;
|
|
byte *pSrc = (byte *)_vm->screen().getBasePtr(pClip.left, pClip.top);
|
|
g_system->copyRectToScreen(pSrc, _vm->screen().pitch, pClip.left, pClip.top + yOffset,
|
|
pClip.width(), pClip.height());
|
|
}
|
|
|
|
/**
|
|
* Draws the specified object onto the screen surface buffer
|
|
*/
|
|
void DrawObject(DRAWOBJECT *pObj) {
|
|
uint8 *srcPtr = NULL;
|
|
uint8 *destPtr;
|
|
byte psxMapperTable[16];
|
|
|
|
bool psxFourBitClut = false; // Used by Tinsel PSX, true if an image using a 4bit CLUT is rendered
|
|
bool psxRLEindex = false; // Used by Tinsel PSX, true if an image is using PJCRLE compressed indexes
|
|
uint32 psxSkipBytes = 0; // Used by Tinsel PSX, number of bytes to skip before counting indexes for image tiles
|
|
|
|
if ((pObj->width <= 0) || (pObj->height <= 0))
|
|
// Empty image, so return immediately
|
|
return;
|
|
|
|
// If writing constant data, don't bother locking the data pointer and reading src details
|
|
if ((pObj->flags & DMA_CONST) == 0) {
|
|
if (TinselV2) {
|
|
srcPtr = (byte *)LockMem(pObj->hBits);
|
|
pObj->charBase = NULL;
|
|
pObj->transOffset = 0;
|
|
} else {
|
|
byte *p = (byte *)LockMem(pObj->hBits & HANDLEMASK);
|
|
|
|
srcPtr = p + (pObj->hBits & OFFSETMASK);
|
|
pObj->charBase = (char *)p + READ_LE_UINT32(p + 0x10);
|
|
pObj->transOffset = READ_LE_UINT32(p + 0x14);
|
|
|
|
// Decompress block indexes for Discworld PSX
|
|
if (TinselV1PSX) {
|
|
uint8 paletteType = *(srcPtr); // if 0x88 we are using an 8bit palette type, if 0x44 we are using a 4 bit PSX CLUT
|
|
uint8 indexType = *(srcPtr + 1); // if 0xCC indexes for this image are compressed with PCJRLE, if 0xDD indexes are not compressed
|
|
|
|
switch (paletteType) {
|
|
case 0x88: // Normal 8-bit palette
|
|
psxFourBitClut = false;
|
|
psxSkipBytes = 0;
|
|
switch (indexType) {
|
|
case 0xDD: // Normal uncompressed indexes
|
|
psxRLEindex = false;
|
|
srcPtr += sizeof(uint16); // Get to the beginning of index data
|
|
break;
|
|
case 0xCC: // PJCRLE compressed indexes
|
|
psxRLEindex = true;
|
|
srcPtr = psxPJCRLEUnwinder(pObj->width, pObj->height, srcPtr + sizeof(uint16));
|
|
break;
|
|
default:
|
|
error("Unknown PSX index type 0x%.2X", indexType);
|
|
break;
|
|
}
|
|
break;
|
|
case 0x44: // PSX 4-bit CLUT
|
|
psxPaletteMapper(pObj->pPal, srcPtr + sizeof(uint16), psxMapperTable);
|
|
|
|
psxFourBitClut = true;
|
|
psxSkipBytes = READ_LE_UINT32(p + sizeof(uint32) * 5) << 4; // Fetch number of bytes we have to skip
|
|
switch (indexType) {
|
|
case 0xDD: // Normal uncompressed indexes
|
|
psxRLEindex = false;
|
|
srcPtr += sizeof(uint16) * 17; // Skip image type and clut, and get to beginning of index data
|
|
break;
|
|
case 0xCC: // PJCRLE compressed indexes
|
|
psxRLEindex = true;
|
|
srcPtr = psxPJCRLEUnwinder(pObj->width, pObj->height, srcPtr + sizeof(uint16) * 17);
|
|
break;
|
|
default:
|
|
error("Unknown PSX index type 0x%.2X", indexType);
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
error("Unknown PSX palette type 0x%.2X", paletteType);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// Get destination starting point
|
|
destPtr = (byte *)_vm->screen().getBasePtr(pObj->xPos, pObj->yPos);
|
|
|
|
// Handle various draw types
|
|
uint8 typeId = pObj->flags & 0xff;
|
|
int packType = pObj->flags >> 14; // TinselV2
|
|
|
|
if (TinselV2 && packType != 0) {
|
|
// Color packing for TinselV2
|
|
|
|
if (packType == 1)
|
|
pObj->baseCol = 0xF0; // 16 from 240
|
|
else if (packType == 2)
|
|
pObj->baseCol = 0xE0; // 16 from 224
|
|
// 3 = variable color
|
|
|
|
PackedWrtNonZero(pObj, srcPtr, destPtr, (pObj->flags & DMA_CLIP) != 0,
|
|
(pObj->flags & DMA_FLIPH), packType);
|
|
} else {
|
|
switch (typeId) {
|
|
case 0x01: // all versions, draw sprite without clipping
|
|
case 0x41: // all versions, draw sprite with clipping
|
|
case 0x02: // TinselV2, draw sprite without clipping
|
|
case 0x11: // TinselV2, draw sprite without clipping, flipped horizontally
|
|
case 0x42: // TinselV2, draw sprite with clipping
|
|
case 0x51: // TinselV2, draw sprite with clipping, flipped horizontally
|
|
case 0x81: // TinselV2, draw sprite with clipping
|
|
case 0xC1: // TinselV2, draw sprite with clipping
|
|
assert(TinselV2 || (typeId == 0x01 || typeId == 0x41));
|
|
|
|
if (TinselV2)
|
|
t2WrtNonZero(pObj, srcPtr, destPtr, typeId >= 0x40, (typeId & 0x10) != 0);
|
|
else if (TinselV1PSX)
|
|
PsxDrawTiles(pObj, srcPtr, destPtr, typeId == 0x41, psxFourBitClut, psxSkipBytes, psxMapperTable, true);
|
|
else if (TinselV1Mac)
|
|
MacDrawTiles(pObj, srcPtr, destPtr, typeId == 0x41);
|
|
else if (TinselV1)
|
|
WrtNonZero(pObj, srcPtr, destPtr, typeId == 0x41);
|
|
else if (TinselV0)
|
|
t0WrtNonZero(pObj, srcPtr, destPtr, typeId == 0x41);
|
|
break;
|
|
case 0x08: // draw background without clipping
|
|
case 0x48: // draw background with clipping
|
|
if (TinselV2 || TinselV1Mac || TinselV0)
|
|
WrtAll(pObj, srcPtr, destPtr, typeId == 0x48);
|
|
else if (TinselV1PSX)
|
|
PsxDrawTiles(pObj, srcPtr, destPtr, typeId == 0x48, psxFourBitClut, psxSkipBytes, psxMapperTable, false);
|
|
else if (TinselV1)
|
|
WrtNonZero(pObj, srcPtr, destPtr, typeId == 0x48);
|
|
break;
|
|
case 0x04: // fill with constant color without clipping
|
|
case 0x44: // fill with constant color with clipping
|
|
WrtConst(pObj, destPtr, typeId == 0x44);
|
|
break;
|
|
case 0x84: // draw transparent surface without clipping
|
|
case 0xC4: // draw transparent surface with clipping
|
|
WrtTrans(pObj, destPtr, typeId == 0xC4);
|
|
break;
|
|
default:
|
|
error("Unknown drawing type %d", typeId);
|
|
}
|
|
}
|
|
|
|
// If we were using Discworld PSX, free the memory allocated
|
|
// for decompressed block indexes.
|
|
if (TinselV1PSX && psxRLEindex)
|
|
free(srcPtr);
|
|
}
|
|
|
|
} // End of namespace Tinsel
|