diff options
Diffstat (limited to 'engines/sword2/sprite.cpp')
-rw-r--r-- | engines/sword2/sprite.cpp | 655 |
1 files changed, 655 insertions, 0 deletions
diff --git a/engines/sword2/sprite.cpp b/engines/sword2/sprite.cpp new file mode 100644 index 0000000000..a0ba7f7189 --- /dev/null +++ b/engines/sword2/sprite.cpp @@ -0,0 +1,655 @@ +/* Copyright (C) 1994-1998 Revolution Software Ltd. + * Copyright (C) 2003-2006 The ScummVM project + * + * 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/stdafx.h" + +#include "sword2/sword2.h" +#include "sword2/defs.h" +#include "sword2/build_display.h" + +namespace Sword2 { + +/** + * This function takes a sprite and creates a mirror image of it. + * @param dst destination buffer + * @param src source buffer + * @param w width of the sprite + * @param h height of the sprite + */ + +void Screen::mirrorSprite(byte *dst, byte *src, int16 w, int16 h) { + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + *dst++ = *(src + w - x - 1); + } + src += w; + } +} + +/** + * This function takes a compressed frame of a sprite with up to 256 colours + * and decompresses it. + * @param dst destination buffer + * @param src source buffer + * @param decompSize the expected size of the decompressed sprite + */ + +int32 Screen::decompressRLE256(byte *dst, byte *src, int32 decompSize) { + // PARAMETERS: + // source points to the start of the sprite data for input + // decompSize gives size of decompressed data in bytes + // dest points to start of destination buffer for decompressed + // data + + byte headerByte; // block header byte + byte *endDest = dst + decompSize; // pointer to byte after end of decomp buffer + int32 rv; + + while (1) { + // FLAT block + // read FLAT block header & increment 'scan' to first pixel + // of block + headerByte = *src++; + + // if this isn't a zero-length block + if (headerByte) { + if (dst + headerByte > endDest) { + rv = 1; + break; + } + + // set the next 'headerByte' pixels to the next colour + // at 'source' + memset(dst, *src, headerByte); + + // increment destination pointer to just after this + // block + dst += headerByte; + + // increment source pointer to just after this colour + src++; + + // if we've decompressed all of the data + if (dst == endDest) { + rv = 0; // return "OK" + break; + } + } + + // RAW block + // read RAW block header & increment 'scan' to first pixel of + // block + headerByte = *src++; + + // if this isn't a zero-length block + if (headerByte) { + if (dst + headerByte > endDest) { + rv = 1; + break; + } + + // copy the next 'headerByte' pixels from source to + // destination + memcpy(dst, src, headerByte); + + // increment destination pointer to just after this + // block + dst += headerByte; + + // increment source pointer to just after this block + src += headerByte; + + // if we've decompressed all of the data + if (dst == endDest) { + rv = 0; // return "OK" + break; + } + } + } + + return rv; +} + +/** + * Unwinds a run of 16-colour data into 256-colour palette data. + */ + +void Screen::unwindRaw16(byte *dst, byte *src, uint8 blockSize, byte *colTable) { + // for each pair of pixels + while (blockSize > 1) { + // 1st colour = number in table at position given by upper + // nibble of source byte + *dst++ = colTable[(*src) >> 4]; + + // 2nd colour = number in table at position given by lower + // nibble of source byte + *dst++ = colTable[(*src) & 0x0f]; + + // point to next source byte + src++; + + // decrement count of how many pixels left to read + blockSize -= 2; + } + + // if there's a final odd pixel + if (blockSize) { + // colour = number in table at position given by upper nibble + // of source byte + *dst++ = colTable[(*src) >> 4]; + } +} + +/** + * This function takes a compressed frame of a sprite (with up to 16 colours) + * and decompresses it. + * @param dst destination buffer + * @param src source buffer + * @param decompSize the expected size of the uncompressed sprite + * @param colTable mapping from the 16 encoded colours to the current palette + */ + +int32 Screen::decompressRLE16(byte *dst, byte *src, int32 decompSize, byte *colTable) { + byte headerByte; // block header byte + byte *endDest = dst + decompSize; // pointer to byte after end of decomp buffer + int32 rv; + + while (1) { + // FLAT block + // read FLAT block header & increment 'scan' to first pixel + // of block + headerByte = *src++; + + // if this isn't a zero-length block + if (headerByte) { + if (dst + headerByte > endDest) { + rv = 1; + break; + } + + // set the next 'headerByte' pixels to the next + // colour at 'source' + memset(dst, *src, headerByte); + + // increment destination pointer to just after this + // block + dst += headerByte; + + // increment source pointer to just after this colour + src++; + + // if we've decompressed all of the data + if (dst == endDest) { + rv = 0; // return "OK" + break; + } + } + + // RAW block + // read RAW block header & increment 'scan' to first pixel of + // block + headerByte = *src++; + + // if this isn't a zero-length block + if (headerByte) { + if (dst + headerByte > endDest) { + rv = 1; + break; + } + + // copy the next 'headerByte' pixels from source to + // destination (NB. 2 pixels per byte) + unwindRaw16(dst, src, headerByte, colTable); + + // increment destination pointer to just after this + // block + dst += headerByte; + + // increment source pointer to just after this block + // (NB. headerByte gives pixels, so /2 for bytes) + src += (headerByte + 1) / 2; + + // if we've decompressed all of the data + if (dst >= endDest) { + rv = 0; // return "OK" + break; + } + } + } + + return rv; +} + +/** + * Creates a sprite surface. Sprite surfaces are used by the in-game dialogs + * and for displaying cutscene subtitles, which makes them much easier to draw + * than standard sprites. + * @param s information about how to decode the sprite + * @param sprite the buffer that will be created to store the surface + * @return RD_OK, or an error code + */ + +int32 Screen::createSurface(SpriteInfo *s, byte **sprite) { + *sprite = (byte *)malloc(s->w * s->h); + if (!*sprite) + return RDERR_OUTOFMEMORY; + + // Surfaces are either uncompressed or RLE256-compressed. No need to + // test for anything else. + + if (s->type & RDSPR_NOCOMPRESSION) { + memcpy(*sprite, s->data, s->w * s->h); + } else if (decompressRLE256(*sprite, s->data, s->w * s->h)) { + free(*sprite); + return RDERR_DECOMPRESSION; + } + + return RD_OK; +} + +/** + * Draws the sprite surface created earlier. + * @param s information about how to place the sprite + * @param surface pointer to the surface created earlier + * @param clipRect the clipping rectangle + */ + +void Screen::drawSurface(SpriteInfo *s, byte *surface, Common::Rect *clipRect) { + Common::Rect rd, rs; + uint16 x, y; + byte *src, *dst; + + rs.left = 0; + rs.right = s->w; + rs.top = 0; + rs.bottom = s->h; + + rd.left = s->x; + rd.right = rd.left + rs.right; + rd.top = s->y; + rd.bottom = rd.top + rs.bottom; + + Common::Rect defClipRect(0, 0, _screenWide, _screenDeep); + + if (!clipRect) { + clipRect = &defClipRect; + } + + if (clipRect->left > rd.left) { + rs.left += (clipRect->left - rd.left); + rd.left = clipRect->left; + } + + if (clipRect->top > rd.top) { + rs.top += (clipRect->top - rd.top); + rd.top = clipRect->top; + } + + if (clipRect->right < rd.right) { + rd.right = clipRect->right; + } + + if (clipRect->bottom < rd.bottom) { + rd.bottom = clipRect->bottom; + } + + if (rd.width() <= 0 || rd.height() <= 0) + return; + + src = surface + rs.top * s->w + rs.left; + dst = _buffer + _screenWide * rd.top + rd.left; + + // Surfaces are always transparent. + + for (y = 0; y < rd.height(); y++) { + for (x = 0; x < rd.width(); x++) { + if (src[x]) + dst[x] = src[x]; + } + src += s->w; + dst += _screenWide; + } + + updateRect(&rd); +} + +/** + * Destroys a surface. + */ + +void Screen::deleteSurface(byte *surface) { + free(surface); +} + +/** + * Draws a sprite onto the screen. The type of the sprite can be a combination + * of the following flags, some of which are mutually exclusive: + * RDSPR_DISPLAYALIGN The sprite is drawn relative to the top left corner + * of the screen + * RDSPR_FLIP The sprite is mirrored + * RDSPR_TRANS The sprite has a transparent colour zero + * RDSPR_BLEND The sprite is translucent + * RDSPR_SHADOW The sprite is affected by the light mask. (Scaled + * sprites always are.) + * RDSPR_NOCOMPRESSION The sprite data is not compressed + * RDSPR_RLE16 The sprite data is a 16-colour compressed sprite + * RDSPR_RLE256 The sprite data is a 256-colour compressed sprite + * @param s all the information needed to draw the sprite + * @warning Sprites will only be drawn onto the background, not over menubar + * areas. + */ + +// FIXME: I'm sure this could be optimized. There's plenty of data copying and +// mallocing here. + +int32 Screen::drawSprite(SpriteInfo *s) { + byte *src, *dst; + byte *sprite, *newSprite; + uint16 scale; + int16 i, j; + uint16 srcPitch; + bool freeSprite = false; + Common::Rect rd, rs; + + // ----------------------------------------------------------------- + // Decompression and mirroring + // ----------------------------------------------------------------- + + if (s->type & RDSPR_NOCOMPRESSION) + sprite = s->data; + else { + sprite = (byte *)malloc(s->w * s->h); + freeSprite = true; + if (!sprite) + return RDERR_OUTOFMEMORY; + if ((s->type & 0xff00) == RDSPR_RLE16) { + if (decompressRLE16(sprite, s->data, s->w * s->h, s->colourTable)) { + free(sprite); + return RDERR_DECOMPRESSION; + } + } else { + if (decompressRLE256(sprite, s->data, s->w * s->h)) { + free(sprite); + return RDERR_DECOMPRESSION; + } + } + } + + if (s->type & RDSPR_FLIP) { + newSprite = (byte *)malloc(s->w * s->h); + if (newSprite == NULL) { + if (freeSprite) + free(sprite); + return RDERR_OUTOFMEMORY; + } + mirrorSprite(newSprite, sprite, s->w, s->h); + if (freeSprite) + free(sprite); + sprite = newSprite; + freeSprite = true; + } + + // ----------------------------------------------------------------- + // Positioning and clipping. + // ----------------------------------------------------------------- + + int16 spriteX = s->x; + int16 spriteY = s->y; + + if (!(s->type & RDSPR_DISPLAYALIGN)) { + spriteX += _parallaxScrollX; + spriteY += _parallaxScrollY; + } + + spriteY += MENUDEEP; + + // A scale factor 0 or 256 means don't scale. Why do they use two + // different values to mean the same thing? Normalize it here for + // convenience. + + scale = (s->scale == 0) ? 256 : s->scale; + + rs.top = 0; + rs.left = 0; + + if (scale != 256) { + rs.right = s->scaledWidth; + rs.bottom = s->scaledHeight; + srcPitch = s->scaledWidth; + } else { + rs.right = s->w; + rs.bottom = s->h; + srcPitch = s->w; + } + + rd.top = spriteY; + rd.left = spriteX; + + if (!(s->type & RDSPR_DISPLAYALIGN)) { + rd.top -= _scrollY; + rd.left -= _scrollX; + } + + rd.right = rd.left + rs.right; + rd.bottom = rd.top + rs.bottom; + + // Check if the sprite would end up completely outside the screen. + + if (rd.left > RENDERWIDE || rd.top > RENDERDEEP + MENUDEEP || rd.right < 0 || rd.bottom < MENUDEEP) { + if (freeSprite) + free(sprite); + return RD_OK; + } + + if (rd.top < MENUDEEP) { + rs.top = MENUDEEP - rd.top; + rd.top = MENUDEEP; + } + if (rd.bottom > RENDERDEEP + MENUDEEP) { + rd.bottom = RENDERDEEP + MENUDEEP; + rs.bottom = rs.top + (rd.bottom - rd.top); + } + if (rd.left < 0) { + rs.left = -rd.left; + rd.left = 0; + } + if (rd.right > RENDERWIDE) { + rd.right = RENDERWIDE; + rs.right = rs.left + (rd.right - rd.left); + } + + // ----------------------------------------------------------------- + // Scaling + // ----------------------------------------------------------------- + + if (scale != 256) { + if (s->scaledWidth > SCALE_MAXWIDTH || s->scaledHeight > SCALE_MAXHEIGHT) { + if (freeSprite) + free(sprite); + return RDERR_NOTIMPLEMENTED; + } + + newSprite = (byte *)malloc(s->scaledWidth * s->scaledHeight); + if (newSprite == NULL) { + if (freeSprite) + free(sprite); + return RDERR_OUTOFMEMORY; + } + + if (_renderCaps & RDBLTFX_EDGEBLEND) + scaleImageGood(newSprite, s->scaledWidth, s->scaledWidth, s->scaledHeight, sprite, s->w, s->w, s->h, _buffer + _screenWide * rd.top + rd.left); + else + scaleImageFast(newSprite, s->scaledWidth, s->scaledWidth, s->scaledHeight, sprite, s->w, s->w, s->h); + + if (freeSprite) + free(sprite); + sprite = newSprite; + freeSprite = true; + } + + // ----------------------------------------------------------------- + // Light masking + // ----------------------------------------------------------------- + + // The light mask is an optional layer that covers the entire room + // and which is used to simulate light and shadows. Scaled sprites + // (actors, presumably) are always affected. + + if ((_renderCaps & RDBLTFX_SHADOWBLEND) && _lightMask && (scale != 256 || (s->type & RDSPR_SHADOW))) { + byte *lightMap; + + // Make sure that we never apply the shadow to the original + // resource data. This could only ever happen in the + // RDSPR_NOCOMPRESSION case. + + if (!freeSprite) { + newSprite = (byte *)malloc(s->w * s->h); + memcpy(newSprite, sprite, s->w * s->h); + sprite = newSprite; + freeSprite = true; + } + + src = sprite + rs.top * srcPitch + rs.left; + lightMap = _lightMask + (rd.top + _scrollY - MENUDEEP) * _locationWide + rd.left + _scrollX; + + for (i = 0; i < rs.height(); i++) { + for (j = 0; j < rs.width(); j++) { + if (src[j] && lightMap[j]) { + uint8 r = ((32 - lightMap[j]) * _palette[src[j] * 4 + 0]) >> 5; + uint8 g = ((32 - lightMap[j]) * _palette[src[j] * 4 + 1]) >> 5; + uint8 b = ((32 - lightMap[j]) * _palette[src[j] * 4 + 2]) >> 5; + src[j] = quickMatch(r, g, b); + } + } + src += srcPitch; + lightMap += _locationWide; + } + } + + // ----------------------------------------------------------------- + // Drawing + // ----------------------------------------------------------------- + + src = sprite + rs.top * srcPitch + rs.left; + dst = _buffer + _screenWide * rd.top + rd.left; + + if (s->type & RDSPR_BLEND) { + // The original code had two different blending cases. One for + // s->blend & 0x01 and one for s->blend & 0x02. However, the + // only values that actually appear in the cluster files are + // 0, 513 and 1025 so the s->blend & 0x02 case was never used. + // Which is just as well since that code made no sense to me. + + if (!(_renderCaps & RDBLTFX_SPRITEBLEND)) { + for (i = 0; i < rs.height(); i++) { + for (j = 0; j < rs.width(); j++) { + if (src[j] && ((i & 1) == (j & 1))) + dst[j] = src[j]; + } + src += srcPitch; + dst += _screenWide; + } + } else { + uint8 n = s->blend >> 8; + + for (i = 0; i < rs.height(); i++) { + for (j = 0; j < rs.width(); j++) { + if (src[j]) { + uint8 r1 = _palette[src[j] * 4 + 0]; + uint8 g1 = _palette[src[j] * 4 + 1]; + uint8 b1 = _palette[src[j] * 4 + 2]; + uint8 r2 = _palette[dst[j] * 4 + 0]; + uint8 g2 = _palette[dst[j] * 4 + 1]; + uint8 b2 = _palette[dst[j] * 4 + 2]; + + uint8 r = (r1 * n + r2 * (8 - n)) >> 3; + uint8 g = (g1 * n + g2 * (8 - n)) >> 3; + uint8 b = (b1 * n + b2 * (8 - n)) >> 3; + dst[j] = quickMatch(r, g, b); + } + } + src += srcPitch; + dst += _screenWide; + } + } + } else { + if (s->type & RDSPR_TRANS) { + for (i = 0; i < rs.height(); i++) { + for (j = 0; j < rs.width(); j++) { + if (src[j]) + dst[j] = src[j]; + } + src += srcPitch; + dst += _screenWide; + } + } else { + for (i = 0; i < rs.height(); i++) { + memcpy(dst, src, rs.width()); + src += srcPitch; + dst += _screenWide; + } + } + } + + if (freeSprite) + free(sprite); + + markAsDirty(rd.left, rd.top, rd.right - 1, rd.bottom - 1); + return RD_OK; +} + +/** + * Opens the light masking sprite for a room. + */ + +int32 Screen::openLightMask(SpriteInfo *s) { + // FIXME: The light mask is only needed on higher graphics detail + // settings, so to save memory we could simply ignore it on lower + // settings. But then we need to figure out how to ensure that it + // is properly loaded if the user changes the settings in mid-game. + + if (_lightMask) + return RDERR_NOTCLOSED; + + _lightMask = (byte *)malloc(s->w * s->h); + if (!_lightMask) + return RDERR_OUTOFMEMORY; + + if (decompressRLE256(_lightMask, s->data, s->w * s->h)) + return RDERR_DECOMPRESSION; + + return RD_OK; +} + +/** + * Closes the light masking sprite for a room. + */ + +int32 Screen::closeLightMask() { + if (!_lightMask) + return RDERR_NOTOPEN; + + free(_lightMask); + _lightMask = NULL; + return RD_OK; +} + +} // End of namespace Sword2 |