mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-26 11:46:54 +00:00
1168 lines
28 KiB
C++
1168 lines
28 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.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* This code is based on original Mortville Manor DOS source code
|
|
* Copyright (c) 1987-1989 Lankhor
|
|
*/
|
|
|
|
#include "mortevielle/mortevielle.h"
|
|
#include "mortevielle/graphics.h"
|
|
#include "mortevielle/mouse.h"
|
|
|
|
#include "common/endian.h"
|
|
#include "common/system.h"
|
|
#include "graphics/palette.h"
|
|
|
|
namespace Mortevielle {
|
|
|
|
/*-------------------------------------------------------------------------*
|
|
* Palette Manager
|
|
*
|
|
*-------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Set palette entries from the 64 color available EGA palette
|
|
*/
|
|
void PaletteManager::setPalette(const int *palette, uint idx, uint size) {
|
|
assert((idx + size) <= 16);
|
|
|
|
// Build up the EGA palette
|
|
byte egaPalette[64 * 3];
|
|
|
|
byte *p = &egaPalette[0];
|
|
for (int i = 0; i < 64; ++i) {
|
|
*p++ = (i >> 2 & 1) * 0xaa + (i >> 5 & 1) * 0x55;
|
|
*p++ = (i >> 1 & 1) * 0xaa + (i >> 4 & 1) * 0x55;
|
|
*p++ = (i & 1) * 0xaa + (i >> 3 & 1) * 0x55;
|
|
}
|
|
|
|
// Loop through setting palette colors based on the passed indexes
|
|
for (; size > 0; --size, ++idx) {
|
|
int palIndex = palette[idx];
|
|
assert(palIndex < 64);
|
|
|
|
const byte *pRgb = (const byte *)&egaPalette[palIndex * 3];
|
|
g_system->getPaletteManager()->setPalette(pRgb, idx, 1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the default EGA palette
|
|
*/
|
|
void PaletteManager::setDefaultPalette() {
|
|
const int defaultPalette[16] = { 0, 1, 2, 3, 4, 5, 20, 7, 56, 57, 58, 59, 60, 61, 62, 63 };
|
|
setPalette(defaultPalette, 0, 16);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*
|
|
* Image decoding
|
|
*
|
|
* The code in this section is responsible for decoding image resources.
|
|
* Images are broken down into rectangular sections, which can use one
|
|
* of 18 different encoding methods.
|
|
*-------------------------------------------------------------------------*/
|
|
|
|
#define INCR_XSIZE { if (_xSize & 1) ++_xSize; }
|
|
#define DEFAULT_WIDTH (SCREEN_WIDTH / 2)
|
|
#define BUFFER_SIZE 40000
|
|
|
|
void GfxSurface::decode(const byte *pSrc) {
|
|
w = h = 0;
|
|
// If no transparency, use invalid (for EGA) palette index of 16. Otherwise get index to use
|
|
_transparency = (*pSrc == 0) ? 16 : *(pSrc + 2);
|
|
bool offsetFlag = *pSrc++ == 0;
|
|
int entryCount = *pSrc++;
|
|
pSrc += 2;
|
|
|
|
if (offsetFlag)
|
|
pSrc += 30;
|
|
|
|
// First run through the data to calculate starting offsets
|
|
const byte *p = pSrc;
|
|
_offset.x = _offset.y = 999;
|
|
|
|
assert(entryCount > 0);
|
|
for (int idx = 0; idx < entryCount; ++idx) {
|
|
_xp = READ_BE_UINT16(p + 4);
|
|
if (_xp < _offset.x)
|
|
_offset.x = _xp;
|
|
|
|
_yp = READ_BE_UINT16(p + 6);
|
|
if (_yp < _offset.y)
|
|
_offset.y = _yp;
|
|
|
|
// Move to next entry
|
|
int size = READ_BE_UINT16(p) + READ_BE_UINT16(p + 2);
|
|
if ((size % 2) == 1)
|
|
++size;
|
|
|
|
p += size + 14;
|
|
}
|
|
|
|
// Temporary output buffer
|
|
byte *outputBuffer = (byte *)malloc(sizeof(byte) * 65536);
|
|
memset(outputBuffer, _transparency, 65536);
|
|
|
|
byte *pDest = &outputBuffer[0];
|
|
const byte *pSrcStart = pSrc;
|
|
const byte *pLookup = NULL;
|
|
|
|
byte *lookupTable = (byte *)malloc(sizeof(byte) * BUFFER_SIZE);
|
|
byte *srcBuffer = (byte *)malloc(sizeof(byte) * BUFFER_SIZE);
|
|
|
|
// Main processing loop
|
|
for (int entryIndex = 0; entryIndex < entryCount; ++entryIndex) {
|
|
int lookupBytes = READ_BE_UINT16(pSrc);
|
|
int srcSize = READ_BE_UINT16(pSrc + 2);
|
|
_xp = READ_BE_UINT16(pSrc + 4) - _offset.x;
|
|
_yp = READ_BE_UINT16(pSrc + 6) - _offset.y;
|
|
assert((_xp >= 0) && (_yp >= 0) && (_xp < SCREEN_WIDTH) && (_yp < SCREEN_ORIG_HEIGHT));
|
|
pSrc += 8;
|
|
|
|
int decomCode = READ_BE_UINT16(pSrc);
|
|
_xSize = READ_BE_UINT16(pSrc + 2) + 1;
|
|
_ySize = READ_BE_UINT16(pSrc + 4) + 1;
|
|
majTtxTty();
|
|
|
|
pSrc += 6;
|
|
pDest = &outputBuffer[0];
|
|
|
|
_lookupIndex = 0;
|
|
_nibbleFlag = false;
|
|
|
|
int decomIndex = 0;
|
|
if (decomCode >> 8) {
|
|
// Build up reference table
|
|
int tableOffset = 0;
|
|
|
|
if (decomCode & 1) {
|
|
// Handle decompression of the pattern lookup table
|
|
do {
|
|
int outerCount = desanalyse(pSrc);
|
|
int innerCount = desanalyse(pSrc);
|
|
|
|
const byte *pSrcSaved = pSrc;
|
|
bool savedNibbleFlag = _nibbleFlag;
|
|
int savedLookupIndex = _lookupIndex;
|
|
|
|
do {
|
|
pSrc = pSrcSaved;
|
|
_nibbleFlag = savedNibbleFlag;
|
|
_lookupIndex = savedLookupIndex;
|
|
|
|
for (int idx = 0; idx < innerCount; ++idx, ++tableOffset) {
|
|
assert(tableOffset < BUFFER_SIZE);
|
|
lookupTable[tableOffset] = nextNibble(pSrc);
|
|
}
|
|
} while (--outerCount > 0);
|
|
} while (_lookupIndex < (lookupBytes - 1));
|
|
|
|
} else {
|
|
assert(lookupBytes < BUFFER_SIZE);
|
|
for (int idx = 0; idx < (lookupBytes * 2); ++idx)
|
|
lookupTable[idx] = nextNibble(pSrc);
|
|
}
|
|
|
|
if (_nibbleFlag) {
|
|
++pSrc;
|
|
_nibbleFlag = false;
|
|
}
|
|
if ((lookupBytes + srcSize) & 1)
|
|
++pSrc;
|
|
|
|
tableOffset = 0;
|
|
_lookupIndex = 0;
|
|
|
|
if (decomCode & 2) {
|
|
// Handle decompression of the temporary source buffer
|
|
do {
|
|
int outerCount = desanalyse(pSrc);
|
|
int innerCount = desanalyse(pSrc);
|
|
_lookupIndex += innerCount;
|
|
|
|
if (_nibbleFlag) {
|
|
++pSrc;
|
|
++_lookupIndex;
|
|
_nibbleFlag = false;
|
|
}
|
|
|
|
const byte *pStart = pSrc;
|
|
do {
|
|
pSrc = pStart;
|
|
for (int idx = 0; idx < innerCount; ++idx) {
|
|
assert(tableOffset < BUFFER_SIZE);
|
|
srcBuffer[tableOffset++] = *pSrc++;
|
|
}
|
|
} while (--outerCount > 0);
|
|
} while (_lookupIndex < (srcSize - 1));
|
|
} else {
|
|
assert(srcSize < BUFFER_SIZE);
|
|
for (int idx = 0; idx < srcSize; ++idx)
|
|
srcBuffer[idx] = *pSrc++;
|
|
}
|
|
|
|
if (_nibbleFlag)
|
|
++pSrc;
|
|
|
|
// Switch over to using the decompressed source and lookup buffers
|
|
pSrcStart = pSrc;
|
|
pDest = &outputBuffer[_yp * DEFAULT_WIDTH + _xp];
|
|
pSrc = &srcBuffer[0];
|
|
pLookup = &lookupTable[0] - 1;
|
|
|
|
_lookupValue = _lookupIndex = 0;
|
|
_nibbleFlag = false;
|
|
decomIndex = decomCode >> 8;
|
|
}
|
|
|
|
// Main decompression switch
|
|
switch (decomIndex) {
|
|
case 0:
|
|
// Draw rect at pos
|
|
pDest = &outputBuffer[_yp * DEFAULT_WIDTH + _xp];
|
|
pSrcStart = pSrc;
|
|
INCR_XSIZE;
|
|
|
|
for (int yCtr = 0; yCtr < _ySize; ++yCtr, pDest += DEFAULT_WIDTH) {
|
|
byte *pDestLine = pDest;
|
|
for (int xCtr = 0; xCtr < _xSize; ++xCtr) {
|
|
*pDestLine++ = nextNibble(pSrc);
|
|
}
|
|
}
|
|
|
|
pSrcStart += lookupBytes + ((lookupBytes & 1) ? 1 : 0);
|
|
break;
|
|
|
|
case 1:
|
|
// Draw rect using horizontal lines alternating left to right, then right to left
|
|
INCR_XSIZE;
|
|
for (int yCtr = 0; yCtr < _ySize; ++yCtr) {
|
|
if ((yCtr % 2) == 0) {
|
|
for (int xCtr = 0; xCtr < _xSize; ++xCtr) {
|
|
*pDest++ = nextByte(pSrc, pLookup);
|
|
}
|
|
} else {
|
|
for (int xCtr = 0; xCtr < _xSize; ++xCtr) {
|
|
*--pDest = nextByte(pSrc, pLookup);
|
|
}
|
|
}
|
|
pDest += DEFAULT_WIDTH;
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
// Draw rect alternating top to bottom, bottom to top
|
|
for (int xCtr = 0; xCtr < _xSize; ++xCtr) {
|
|
if ((xCtr % 2) == 0) {
|
|
for (int yCtr = 0; yCtr < _ySize; ++yCtr, pDest += DEFAULT_WIDTH) {
|
|
*pDest = nextByte(pSrc, pLookup);
|
|
}
|
|
} else {
|
|
for (int yCtr = 0; yCtr < _ySize; ++yCtr) {
|
|
pDest -= DEFAULT_WIDTH;
|
|
*pDest = nextByte(pSrc, pLookup);
|
|
}
|
|
}
|
|
++pDest;
|
|
}
|
|
break;
|
|
|
|
case 3:
|
|
// Draw horizontal area?
|
|
_thickness = 2;
|
|
horizontal(pSrc, pDest, pLookup);
|
|
break;
|
|
|
|
case 4:
|
|
// Draw vertical area?
|
|
_thickness = 2;
|
|
vertical(pSrc, pDest, pLookup);
|
|
break;
|
|
|
|
case 5:
|
|
_thickness = 3;
|
|
horizontal(pSrc, pDest, pLookup);
|
|
break;
|
|
|
|
case 6:
|
|
_thickness = 4;
|
|
vertical(pSrc, pDest, pLookup);
|
|
break;
|
|
|
|
case 7:
|
|
// Draw rect using horizontal lines left to right
|
|
INCR_XSIZE;
|
|
for (int yCtr = 0; yCtr < _ySize; ++yCtr, pDest += DEFAULT_WIDTH) {
|
|
byte *pDestLine = pDest;
|
|
for (int xCtr = 0; xCtr < _xSize; ++xCtr)
|
|
*pDestLine++ = nextByte(pSrc, pLookup);
|
|
}
|
|
break;
|
|
|
|
case 8:
|
|
// Draw box
|
|
for (int xCtr = 0; xCtr < _xSize; ++xCtr, ++pDest) {
|
|
byte *pDestLine = pDest;
|
|
for (int yCtr = 0; yCtr < _ySize; ++yCtr, pDestLine += DEFAULT_WIDTH)
|
|
*pDestLine = nextByte(pSrc, pLookup);
|
|
}
|
|
break;
|
|
|
|
case 9:
|
|
_thickness = 4;
|
|
horizontal(pSrc, pDest, pLookup);
|
|
break;
|
|
|
|
case 10:
|
|
_thickness = 6;
|
|
horizontal(pSrc, pDest, pLookup);
|
|
break;
|
|
|
|
case 11:
|
|
decom11(pSrc, pDest, pLookup);
|
|
break;
|
|
|
|
case 12:
|
|
INCR_XSIZE;
|
|
_thickness = _xInc = 1;
|
|
_yInc = DEFAULT_WIDTH;
|
|
_yEnd = _ySize;
|
|
_xEnd = _xSize;
|
|
diag(pSrc, pDest, pLookup);
|
|
break;
|
|
|
|
case 13:
|
|
INCR_XSIZE;
|
|
_thickness = _xSize;
|
|
_yInc = 1;
|
|
_yEnd = _xSize;
|
|
_xInc = DEFAULT_WIDTH;
|
|
_xEnd = _ySize;
|
|
diag(pSrc, pDest, pLookup);
|
|
break;
|
|
|
|
case 14:
|
|
_thickness = _yInc = 1;
|
|
_yEnd = _xSize;
|
|
_xInc = DEFAULT_WIDTH;
|
|
_xEnd = _ySize;
|
|
diag(pSrc, pDest, pLookup);
|
|
break;
|
|
|
|
case 15:
|
|
INCR_XSIZE;
|
|
_thickness = 2;
|
|
_yInc = DEFAULT_WIDTH;
|
|
_yEnd = _ySize;
|
|
_xInc = 1;
|
|
_xEnd = _xSize;
|
|
diag(pSrc, pDest, pLookup);
|
|
break;
|
|
|
|
case 16:
|
|
_thickness = 3;
|
|
_yInc = 1;
|
|
_yEnd = _xSize;
|
|
_xInc = DEFAULT_WIDTH;
|
|
_xEnd = _ySize;
|
|
diag(pSrc, pDest, pLookup);
|
|
break;
|
|
|
|
case 17:
|
|
INCR_XSIZE;
|
|
_thickness = 3;
|
|
_yInc = DEFAULT_WIDTH;
|
|
_yEnd = _ySize;
|
|
_xInc = 1;
|
|
_xEnd = _xSize;
|
|
diag(pSrc, pDest, pLookup);
|
|
break;
|
|
|
|
case 18:
|
|
INCR_XSIZE;
|
|
_thickness = 5;
|
|
_yInc = DEFAULT_WIDTH;
|
|
_yEnd = _ySize;
|
|
_xInc = 1;
|
|
_xEnd = _xSize;
|
|
diag(pSrc, pDest, pLookup);
|
|
break;
|
|
|
|
default:
|
|
error("Unknown decompression block type %d", decomIndex);
|
|
}
|
|
|
|
pSrc = pSrcStart;
|
|
debugC(2, kMortevielleGraphics, "Decoding image block %d position %d,%d size %d,%d method %d",
|
|
entryIndex + 1, _xp, _yp, w, h, decomIndex);
|
|
}
|
|
|
|
// At this point, the outputBuffer has the data for the image. Initialize the surface
|
|
// with the calculated size, and copy the lines to the surface
|
|
create(w, h, Graphics::PixelFormat::createFormatCLUT8());
|
|
|
|
for (int yCtr = 0; yCtr < h; ++yCtr) {
|
|
const byte *copySrc = &outputBuffer[yCtr * DEFAULT_WIDTH];
|
|
byte *copyDest = (byte *)getBasePtr(0, yCtr);
|
|
|
|
Common::copy(copySrc, copySrc + w, copyDest);
|
|
}
|
|
|
|
::free(outputBuffer);
|
|
::free(lookupTable);
|
|
::free(srcBuffer);
|
|
}
|
|
|
|
void GfxSurface::majTtxTty() {
|
|
if (!_yp)
|
|
w += _xSize;
|
|
|
|
if (!_xp)
|
|
h += _ySize;
|
|
}
|
|
|
|
/**
|
|
* Decompression Function - get next nibble
|
|
* @remarks Originally called 'suiv'
|
|
*/
|
|
byte GfxSurface::nextNibble(const byte *&pSrc) {
|
|
int v = *pSrc;
|
|
if (_nibbleFlag) {
|
|
++pSrc;
|
|
++_lookupIndex;
|
|
_nibbleFlag = false;
|
|
return v & 0xf;
|
|
} else {
|
|
_nibbleFlag = !_nibbleFlag;
|
|
return v >> 4;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Decompression Function - get next byte
|
|
* @remarks Originally called 'csuiv'
|
|
*/
|
|
byte GfxSurface::nextByte(const byte *&pSrc, const byte *&pLookup) {
|
|
assert(pLookup);
|
|
|
|
while (!_lookupValue) {
|
|
int v;
|
|
do {
|
|
v = nextNibble(pSrc) & 0xff;
|
|
_lookupValue += v;
|
|
} while (v == 0xf);
|
|
++pLookup;
|
|
}
|
|
|
|
--_lookupValue;
|
|
return *pLookup;
|
|
}
|
|
|
|
int GfxSurface::desanalyse(const byte *&pSrc) {
|
|
int total = 0;
|
|
int v = nextNibble(pSrc);
|
|
if (v == 0xf) {
|
|
int v2;
|
|
do {
|
|
v2 = nextNibble(pSrc);
|
|
total += v2;
|
|
} while (v2 == 0xf);
|
|
|
|
total *= 15;
|
|
v = nextNibble(pSrc);
|
|
}
|
|
|
|
total += v;
|
|
return total;
|
|
}
|
|
|
|
void GfxSurface::horizontal(const byte *&pSrc, byte *&pDest, const byte *&pLookup) {
|
|
INCR_XSIZE;
|
|
byte *pDestEnd = pDest + (_ySize - 1) * DEFAULT_WIDTH + _xSize;
|
|
|
|
for (;;) {
|
|
// If position is past end point, then skip this line
|
|
if (((_thickness - 1) * DEFAULT_WIDTH) + pDest >= pDestEnd) {
|
|
if (--_thickness == 0)
|
|
break;
|
|
continue;
|
|
}
|
|
|
|
bool continueFlag = false;
|
|
do {
|
|
for (int xIndex = 0; xIndex < _xSize; ++xIndex) {
|
|
if ((xIndex % 2) == 0) {
|
|
if (xIndex != 0)
|
|
++pDest;
|
|
|
|
// Write out vertical slice top to bottom
|
|
for (int yIndex = 0; yIndex < _thickness; ++yIndex, pDest += DEFAULT_WIDTH)
|
|
*pDest = nextByte(pSrc, pLookup);
|
|
|
|
++pDest;
|
|
} else {
|
|
// Write out vertical slice bottom to top
|
|
for (int yIndex = 0; yIndex < _thickness; ++yIndex) {
|
|
pDest -= DEFAULT_WIDTH;
|
|
*pDest = nextByte(pSrc, pLookup);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((_xSize % 2) == 0) {
|
|
int blockSize = _thickness * DEFAULT_WIDTH;
|
|
pDest += blockSize;
|
|
blockSize -= DEFAULT_WIDTH;
|
|
|
|
if (pDestEnd < (pDest + blockSize)) {
|
|
do {
|
|
if (--_thickness == 0)
|
|
return;
|
|
} while ((pDest + (_thickness - 1) * DEFAULT_WIDTH) >= pDestEnd);
|
|
}
|
|
} else {
|
|
while ((pDest + (_thickness - 1) * DEFAULT_WIDTH) >= pDestEnd) {
|
|
if (--_thickness == 0)
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (int xIndex = 0; xIndex < _xSize; ++xIndex, --pDest) {
|
|
if ((xIndex % 2) == 0) {
|
|
// Write out vertical slice top to bottom
|
|
for (int yIndex = 0; yIndex < _thickness; ++yIndex, pDest += DEFAULT_WIDTH)
|
|
*pDest = nextByte(pSrc, pLookup);
|
|
} else {
|
|
// Write out vertical slice top to bottom
|
|
for (int yIndex = 0; yIndex < _thickness; ++yIndex) {
|
|
pDest -= DEFAULT_WIDTH;
|
|
*pDest = nextByte(pSrc, pLookup);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((_xSize % 2) == 1) {
|
|
++pDest;
|
|
|
|
if ((pDest + (_thickness - 1) * DEFAULT_WIDTH) < pDestEnd) {
|
|
continueFlag = true;
|
|
break;
|
|
}
|
|
} else {
|
|
pDest += _thickness * DEFAULT_WIDTH + 1;
|
|
continueFlag = true;
|
|
break;
|
|
}
|
|
|
|
++pDest;
|
|
} while (((_thickness - 1) * DEFAULT_WIDTH + pDest) < pDestEnd);
|
|
|
|
if (continueFlag)
|
|
continue;
|
|
|
|
// Move to next line
|
|
if (--_thickness == 0)
|
|
break;
|
|
}
|
|
}
|
|
|
|
void GfxSurface::vertical(const byte *&pSrc, byte *&pDest, const byte *&pLookup) {
|
|
int drawIndex = 0;
|
|
|
|
for (;;) {
|
|
// Reduce thickness as necessary
|
|
while ((drawIndex + _thickness) > _xSize) {
|
|
if (--_thickness == 0)
|
|
return;
|
|
}
|
|
|
|
// Loop
|
|
for (int yCtr = 0; yCtr < _ySize; ++yCtr) {
|
|
if ((yCtr % 2) == 0) {
|
|
if (yCtr > 0)
|
|
pDest += DEFAULT_WIDTH;
|
|
|
|
drawIndex += _thickness;
|
|
for (int xCtr = 0; xCtr < _thickness; ++xCtr)
|
|
*pDest++ = nextByte(pSrc, pLookup);
|
|
} else {
|
|
pDest += DEFAULT_WIDTH;
|
|
drawIndex -= _thickness;
|
|
for (int xCtr = 0; xCtr < _thickness; ++xCtr)
|
|
*--pDest = nextByte(pSrc, pLookup);
|
|
}
|
|
}
|
|
if ((_ySize % 2) == 0) {
|
|
pDest += _thickness;
|
|
drawIndex += _thickness;
|
|
}
|
|
|
|
while (_xSize < (drawIndex + _thickness)) {
|
|
if (--_thickness == 0)
|
|
return;
|
|
}
|
|
|
|
// Loop
|
|
for (int yCtr = 0; yCtr < _ySize; ++yCtr) {
|
|
if ((yCtr % 2) == 0) {
|
|
if (yCtr > 0)
|
|
pDest -= DEFAULT_WIDTH;
|
|
|
|
drawIndex += _thickness;
|
|
|
|
for (int xCtr = 0; xCtr < _thickness; ++xCtr)
|
|
*pDest++ = nextByte(pSrc, pLookup);
|
|
} else {
|
|
pDest -= DEFAULT_WIDTH;
|
|
drawIndex -= _thickness;
|
|
|
|
for (int xCtr = 0; xCtr < _thickness; ++xCtr)
|
|
*--pDest = nextByte(pSrc, pLookup);
|
|
}
|
|
}
|
|
if ((_ySize % 2) == 0) {
|
|
pDest += _thickness;
|
|
drawIndex += _thickness;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GfxSurface::decom11(const byte *&pSrc, byte *&pDest, const byte *&pLookup) {
|
|
int yPos = 0, drawIndex = 0;
|
|
_yInc = DEFAULT_WIDTH;
|
|
_xInc = -1;
|
|
--_xSize;
|
|
--_ySize;
|
|
|
|
int areaNum = 0;
|
|
while (areaNum != -1) {
|
|
switch (areaNum) {
|
|
case 0:
|
|
*pDest = nextByte(pSrc, pLookup);
|
|
areaNum = 1;
|
|
break;
|
|
|
|
case 1:
|
|
nextDecompPtr(pDest);
|
|
|
|
if (!drawIndex) {
|
|
negXInc();
|
|
negYInc();
|
|
|
|
if (yPos == _ySize) {
|
|
nextDecompPtr(pDest);
|
|
++drawIndex;
|
|
} else {
|
|
++yPos;
|
|
}
|
|
|
|
*++pDest = nextByte(pSrc, pLookup);
|
|
areaNum = 2;
|
|
} else if (yPos != _ySize) {
|
|
++yPos;
|
|
--drawIndex;
|
|
areaNum = 0;
|
|
} else {
|
|
negXInc();
|
|
negYInc();
|
|
nextDecompPtr(pDest);
|
|
++drawIndex;
|
|
|
|
*++pDest = nextByte(pSrc, pLookup);
|
|
|
|
if (drawIndex == _xSize) {
|
|
areaNum = -1;
|
|
} else {
|
|
areaNum = 2;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
nextDecompPtr(pDest);
|
|
|
|
if (!yPos) {
|
|
negXInc();
|
|
negYInc();
|
|
|
|
if (drawIndex == _xSize) {
|
|
nextDecompPtr(pDest);
|
|
++yPos;
|
|
} else {
|
|
++drawIndex;
|
|
}
|
|
|
|
pDest += DEFAULT_WIDTH;
|
|
areaNum = 0;
|
|
} else if (drawIndex != _xSize) {
|
|
++drawIndex;
|
|
--yPos;
|
|
|
|
*pDest = nextByte(pSrc, pLookup);
|
|
areaNum = 2;
|
|
} else {
|
|
pDest += DEFAULT_WIDTH;
|
|
++yPos;
|
|
negXInc();
|
|
negYInc();
|
|
nextDecompPtr(pDest);
|
|
|
|
*pDest = nextByte(pSrc, pLookup);
|
|
|
|
if (yPos == _ySize)
|
|
areaNum = -1;
|
|
else
|
|
areaNum = 1;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GfxSurface::diag(const byte *&pSrc, byte *&pDest, const byte *&pLookup) {
|
|
int diagIndex = 0, drawIndex = 0;
|
|
--_xEnd;
|
|
|
|
while (!TFP(diagIndex)) {
|
|
for (;;) {
|
|
negXInc();
|
|
for (int idx = 0; idx <= _thickness; ++idx) {
|
|
*pDest = nextByte(pSrc, pLookup);
|
|
negXInc();
|
|
nextDecompPtr(pDest);
|
|
}
|
|
|
|
negYInc();
|
|
pDest += _yInc;
|
|
|
|
for (int idx = 0; idx <= _thickness; ++idx) {
|
|
*pDest = nextByte(pSrc, pLookup);
|
|
negXInc();
|
|
nextDecompPtr(pDest);
|
|
}
|
|
|
|
negXInc();
|
|
negYInc();
|
|
nextDecompPtr(pDest);
|
|
|
|
++drawIndex;
|
|
if (_xEnd < (drawIndex + 1)) {
|
|
TF1(pDest, diagIndex);
|
|
break;
|
|
}
|
|
|
|
pDest += _xInc;
|
|
++drawIndex;
|
|
if (_xEnd < (drawIndex + 1)) {
|
|
TF2(pSrc, pDest, pLookup, diagIndex);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (TFP(diagIndex))
|
|
break;
|
|
|
|
for (;;) {
|
|
for (int idx = 0; idx <= _thickness; ++idx) {
|
|
*pDest = nextByte(pSrc, pLookup);
|
|
negXInc();
|
|
nextDecompPtr(pDest);
|
|
}
|
|
|
|
negYInc();
|
|
pDest += _yInc;
|
|
|
|
for (int idx = 0; idx <= _thickness; ++idx) {
|
|
*pDest = nextByte(pSrc, pLookup);
|
|
negXInc();
|
|
nextDecompPtr(pDest);
|
|
}
|
|
|
|
negXInc();
|
|
negYInc();
|
|
nextDecompPtr(pDest);
|
|
|
|
if (--drawIndex == 0) {
|
|
TF1(pDest, diagIndex);
|
|
negXInc();
|
|
break;
|
|
} else {
|
|
pDest += _xInc;
|
|
|
|
if (--drawIndex == 0) {
|
|
TF2(pSrc, pDest, pLookup, diagIndex);
|
|
negXInc();
|
|
break;
|
|
}
|
|
}
|
|
|
|
negXInc();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Decompression Function - Move pDest ptr to next value to uncompress
|
|
* @remarks Originally called 'increments'
|
|
*/
|
|
void GfxSurface::nextDecompPtr(byte *&pDest) {
|
|
pDest += _xInc + _yInc;
|
|
}
|
|
|
|
/**
|
|
* Decompression Function - set xInc to its opposite value
|
|
* @remarks Originally called 'NIH'
|
|
*/
|
|
void GfxSurface::negXInc() {
|
|
_xInc = -_xInc;
|
|
}
|
|
|
|
/**
|
|
* Decompression Function - set yInc to its opposite value
|
|
* @remarks Originally called 'NIV'
|
|
*/
|
|
void GfxSurface::negYInc() {
|
|
_yInc = -_yInc;
|
|
}
|
|
|
|
bool GfxSurface::TFP(int v) {
|
|
int diff = _yEnd - v;
|
|
if (!diff)
|
|
// Time to finish loop in outer method
|
|
return true;
|
|
|
|
if (diff < (_thickness + 1))
|
|
_thickness = diff - 1;
|
|
|
|
return false;
|
|
}
|
|
|
|
void GfxSurface::TF1(byte *&pDest, int &v) {
|
|
v += _thickness + 1;
|
|
pDest += (_thickness + 1) * _yInc;
|
|
}
|
|
|
|
void GfxSurface::TF2(const byte *&pSrc, byte *&pDest, const byte *&pLookup, int &v) {
|
|
v += _thickness + 1;
|
|
|
|
for (int idx = 0; idx <= _thickness; ++idx) {
|
|
*pDest = nextByte(pSrc, pLookup);
|
|
pDest += _yInc;
|
|
}
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
GfxSurface::~GfxSurface() {
|
|
free();
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*
|
|
* Screen surface
|
|
*-------------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* Called to populate the font data from the passed file
|
|
*/
|
|
void ScreenSurface::readFontData(Common::File &f, int dataSize) {
|
|
assert(dataSize == (FONT_NUM_CHARS * FONT_HEIGHT));
|
|
f.read(_fontData, FONT_NUM_CHARS * FONT_HEIGHT);
|
|
}
|
|
|
|
/**
|
|
* Returns a graphics surface representing a subset of the screen. The affected area
|
|
* is also marked as dirty
|
|
*/
|
|
Graphics::Surface ScreenSurface::lockArea(const Common::Rect &bounds) {
|
|
_dirtyRects.push_back(bounds);
|
|
|
|
Graphics::Surface s;
|
|
s.init(bounds.width(), bounds.height(), pitch, getBasePtr(bounds.left, bounds.top), format);
|
|
return s;
|
|
}
|
|
|
|
/**
|
|
* Updates the affected areas of the surface to the underlying physical screen
|
|
*/
|
|
void ScreenSurface::updateScreen() {
|
|
// Iterate through copying dirty areas to the screen
|
|
for (Common::List<Common::Rect>::iterator i = _dirtyRects.begin(); i != _dirtyRects.end(); ++i) {
|
|
Common::Rect r = *i;
|
|
g_system->copyRectToScreen((const byte *)getBasePtr(r.left, r.top), pitch,
|
|
r.left, r.top, r.width(), r.height());
|
|
}
|
|
_dirtyRects.clear();
|
|
|
|
// Update the screen
|
|
g_system->updateScreen();
|
|
}
|
|
|
|
/**
|
|
* Draws a decoded picture on the screen
|
|
* @remarks - Because the ScummVM surface is using a double height 640x400 surface to
|
|
* simulate the original 640x400 surface, all Y values have to be doubled.
|
|
* - Image resources are stored at 320x200, so when drawn onto the screen a single pixel
|
|
* from the source image is drawn using the two pixels at the given index in the palette map
|
|
* - Because the original game supported 320 width resolutions, the X coordinate
|
|
* also needs to be doubled for EGA mode
|
|
*/
|
|
void ScreenSurface::drawPicture(GfxSurface &surface, int x, int y) {
|
|
// Adjust the draw position by the draw offset
|
|
x += surface._offset.x;
|
|
y += surface._offset.y;
|
|
|
|
// Lock the affected area of the surface to write to
|
|
Graphics::Surface destSurface = lockArea(Common::Rect(x * 2, y * 2,
|
|
(x + surface.w) * 2, (y + surface.h) * 2));
|
|
|
|
// Get a lookup for the palette mapping
|
|
const byte *paletteMap = &_vm->_curPict[2];
|
|
|
|
// Loop through writing
|
|
for (int yp = 0; yp < surface.h; ++yp) {
|
|
if (((y + yp) < 0) || ((y + yp) >= 200))
|
|
continue;
|
|
|
|
const byte *pSrc = (const byte *)surface.getBasePtr(0, yp);
|
|
byte *pDest = (byte *)destSurface.getBasePtr(0, yp * 2);
|
|
|
|
for (int xp = 0; xp < surface.w; ++xp, ++pSrc) {
|
|
if (*pSrc == surface._transparency) {
|
|
// Transparent point, so skip pixels
|
|
pDest += 2;
|
|
} else {
|
|
// Draw the pixel using the specified index in the palette map
|
|
*pDest = paletteMap[*pSrc * 2];
|
|
*(pDest + SCREEN_WIDTH) = paletteMap[*pSrc * 2];
|
|
++pDest;
|
|
|
|
// Use the secondary mapping value to draw the secondary column pixel
|
|
*pDest = paletteMap[*pSrc * 2 + 1];
|
|
*(pDest + SCREEN_WIDTH) = paletteMap[*pSrc * 2 + 1];
|
|
++pDest;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Copys a given surface to the given position
|
|
*/
|
|
void ScreenSurface::copyFrom(Graphics::Surface &src, int x, int y) {
|
|
lockArea(Common::Rect(x, y, x + src.w, y + src.h));
|
|
|
|
// Loop through writing
|
|
for (int yp = 0; yp < src.h; ++yp) {
|
|
if (((y + yp) < 0) || ((y + yp) >= SCREEN_HEIGHT))
|
|
continue;
|
|
|
|
const byte *pSrc = (const byte *)src.getBasePtr(0, yp);
|
|
byte *pDest = (byte *)getBasePtr(0, yp);
|
|
Common::copy(pSrc, pSrc + src.w, pDest);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draws a character at the specified co-ordinates
|
|
* @remarks Because the ScummVM surface is using a double height 640x400 surface to
|
|
* simulate the original 640x400 surface, all Y values have to be doubled
|
|
*/
|
|
void ScreenSurface::writeCharacter(const Common::Point &pt, unsigned char ch, int palIndex) {
|
|
Graphics::Surface destSurface = lockArea(Common::Rect(pt.x, pt.y * 2,
|
|
pt.x + FONT_WIDTH, (pt.y + FONT_HEIGHT) * 2));
|
|
|
|
// Get the start of the character to use
|
|
assert((ch >= ' ') && (ch <= (unsigned char)(32 + FONT_NUM_CHARS)));
|
|
const byte *charData = &_fontData[((int)ch - 32) * FONT_HEIGHT];
|
|
|
|
// Loop through decoding each character's data
|
|
for (int yp = 0; yp < FONT_HEIGHT; ++yp) {
|
|
byte *lineP = (byte *)destSurface.getBasePtr(0, yp * 2);
|
|
byte byteVal = *charData++;
|
|
|
|
for (int xp = 0; xp < FONT_WIDTH; ++xp, ++lineP, byteVal <<= 1) {
|
|
if (byteVal & 0x80) {
|
|
*lineP = palIndex;
|
|
*(lineP + SCREEN_WIDTH) = palIndex;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draws a box at the specified position and size
|
|
* @remarks Because the ScummVM surface is using a double height 640x400 surface to
|
|
* simulate the original 640x400 surface, all Y values have to be doubled
|
|
*/
|
|
void ScreenSurface::drawBox(int x, int y, int dx, int dy, int col) {
|
|
Graphics::Surface destSurface = lockArea(Common::Rect(x, y * 2, x + dx, (y + dy) * 2));
|
|
|
|
destSurface.hLine(0, 0, dx, col);
|
|
destSurface.hLine(0, 1, dx, col);
|
|
destSurface.hLine(0, destSurface.h - 1, dx, col);
|
|
destSurface.hLine(0, destSurface.h - 2, dx, col);
|
|
destSurface.vLine(0, 2, destSurface.h - 3, col);
|
|
destSurface.vLine(1, 2, destSurface.h - 3, col);
|
|
destSurface.vLine(dx - 1, 2, destSurface.h - 3, col);
|
|
destSurface.vLine(dx - 2, 2, destSurface.h - 3, col);
|
|
}
|
|
|
|
/**
|
|
* Fills an area with the specified color
|
|
* @remarks Because the ScummVM surface is using a double height 640x400 surface to
|
|
* simulate the original 640x400 surface, all Y values have to be doubled
|
|
*/
|
|
void ScreenSurface::fillRect(int color, const Common::Rect &bounds) {
|
|
Graphics::Surface destSurface = lockArea(Common::Rect(bounds.left, bounds.top * 2,
|
|
bounds.right, bounds.bottom * 2));
|
|
|
|
// Fill the area
|
|
destSurface.fillRect(Common::Rect(0, 0, destSurface.w, destSurface.h), color);
|
|
}
|
|
|
|
/**
|
|
* Clears the screen
|
|
*/
|
|
void ScreenSurface::clearScreen() {
|
|
Graphics::Surface destSurface = lockArea(Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
|
|
destSurface.fillRect(Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), 0);
|
|
}
|
|
|
|
/**
|
|
* Sets a single pixel at the specified co-ordinates
|
|
* @remarks Because the ScummVM surface is using a double height 640x400 surface to
|
|
* simulate the original 640x400 surface, all Y values have to be doubled
|
|
*/
|
|
void ScreenSurface::setPixel(const Common::Point &pt, int palIndex) {
|
|
assert((pt.x >= 0) && (pt.y >= 0) && (pt.x <= SCREEN_WIDTH) && (pt.y <= SCREEN_ORIG_HEIGHT));
|
|
Graphics::Surface destSurface = lockArea(Common::Rect(pt.x, pt.y * 2, pt.x + 1, (pt.y + 1) * 2));
|
|
|
|
byte *destP = (byte *)destSurface.getPixels();
|
|
*destP = palIndex;
|
|
*(destP + SCREEN_WIDTH) = palIndex;
|
|
}
|
|
|
|
/**
|
|
* Write out a string
|
|
* @remarks Originally called 'writeg'
|
|
*/
|
|
void ScreenSurface::drawString(const Common::String &l, int command) {
|
|
if (l == "")
|
|
return;
|
|
|
|
_vm->_mouse.hideMouse();
|
|
Common::Point pt = _textPos;
|
|
|
|
int charWidth = 6;
|
|
|
|
int x = pt.x + charWidth * l.size();
|
|
int color = 0;
|
|
|
|
switch (command) {
|
|
case 0:
|
|
case 2:
|
|
color = 15;
|
|
_vm->_screenSurface.fillRect(0, Common::Rect(pt.x, pt.y, x, pt.y + 7));
|
|
break;
|
|
case 1:
|
|
case 3:
|
|
_vm->_screenSurface.fillRect(15, Common::Rect(pt.x, pt.y, x, pt.y + 7));
|
|
break;
|
|
case 5:
|
|
color = 15;
|
|
break;
|
|
default:
|
|
// Default: Color set to zero (already done)
|
|
break;
|
|
}
|
|
|
|
pt.x += 1;
|
|
pt.y += 1;
|
|
for (x = 1; (x <= (int)l.size()) && (l[x - 1] != 0); ++x) {
|
|
_vm->_screenSurface.writeCharacter(Common::Point(pt.x, pt.y), l[x - 1], color);
|
|
pt.x += charWidth;
|
|
}
|
|
_vm->_mouse.showMouse();
|
|
}
|
|
|
|
/**
|
|
* Gets the width in pixels of the specified string
|
|
*/
|
|
int ScreenSurface::getStringWidth(const Common::String &s) {
|
|
int charWidth = 6;
|
|
|
|
return s.size() * charWidth;
|
|
}
|
|
|
|
void ScreenSurface::drawLine(int x, int y, int xx, int yy, int coul) {
|
|
int step, i;
|
|
float a, b;
|
|
float xr, yr, xro, yro;
|
|
|
|
xr = x;
|
|
yr = y;
|
|
xro = xx;
|
|
yro = yy;
|
|
|
|
if (abs(y - yy) > abs(x - xx)) {
|
|
a = (float)((x - xx)) / (y - yy);
|
|
b = (yr * xro - yro * xr) / (y - yy);
|
|
i = y;
|
|
if (y > yy)
|
|
step = -1;
|
|
else
|
|
step = 1;
|
|
do {
|
|
_vm->_screenSurface.setPixel(Common::Point(abs((int)(a * i + b)), i), coul);
|
|
i += step;
|
|
} while (i != yy);
|
|
} else {
|
|
a = (float)((y - yy)) / (x - xx);
|
|
b = ((yro * xr) - (yr * xro)) / (x - xx);
|
|
i = x;
|
|
if (x > xx)
|
|
step = -1;
|
|
else
|
|
step = 1;
|
|
do {
|
|
_vm->_screenSurface.setPixel(Common::Point(i, abs((int)(a * i + b))), coul);
|
|
i = i + step;
|
|
} while (i != xx);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draw plain rectangle
|
|
* @remarks Originally called 'paint_rect'
|
|
*/
|
|
void ScreenSurface::drawRectangle(int x, int y, int dx, int dy) {
|
|
_vm->_screenSurface.fillRect(11, Common::Rect(x, y, x + dx, y + dy));
|
|
}
|
|
|
|
void ScreenSurface::setParent(MortevielleEngine *vm) {
|
|
_vm = vm;
|
|
}
|
|
|
|
|
|
} // End of namespace Mortevielle
|