diff options
Diffstat (limited to 'engines/m4/graphics.cpp')
-rw-r--r-- | engines/m4/graphics.cpp | 1074 |
1 files changed, 1074 insertions, 0 deletions
diff --git a/engines/m4/graphics.cpp b/engines/m4/graphics.cpp new file mode 100644 index 0000000000..beda178344 --- /dev/null +++ b/engines/m4/graphics.cpp @@ -0,0 +1,1074 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "common/file.h" +#include "common/endian.h" +#include "common/system.h" +#include "common/util.h" +#include "common/ptr.h" + +#include "m4/globals.h" +#include "m4/graphics.h" +#include "m4/sprite.h" +#include "m4/m4.h" +#include "m4/compression.h" + +namespace M4 { + +RGBList::RGBList(int numEntries, RGB8 *srcData, bool freeData) { + _size = numEntries; + assert(numEntries <= 256); + + if (srcData == NULL) { + _data = new RGB8[numEntries]; + _freeData = true; + } else { + _data = srcData; + _freeData = freeData; + } + + _palIndexes = new byte[numEntries]; + Common::set_to(&_palIndexes[0], &_palIndexes[numEntries], 0); +} + +RGBList::~RGBList() { + if (_freeData) + delete[] _data; + delete[] _palIndexes; +} + +//-------------------------------------------------------------------------- + +#define VGA_COLOR_TRANS(x) (x == 0x3f ? 255 : x << 2) + +void M4Surface::loadCodesM4(Common::SeekableReadStream *source) { + if (!source) { + free(); + return; + } + + uint16 widthVal = source->readUint16LE(); + uint16 heightVal = source->readUint16LE(); + + create(widthVal, heightVal, 1); + source->read(pixels, widthVal * heightVal); +} + +void M4Surface::loadCodesMads(Common::SeekableReadStream *source) { + if (!source) { + free(); + return; + } + + uint16 widthVal = 320; + uint16 heightVal = 156; + byte *walkMap = new byte[source->size()]; + + create(widthVal, heightVal, 1); + source->read(walkMap, source->size()); + + byte *ptr = (byte *)getBasePtr(0, 0); + + for (int y = 0; y < heightVal; y++) { + for (int x = 0; x < widthVal; x++) { + int ofs = x + (y * widthVal); + if ((walkMap[ofs / 8] << (ofs % 8)) & 0x80) + *ptr++ = 1; // walkable + else + *ptr++ = 0; + } + } +} + +// Sprite related methods + +void M4Surface::vLine(int x, int y1, int y2) { + Graphics::Surface::vLine(x, y1, y2, _color); +} + +void M4Surface::hLine(int x1, int x2, int y) { + Graphics::Surface::hLine(x1, y, x2, _color); +} + +void M4Surface::vLineXor(int x, int y1, int y2) { + // Clipping + if (x < 0 || x >= w) + return; + + if (y2 < y1) + SWAP(y2, y1); + + if (y1 < 0) + y1 = 0; + if (y2 >= h) + y2 = h - 1; + + byte *ptr = (byte *)getBasePtr(x, y1); + while (y1++ <= y2) { + *ptr ^= 0xFF; + ptr += pitch; + } + +} + +void M4Surface::hLineXor(int x1, int x2, int y) { + // Clipping + if (y < 0 || y >= h) + return; + + if (x2 < x1) + SWAP(x2, x1); + + if (x1 < 0) + x1 = 0; + if (x2 >= w) + x2 = w - 1; + + if (x2 < x1) + return; + + byte *ptr = (byte *)getBasePtr(x1, y); + while (x1++ <= x2) + *ptr++ ^= 0xFF; + +} + +void M4Surface::line(int x1, int y1, int x2, int y2, byte color) { + Graphics::Surface::drawLine(x1, y1, x2, y2, color); +} + + +void M4Surface::frameRect(int x1, int y1, int x2, int y2) { + Graphics::Surface::frameRect(Common::Rect(x1, y1, x2, y2), _color); +} + +void M4Surface::fillRect(int x1, int y1, int x2, int y2) { + Graphics::Surface::fillRect(Common::Rect(x1, y1, x2, y2), _color); +} + +void M4Surface::drawSprite(int x, int y, SpriteInfo &info, const Common::Rect &clipRect) { + + enum { + kStatusSkip, + kStatusScale, + kStatusDraw + }; + + // NOTE: The current clipping code assumes that the top left corner of the clip + // rectangle is always 0, 0 + assert(clipRect.top == 0 && clipRect.left == 0); + + // TODO: Put err* and scaled* into SpriteInfo + int errX = info.hotX * info.scaleX % 100; + int errY = info.hotY * info.scaleY % 100; + int scaledWidth = scaleValue(info.width, info.scaleX, errX); + int scaledHeight = scaleValue(info.height, info.scaleY, errY); + + /* + printf("M4Surface::drawSprite() info.width = %d; info.scaleX = %d; info.height = %d; info.scaleY = %d; scaledWidth = %d; scaledHeight = %d\n", + info.width, info.scaleX, info.height, info.scaleY, scaledWidth, scaledHeight); fflush(stdout); + */ + + int clipX = 0, clipY = 0; + // Clip the sprite's width and height according to the clip rectangle's dimensions + // This clips the sprite to the bottom and right + if (x >= 0) { + scaledWidth = MIN<int>(x + scaledWidth, clipRect.right) - x; + } else { + clipX = x; + scaledWidth = x + scaledWidth; + } + if (y >= 0) { + scaledHeight = MIN<int>(y + scaledHeight, clipRect.bottom) - y; + } else { + clipY = y; + scaledHeight = y + scaledHeight; + } + + //printf("M4Surface::drawSprite() width = %d; height = %d; scaledWidth = %d; scaledHeight = %d\n", info.width, info.height, scaledWidth, scaledHeight); fflush(stdout); + + // Check if sprite is inside the screen. If it's not, there's no need to draw it + if (scaledWidth + x <= 0 || scaledHeight + y <= 0) // check left and top (in case x,y are negative) + return; + if (scaledWidth <= 0 || scaledHeight <= 0) // check right and bottom + return; + int heightAmt = scaledHeight; + + byte *src = info.sprite->getData(); + byte *dst = getBasePtr(x - info.hotX - clipX, y - info.hotY - clipY); + + int status = kStatusSkip; + byte *scaledLineBuf = new byte[scaledWidth]; + + while (heightAmt > 0) { + + if (status == kStatusSkip) { + // Skip line + errY -= info.scaleY; + if (errY < 0) + status = kStatusScale; + else + src += info.width; + } else { + + if (status == kStatusScale) { + // Scale current line + byte *lineDst = scaledLineBuf; + int curErrX = errX; + int widthVal = scaledWidth; + byte *tempSrc = src; + int startX = clipX; + while (widthVal > 0) { + byte pixel = *tempSrc++; + curErrX -= info.scaleX; + while (curErrX < 0) { + if (startX == 0) { + *lineDst++ = pixel; + widthVal--; + } else { + startX++; + } + curErrX += 100; + } + } + src += info.width; + status = kStatusDraw; + } + + if (status == kStatusDraw && clipY == 0) { + // Draw previously scaled line + // TODO Implement different drawing types (depth, shadow etc.) + byte *tempDst = dst; + for (int lineX = 0; lineX < scaledWidth; lineX++) { + byte pixel = scaledLineBuf[lineX]; + + if (info.encoding & 0x80) { + + if (pixel == 0x80) { + pixel = 0; + } else { + byte destPixel = *tempDst; + byte r, g, b; + r = CLIP((info.palette[destPixel].r * pixel) >> 10, 0, 31); + g = CLIP((info.palette[destPixel].g * pixel) >> 10, 0, 31); + b = CLIP((info.palette[destPixel].b * pixel) >> 10, 0, 31); + pixel = info.inverseColorTable[(b << 10) | (g << 5) | r]; + } + } + + if (pixel) + *tempDst = pixel; + + tempDst++; + } + dst += pitch; + heightAmt--; + // TODO depth etc. + //depthAddress += Destination -> Width; + + errY += 100; + if (errY >= 0) + status = kStatusSkip; + } else if (status == kStatusDraw && clipY < 0) { + clipY++; + + errY += 100; + if (errY >= 0) + status = kStatusSkip; + } + + } + + } + + delete[] scaledLineBuf; + +} + +// Surface methods + +byte *M4Surface::getData() { + return (byte *)pixels; +} + +byte *M4Surface::getBasePtr(int x, int y) { + return (byte *)Graphics::Surface::getBasePtr(x, y); +} + +void M4Surface::freeData() { +} + +void M4Surface::empty() { + Common::set_to((byte *) pixels, (byte *) pixels + w * h, _vm->_palette->BLACK); +} + +void M4Surface::frameRect(const Common::Rect &r, uint8 color) { + Graphics::Surface::frameRect(r, color); +} + +void M4Surface::fillRect(const Common::Rect &r, uint8 color) { + Graphics::Surface::fillRect(r, color); +} + +void M4Surface::copyFrom(M4Surface *src, const Common::Rect &srcBounds, int destX, int destY, + int transparentColor) { + // Validation of the rectangle and position + if ((destX >= w) || (destY >= h)) + return; + + Common::Rect copyRect = srcBounds; + if (destX < 0) { + copyRect.left += -destX; + destX = 0; + } else if (destX + copyRect.width() > w) { + copyRect.right -= destX + copyRect.width() - w; + } + if (destY < 0) { + copyRect.top += -destY; + destY = 0; + } else if (destY + copyRect.height() > h) { + copyRect.bottom -= destY + copyRect.height() - h; + } + + if (!copyRect.isValidRect()) + return; + + // Copy the specified area + + byte *data = src->getData(); + byte *srcPtr = data + (src->width() * copyRect.top + copyRect.left); + byte *destPtr = (byte *)pixels + (destY * width()) + destX; + + for (int rowCtr = 0; rowCtr < copyRect.height(); ++rowCtr) { + if (transparentColor == -1) + // No transparency, so copy line over + Common::copy(srcPtr, srcPtr + copyRect.width(), destPtr); + else { + // Copy each byte one at a time checking for the transparency color + for (int xCtr = 0; xCtr < copyRect.width(); ++xCtr) + if (srcPtr[xCtr] != transparentColor) destPtr[xCtr] = srcPtr[xCtr]; + } + + srcPtr += src->width(); + destPtr += width(); + } + + src->freeData(); +} + +void M4Surface::loadBackgroundRiddle(const char *sceneName) { + char resourceName[20]; + Common::SeekableReadStream *stream; + // Loads a Riddle scene + sprintf(resourceName, "%s.tt", sceneName); + stream = _vm->_resourceManager->get(resourceName); + m4LoadBackground(stream); + _vm->_resourceManager->toss(resourceName); +} + +void M4Surface::loadBackground(int sceneNumber, RGBList **palData) { + this->empty(); // clear previous scene + + if (_vm->isM4() || (_vm->getGameType() == GType_RexNebular)) { + char resourceName[20]; + Common::SeekableReadStream *stream; + + if (_vm->getGameType() == GType_RexNebular) { + // Load Rex Nebular screen + sprintf(resourceName, "rm%d.art", sceneNumber); + stream = _vm->_resourceManager->get(resourceName); + rexLoadBackground(stream, palData); + } else { + // Loads M4 game scene + if (palData) + *palData = NULL; + sprintf(resourceName, "%i.tt", sceneNumber); + stream = _vm->_resourceManager->get(resourceName); + m4LoadBackground(stream); + } + + _vm->_resourceManager->toss(resourceName); + + } else { + madsLoadBackground(sceneNumber, palData); + } +} + +void M4Surface::madsLoadBackground(int roomNumber, RGBList **palData) { + // Get a MadsPack reference to the tile set and mapping + char resourceName[20]; + int i; + + // Uncompressed tile map resource + sprintf(resourceName, "rm%d.mm", roomNumber); + MadsPack tileMapFile(resourceName, _vm); + Common::SeekableReadStream *mapStream = tileMapFile.getItemStream(0); + + // Get the details of the tiles and map + mapStream->readUint32LE(); + int tileCountX = mapStream->readUint16LE(); + int tileCountY = mapStream->readUint16LE(); + int tileWidthMap = mapStream->readUint16LE(); + int tileHeightMap = mapStream->readUint16LE(); + int screenWidth = mapStream->readUint16LE(); + int screenHeight = mapStream->readUint16LE(); + int tileCountMap = tileCountX * tileCountY; + delete mapStream; + + // Obtain tile map information + typedef Common::List<Common::SharedPtr<M4Surface> > TileSetList; + typedef TileSetList::iterator TileSetIterator; + TileSetList tileSet; + uint16 *tileMap = new uint16[tileCountMap]; + mapStream = tileMapFile.getItemStream(1); + for (i = 0; i < tileCountMap; ++i) + tileMap[i] = mapStream->readUint16LE(); + delete mapStream; + _vm->res()->toss(resourceName); + + // -------------------------------------------------------------------------------- + + // Tile map data, which needs to be kept compressed, as the tile offsets refer to + // the compressed data. Each tile is then uncompressed separately + sprintf(resourceName, "rm%d.tt", roomNumber); + Common::SeekableReadStream *tileDataComp = _vm->_resourceManager->get(resourceName); + MadsPack tileData(tileDataComp); + Common::SeekableReadStream *tileDataUncomp = tileData.getItemStream(0); + + // Validate that the data matches between the tiles and tile map file and is valid + int tileCount = tileDataUncomp->readUint16LE(); + int tileWidth = tileDataUncomp->readUint16LE(); + int tileHeight = tileDataUncomp->readUint16LE(); + delete tileDataUncomp; + assert(tileCountMap == tileCount); + assert(tileWidth == tileWidthMap); + assert(tileHeight == tileHeightMap); + assert(screenWidth == _vm->_screen->width()); + assert(screenHeight <= _vm->_screen->height()); + + // -------------------------------------------------------------------------------- + + // Get the palette to use + tileDataUncomp = tileData.getItemStream(2); + // Set palette + if (!palData) { + _vm->_palette->setMadsPalette(tileDataUncomp, 4); + } else { + int numColors; + RGB8 *rgbList = _vm->_palette->decodeMadsPalette(tileDataUncomp, &numColors); + *palData = new RGBList(numColors, rgbList, true); + } + delete tileDataUncomp; + + // -------------------------------------------------------------------------------- + + // Get tile data + + tileDataUncomp = tileData.getItemStream(1); + FabDecompressor fab; + uint32 compressedTileDataSize = 0; + + for (i = 0; i < tileCount; i++) { + tileDataUncomp->seek(i * 4, SEEK_SET); + uint32 tileOfs = tileDataUncomp->readUint32LE(); + M4Surface* newTile = new M4Surface(tileWidth, tileHeight); + + if (i == tileCount - 1) + compressedTileDataSize = tileDataComp->size() - tileOfs; + else + compressedTileDataSize = tileDataUncomp->readUint32LE() - tileOfs; + + //printf("Tile: %i, compressed size: %i\n", i, compressedTileDataSize); + + newTile->empty(); + + byte *compressedTileData = new byte[compressedTileDataSize]; + + tileDataComp->seek(tileData.getDataOffset() + tileOfs, SEEK_SET); + tileDataComp->read(compressedTileData, compressedTileDataSize); + + fab.decompress(compressedTileData, compressedTileDataSize, (byte*)newTile->pixels, tileWidth * tileHeight); + tileSet.push_back(TileSetList::value_type(newTile)); + delete[] compressedTileData; + } + + delete tileDataUncomp; + + // -------------------------------------------------------------------------------- + + // Loop through the mapping data to place the tiles on the screen + + uint16 *tIndex = &tileMap[0]; + for (int y = 0; y < tileCountY; y++) { + for (int x = 0; x < tileCountX; x++) { + int tileIndex = *tIndex++; + assert(tileIndex < tileCount); + TileSetIterator tile = tileSet.begin(); + for (i = 0; i < tileIndex; i++) + ++tile; + ((*tile).get())->copyTo(this, x * tileWidth, y * tileHeight); + } + } + tileSet.clear(); + _vm->res()->toss(resourceName); +} + +void M4Surface::rexLoadBackground(Common::SeekableReadStream *source, RGBList **palData) { + MadsPack packData(source); + Common::MemoryReadStream *sourceUnc = packData.getItemStream(0); + + int sceneWidth = sourceUnc->readUint16LE(); + int sceneHeight = sourceUnc->readUint16LE(); + int sceneSize = sceneWidth * sceneHeight; + if (sceneWidth > this->width()) { + warning("Background width is %i, too large to fit in screen. Setting it to %i", sceneWidth, this->width()); + sceneWidth = this->width(); + sceneSize = sceneWidth * sceneHeight; + } + if (sceneHeight > this->height()) { + warning("Background height is %i, too large to fit in screen.Setting it to %i", sceneHeight, this->height()); + sceneHeight = this->height(); + sceneSize = sceneWidth * sceneHeight; + } + + // Set palette + if (!palData) { + _vm->_palette->setMadsPalette(sourceUnc, 4); + } else { + int numColors; + RGB8 *rgbList = _vm->_palette->decodeMadsPalette(sourceUnc, &numColors); + *palData = new RGBList(numColors, rgbList, true); + } + delete sourceUnc; + + // Get the raw data for the background + sourceUnc = packData.getItemStream(1); + assert((int)sourceUnc->size() >= sceneSize); + + byte *pData = (byte *)pixels; + sourceUnc->read(pData, sceneSize); + + freeData(); + delete sourceUnc; +} + +#undef COL_TRANS + +void M4Surface::m4LoadBackground(Common::SeekableReadStream *source) { + M4Surface *tileBuffer = new M4Surface(); + uint curTileX = 0, curTileY = 0; + int clipX = 0, clipY = 0; + RGB8 palette[256]; + + source->readUint32LE(); // magic, unused + /*uint32 size =*/ source->readUint32LE(); + uint32 widthVal = source->readUint32LE(); + uint32 heightVal = source->readUint32LE(); + uint32 tilesX = source->readUint32LE(); + uint32 tilesY = source->readUint32LE(); + uint32 tileWidth = source->readUint32LE(); + uint32 tileHeight = source->readUint32LE(); + uint8 blackIndex = 0; + + // Debug + //printf("loadBackground(): %dx%d picture (%d bytes) - %dx%d tiles of size %dx%d\n", + // widthVal, heightVal, size, tilesX, tilesY, tileWidth, tileHeight); + + // BGR data, which is converted to RGB8 + for (uint i = 0; i < 256; i++) { + palette[i].b = source->readByte() << 2; + palette[i].g = source->readByte() << 2; + palette[i].r = source->readByte() << 2; + palette[i].u = source->readByte() << 2; + + if ((blackIndex == 0) && !palette[i].r && !palette[i].g && !palette[i].b) + blackIndex = i; + } + + _vm->_palette->setPalette(palette, 0, 256); + + // resize or create the surface + // note that the height of the scene in game scenes is smaller than 480, as the bottom part of the + // screen is the inventory + assert(width() == (int)widthVal); + //printf("width(): %d, widthVal: %d, height(): %d, heightVal: %d\n", width(), widthVal, height(), heightVal); + + tileBuffer->create(tileWidth, tileHeight, 1); + + for (curTileY = 0; curTileY < tilesY; curTileY++) { + clipY = MIN(heightVal, (1 + curTileY) * tileHeight) - (curTileY * tileHeight); + + for (curTileX = 0; curTileX < tilesX; curTileX++) { + clipX = MIN(widthVal, (1 + curTileX) * tileWidth) - (curTileX * tileWidth); + + // Read a tile and copy it to the destination surface + source->read(tileBuffer->pixels, tileWidth * tileHeight); + Common::Rect srcBounds(0, 0, clipX, clipY); + copyFrom(tileBuffer, srcBounds, curTileX * tileWidth, curTileY * tileHeight); + } + } + + if (heightVal < (uint)height()) + fillRect(Common::Rect(0, heightVal, width(), height()), blackIndex); + + delete tileBuffer; +} + +void M4Surface::madsloadInterface(int index, RGBList **palData) { + char resourceName[20]; + sprintf(resourceName, "i%d.int", index); + MadsPack intFile(resourceName, _vm); + RGB8 *palette = new RGB8[16]; + + // Chunk 0, palette + Common::SeekableReadStream *intStream = intFile.getItemStream(0); + + for (int i = 0; i < 16; i++) { + palette[i].r = intStream->readByte() << 2; + palette[i].g = intStream->readByte() << 2; + palette[i].b = intStream->readByte() << 2; + intStream->readByte(); + intStream->readByte(); + intStream->readByte(); + } + *palData = new RGBList(16, palette, true); + delete intStream; + + // Chunk 1, data + intStream = intFile.getItemStream(1); + create(320, 44, 1); + intStream->read(pixels, 320 * 44); + delete intStream; +} + +void M4Surface::translate(RGBList *list, bool isTransparent) { + byte *p = getBasePtr(0, 0); + byte *palIndexes = list->palIndexes(); + + for (int i = 0; i < width() * height(); ++i, ++p) { + if (!isTransparent || (*p != 0)) { + assert(*p < list->size()); + *p = palIndexes[*p]; + } + } + + freeData(); +} + +//-------------------------------------------------------------------------- +// Palette class +// + +#define GREEN_START 32 +#define NUM_GREENS 32 +#define GREEN_END (GREEN_START + NUM_GREENS - 1) +#define NORMAL_START 64 +#define NORMAL_END 255 +#define NUM_NORMAL (NORMAL_END - NORMAL_START + 1) + +// Support function for creating a list of palette indexes to change entries in the shaded range to + +static void makeTranslationList(RGB8 *palData, byte transList[NUM_GREENS]) { + int i, j, minDistance; + byte bestIndex; + + for (i = 0; i < NUM_GREENS; ++i) { + bestIndex = NORMAL_START; + minDistance = 255; + + uint8 findCol = palData[GREEN_START + i].g; + + // Find the closest matching palette color + for (j = NORMAL_START; j <= NORMAL_END; ++j) { + int greenVal = palData[j].g; + if (ABS(findCol - greenVal) < minDistance) { + minDistance = ABS(findCol - greenVal); + bestIndex = j; + } + + if (minDistance == 0) + break; + } + + transList[i] = bestIndex; + } +} + +// Support function for fading in or out + +static void fadeRange(M4Engine *vm, RGB8 *srcPal, RGB8 *destPal, int startIndex, int endIndex, + int numSteps, uint delayAmount) { + RGB8 tempPal[256]; + + // perform the fade + for(int stepCtr = 1; stepCtr <= numSteps; ++stepCtr) { + // Delay the specified amount + uint32 startTime = g_system->getMillis(); + while ((g_system->getMillis() - startTime) < delayAmount) { + vm->_events->handleEvents(); + g_system->delayMillis(10); + } + + for (int i = startIndex; i <= endIndex; ++i) { + // Handle the intermediate rgb values for fading + tempPal[i].r = (byte) (srcPal[i].r + (destPal[i].r - srcPal[i].r) * stepCtr / numSteps); + tempPal[i].g = (byte) (srcPal[i].g + (destPal[i].g - srcPal[i].g) * stepCtr / numSteps); + tempPal[i].b = (byte) (srcPal[i].b + (destPal[i].b - srcPal[i].b) * stepCtr / numSteps); + } + + vm->_palette->setPalette(&tempPal[startIndex], startIndex, endIndex - startIndex + 1); + vm->_viewManager->refreshAll(); + } + + // Make sure the end palette exactly matches what is wanted + vm->_palette->setPalette(&destPal[startIndex], startIndex, endIndex - startIndex + 1); +} + +Palette::Palette(M4Engine *vm) : _vm(vm) { + reset(); + _fading_in_progress = false; + Common::set_to(&_usageCount[0], &_usageCount[256], 0); +} + +void Palette::setPalette(const byte *colors, uint start, uint num) { + g_system->setPalette(colors, start, num); + reset(); +} + +void Palette::setPalette(const RGB8 *colors, uint start, uint num) { + g_system->setPalette((const byte *)colors, start, num); + reset(); +} + +void Palette::grabPalette(byte *colors, uint start, uint num) { + g_system->grabPalette(colors, start, num); + reset(); +} + +uint8 Palette::palIndexFromRgb(byte r, byte g, byte b, RGB8 *paletteData) { + byte index = 0; + int32 minDist = 0x7fffffff; + RGB8 palData[256]; + int Rdiff, Gdiff, Bdiff; + + if (paletteData == NULL) { + g_system->grabPalette((byte *)palData, 0, 256); + paletteData = &palData[0]; + } + + for (int palIndex = 0; palIndex < 256; ++palIndex) { + Rdiff = r - paletteData[palIndex].r; + Gdiff = g - paletteData[palIndex].g; + Bdiff = b - paletteData[palIndex].b; + + if (Rdiff * Rdiff + Gdiff * Gdiff + Bdiff * Bdiff < minDist) { + minDist = Rdiff * Rdiff + Gdiff * Gdiff + Bdiff * Bdiff; + index = (uint8)palIndex; + } + } + + return (uint8)index; +} + +void Palette::reset() { + RGB8 palData[256]; + g_system->grabPalette((byte *)palData, 0, 256); + + BLACK = palIndexFromRgb(0, 0, 0, palData); + BLUE = palIndexFromRgb(0, 0, 255, palData); + GREEN = palIndexFromRgb(0, 255, 0, palData); + CYAN = palIndexFromRgb(0, 255, 255, palData); + RED = palIndexFromRgb(255, 0, 0, palData); + VIOLET = palIndexFromRgb(255, 0, 255, palData); + BROWN = palIndexFromRgb(168, 84, 84, palData); + LIGHT_GRAY = palIndexFromRgb(168, 168, 168, palData); + DARK_GRAY = palIndexFromRgb(84, 84, 84, palData); + LIGHT_BLUE = palIndexFromRgb(0, 0, 127, palData); + LIGHT_GREEN = palIndexFromRgb(0, 127, 0, palData); + LIGHT_CYAN = palIndexFromRgb(0, 127, 127, palData); + LIGHT_RED = palIndexFromRgb(84, 0, 0, palData); + PINK = palIndexFromRgb(84, 0, 0, palData); + YELLOW = palIndexFromRgb(0, 84, 84, palData); + WHITE = palIndexFromRgb(255, 255, 255, palData); +} + +void Palette::fadeToGreen(int numSteps, uint delayAmount) { + if (_fading_in_progress) + return; + _fading_in_progress = true; + byte translationList[NUM_GREENS]; + + int i; + byte *tempP; + uint8 greenAmount = 0; + RGB8 *srcPalette = (RGB8 *) &_originalPalette[0]; + RGB8 *destPalette = (RGB8 *) &_fadedPalette[0]; + + _vm->_palette->grabPalette(srcPalette, 0, 256); + + // Create the destination 'greenish' palette to fade to by setting the green component + // to the average of the RGB bytes, and leaving the Red and Blue parts as 0 + + Common::copy(&srcPalette[0], &srcPalette[256], &destPalette[0]); + for (i = 32; i < 256; ++i) { + byte luminance = (byte)((destPalette[i].r + destPalette[i].g + destPalette[i].b) / 3); + destPalette[i].g = MIN((byte)255, luminance); + destPalette[i].r = destPalette[i].b = 0; + } + + // Handle the actual fading + fadeRange(_vm, srcPalette, destPalette, 21, 255, numSteps, delayAmount); + + // Create a translation table to be used in translating pixels in the game surface + // using palette indexes in the range the range #32-63 into values from #64-255 + + makeTranslationList(destPalette, translationList); + + // Use palette indexes from #32-63 for the range of possible shades + + for (i = GREEN_START; i <= GREEN_END; ++i, greenAmount += 8) { + destPalette[i].g = greenAmount; + destPalette[i].r = destPalette[i].b = 0; + } + + // Remap all pixels into the #32-63 range + + tempP = _vm->_scene->getData(); + for (int pixelCtr = 0; pixelCtr < _vm->_scene->width() * _vm->_scene->height(); + ++pixelCtr, ++tempP) { + // If pixel is in #32-63 range already, remap to higher palette entries + if ((*tempP >= GREEN_START) && (*tempP <= GREEN_END)) + *tempP = translationList[*tempP - GREEN_START]; + + *tempP = (uint8) (GREEN_START + (destPalette[*tempP].g >> 3)); + } + + _vm->_palette->setPalette(&destPalette[GREEN_START], GREEN_START, NUM_GREENS); + _vm->_viewManager->refreshAll(); + _fading_in_progress = false; +} + +void Palette::fadeFromGreen(int numSteps, uint delayAmount, bool fadeToBlack) { + if (_fading_in_progress) + return; + _fading_in_progress = true; + RGB8 blackPalette[256]; + RGB8 *fadedPalette = (RGB8 *) &_fadedPalette[0]; + RGB8 *destPalette = (RGB8 *) &_originalPalette[0]; + + if (fadeToBlack) { + Common::set_to((byte *)&blackPalette[0], (byte *)&blackPalette[256], 0); + destPalette = &blackPalette[0]; + } + + // Initially restore the faded palette + _vm->_palette->setPalette(fadedPalette, 0, 256); + _vm->_viewManager->refreshAll(); + + // Restore the pixel data from the original screen + _vm->_scene->update(); + + // Handle the actual fading + fadeRange(_vm, fadedPalette, destPalette, GREEN_START, NORMAL_END, numSteps, delayAmount); + + _fading_in_progress = false; +} + +void Palette::fadeIn(int numSteps, uint delayAmount, RGBList *destPalette) { + fadeIn(numSteps, delayAmount, destPalette->data(), destPalette->size()); +} + +void Palette::fadeIn(int numSteps, uint delayAmount, RGB8 *destPalette, int numColors) { + if (_fading_in_progress) + return; + + _fading_in_progress = true; + RGB8 blackPalette[256]; + Common::set_to((byte *)&blackPalette[0], (byte *)&blackPalette[256], 0); + + // Initially set the black palette + _vm->_palette->setPalette(blackPalette, 0, numColors); + + // Handle the actual fading + fadeRange(_vm, blackPalette, destPalette, 0, numColors - 1, numSteps, delayAmount); + + _fading_in_progress = false; +} + +RGB8 *Palette::decodeMadsPalette(Common::SeekableReadStream *palStream, int *numColors) { + *numColors = palStream->readUint16LE(); + assert(*numColors <= 252); + + RGB8 *palData = new RGB8[*numColors]; + Common::set_to((byte *)&palData[0], (byte *)&palData[*numColors], 0); + + for (int i = 0; i < *numColors; ++i) { + byte r = palStream->readByte(); + byte g = palStream->readByte(); + byte b = palStream->readByte(); + palData[i].r = VGA_COLOR_TRANS(r); + palData[i].g = VGA_COLOR_TRANS(g); + palData[i].b = VGA_COLOR_TRANS(b); + + // The next 3 bytes are unused + palStream->skip(3); + } + + return palData; +} + +int Palette::setMadsPalette(Common::SeekableReadStream *palStream, int indexStart) { + int colorCount; + RGB8 *palData = Palette::decodeMadsPalette(palStream, &colorCount); + _vm->_palette->setPalette(palData, indexStart, colorCount); + delete palData; + return colorCount; +} + +void Palette::setMadsSystemPalette() { + // Rex Nebular default system palette + resetColorCounts(); + + RGB8 palData[4]; + palData[0].r = palData[0].g = palData[0].b = 0; + palData[1].r = palData[1].g = palData[1].b = 0x54; + palData[2].r = palData[2].g = palData[2].b = 0xb4; + palData[3].r = palData[3].g = palData[3].b = 0xff; + + setPalette(palData, 0, 4); + blockRange(0, 4); +} + +void Palette::resetColorCounts() { + Common::set_to(&_usageCount[0], &_usageCount[256], 0); +} + +void Palette::blockRange(int startIndex, int size) { + // Use a reference count of -1 to signal a palette index shouldn't be used + Common::set_to(&_usageCount[startIndex], &_usageCount[startIndex + size], -1); +} + +void Palette::addRange(RGBList *list) { + RGB8 *data = list->data(); + byte *palIndexes = list->palIndexes(); + RGB8 palData[256]; + g_system->grabPalette((byte *)&palData[0], 0, 256); + bool paletteChanged = false; + + for (int colIndex = 0; colIndex < list->size(); ++colIndex) { + // Scan through for an existing copy of the RGB value + int palIndex = -1; + while (++palIndex < 256) { + if (_usageCount[palIndex] <= 0) + // Palette index is to be skipped + continue; + + if ((palData[palIndex].r == data[colIndex].r) && + (palData[palIndex].g == data[colIndex].g) && + (palData[palIndex].b == data[colIndex].b)) + // Match found + break; + } + + if (palIndex == 256) { + // No match found, so find a free slot to use + palIndex = -1; + while (++palIndex < 256) { + if (_usageCount[palIndex] == 0) + break; + } + + if (palIndex == 256) + error("addRange - Ran out of palette space to allocate"); + + palData[palIndex].r = data[colIndex].r; + palData[palIndex].g = data[colIndex].g; + palData[palIndex].b = data[colIndex].b; + paletteChanged = true; + } + + palIndexes[colIndex] = palIndex; + ++_usageCount[palIndex]; + } + + if (paletteChanged) { + g_system->setPalette((byte *)&palData[0], 0, 256); + reset(); + } +} + +void Palette::deleteRange(RGBList *list) { + // Release the reference count on each of the palette entries + for (int colIndex = 0; colIndex < list->size(); ++colIndex) { + int palIndex = list->palIndexes()[colIndex]; + assert(_usageCount[palIndex] > 0); + --_usageCount[palIndex]; + } +} + +void Palette::deleteAllRanges() { + for (int colIndex = 0; colIndex < 255; ++colIndex) + _usageCount[colIndex] = 0; +} + +//-------------------------------------------------------------------------- +// Support methods + +void decompressRle(byte *rleData, int rleSize, byte *celData, int w, int h) { + byte *src = rleData; + byte *dst = celData; + byte len; + while (1) { + len = *src++; + if (len == 0) { + len = *src++; + if (len <= 2) { + if (len == 1) // end of sprite marker + break; + } else { + while (len--) + *dst++ = *src++; + } + } else { + while (len--) + *dst++ = *src; + *src++; + } + } +} + +int scaleValue(int value, int scale, int err) { + int scaled = 0; + while (value--) { + err -= scale; + while (err < 0) { + scaled++; + err += 100; + } + } + return scaled; +} + +} // End of namespace M4 |