/* 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 3 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, see . * */ #ifdef ENABLE_HE #include "common/archive.h" #include "common/ptr.h" #include "common/system.h" #include "graphics/cursorman.h" #include "graphics/primitives.h" #include "scumm/he/font_he.h" #include "scumm/he/logic_he.h" #include "scumm/he/intern_he.h" #include "scumm/resource.h" #include "scumm/scumm.h" #include "scumm/util.h" #include "scumm/he/wiz_he.h" #include "scumm/he/moonbase/moonbase.h" namespace Scumm { #ifdef WIZ_DEBUG_BUFFERS Common::Array *WizPxShrdBuffer::_allocLocs = 0; #endif Wiz::Wiz(ScummEngine_v71he *vm) : _vm(vm) { _wizBufferIndex = 0; memset(&_wizBuffer, 0, sizeof(_wizBuffer)); memset(&_polygons, 0, sizeof(_polygons)); _useWizClipRect = false; _uses16BitColor = (_vm->_game.features & GF_16BIT_COLOR); } void Wiz::clearWizBuffer() { _wizBufferIndex = 0; } void Wiz::processWizImageCaptureCmd(const WizImageCommand *params) { bool compressIt = (params->compressionType == kWCTTRLE); bool background = (params->flags & kWRFBackground) != 0; takeAWiz(params->image, params->box.left, params->box.top, params->box.right, params->box.bottom, background, compressIt); _vm->_res->setModified(rtImage, params->image); } void Wiz::takeAWiz(int globnum, int x1, int y1, int x2, int y2, bool back, bool compress) { int bufferWidth, bufferHeight; Common::Rect rect, clipRect; WizPxShrdBuffer srcPtr; VirtScreen *pvs = &_vm->_virtscr[kMainVirtScreen]; bufferWidth = pvs->w; bufferHeight = pvs->h; if (back) { srcPtr = WizPxShrdBuffer(pvs->getBackPixels(0, 0), false); } else { srcPtr = WizPxShrdBuffer(pvs->getPixels(0, 0), false); } rect.left = x1; rect.top = y1; rect.right = x2; rect.bottom = y2; clipRect.left = 0; clipRect.top = 0; clipRect.right = bufferWidth - 1; clipRect.bottom = bufferHeight - 1; if (!findRectOverlap(&rect, &clipRect)) { error("Capture rect invalid (%-4d,%4d,%-4d,%4d)", x1, y1, x2, y2); } uint8 *palPtr = nullptr; if (_vm->_game.heversion >= 99) { palPtr = _vm->_hePalettes + _vm->_hePaletteSlot; } else { palPtr = _vm->_currentPalette; } buildAWiz( srcPtr, bufferWidth, bufferHeight, palPtr, &rect, (compress) ? kWCTTRLE : kWCTNone, globnum, _vm->_game.heversion <= 90 ? 0x05 : _vm->VAR(_vm->VAR_WIZ_TRANSPARENT_COLOR)); } void Wiz::simpleDrawAWiz(int image, int state, int x, int y, int flags) { if (!_vm->_fullRedraw) { drawAWiz(image, state, x, y, 0, flags, 0, 0, nullptr, 0, nullptr); } else { bufferAWiz(image, state, x, y, 0, flags, 0, 0, 0); } } void Wiz::bufferAWiz(int image, int state, int x, int y, int z, int flags, int optionalShadowImage, int optionalZBufferImage, int whichPalette) { assert(_wizBufferIndex < ARRAYSIZE(_wizBuffer)); WizBufferElement *wi = &_wizBuffer[_wizBufferIndex]; wi->image = image; wi->x = x; wi->y = y; wi->z = z; wi->state = state; wi->flags = flags; wi->shadow = optionalShadowImage; wi->zbuffer = optionalZBufferImage; wi->palette = whichPalette; ++_wizBufferIndex; } WizPxShrdBuffer Wiz::drawAWiz(int image, int state, int x, int y, int z, int flags, int optionalShadowImage, int optionalZBufferImage, Common::Rect *optionalClipRect, int whichPalette, WizSimpleBitmap *optionalBitmapOverride) { return drawAWizEx(image, state, x, y, z, flags, optionalShadowImage, optionalZBufferImage, optionalClipRect, whichPalette, optionalBitmapOverride, nullptr); } WizPxShrdBuffer Wiz::drawAWizEx(int image, int state, int x, int y, int z, int flags, int optionalShadowImage, int optionalZBufferImage, Common::Rect *optionalClipRect, int whichPalette, WizSimpleBitmap *optionalBitmapOverride, const WizImageCommand *optionalICmdPtr) { const WizRawPixel *colorConversionTable; Common::Rect *clipRectPtr; if (whichPalette) { colorConversionTable = (WizRawPixel *)_vm->getHEPaletteSlot(whichPalette); } else { colorConversionTable = nullptr; } // Get the shadow... if (!optionalShadowImage) { if (_wizActiveShadow && (flags & kWRFUseShadow)) { optionalShadowImage = _wizActiveShadow; } } if (!(flags & kWRFPolygon)) { // Get the clipping rect if any... if (!optionalClipRect) { if (_useWizClipRect && !(flags & (kWRFPrint | kWRFAlloc))) { clipRectPtr = &_wizClipRect; } else { clipRectPtr = nullptr; } } else { clipRectPtr = optionalClipRect; } // Call the primitive renderer. return drawAWizPrimEx(image, state, x, y, z, optionalShadowImage, optionalZBufferImage, clipRectPtr, flags, optionalBitmapOverride, colorConversionTable, optionalICmdPtr); } else { warpDrawWiz( image, state, x, flags, _vm->_game.heversion <= 90 ? 0x05 : _vm->VAR(_vm->VAR_WIZ_TRANSPARENT_COLOR), optionalBitmapOverride, colorConversionTable, optionalShadowImage); return WizPxShrdBuffer(); } } WizPxShrdBuffer Wiz::drawAWizPrim(int globNum, int state, int x, int y, int z, int shadowImage, int zbufferImage, const Common::Rect *optionalClipRect, int flags, WizSimpleBitmap *optionalBitmapOverride, const WizRawPixel *optionalColorConversionTable) { return drawAWizPrimEx(globNum, state, x, y, z, shadowImage, zbufferImage, optionalClipRect, flags, optionalBitmapOverride, optionalColorConversionTable, 0); } WizPxShrdBuffer Wiz::drawAWizPrimEx(int globNum, int state, int x, int y, int z, int shadowImage, int zbufferImage, const Common::Rect *optionalClipRect, int flags, WizSimpleBitmap *optionalBitmapOverride, const WizRawPixel *optionalColorConversionTable, const WizImageCommand *optionalICmdPtr) { int destWidth, destHeight, srcWidth, srcHeight, srcComp, remapId; byte *srcData, *srcPtr, *stateHeader, *remapPtr; const byte *shadowPtr; Common::Rect destRect, clipRect; bool markUpdates; WizPxShrdBuffer destPtr; markUpdates = true; remapPtr = nullptr; if (_vm->_game.heversion > 98) { // Set the optional remap table up to the default if one isn't specified if (!optionalColorConversionTable) { optionalColorConversionTable = (WizRawPixel *)_vm->getHEPaletteSlot(1); } } if (shadowImage) { shadowPtr = getColorMixBlockPtrForWiz(shadowImage); if (shadowPtr) { shadowPtr += _vm->_resourceHeaderSize; } } else { shadowPtr = nullptr; } if (_vm->_game.heversion < 99 && _uses16BitColor) { if (shadowPtr) { shadowPtr = nullptr; } } if (_vm->_game.heversion > 99 || _vm->_isHE995) { // Z-Buffer image... (only format supported: uncompressed 16-bit) if (zbufferImage) { byte *pzbHeader = getWizStateHeaderPrim(zbufferImage, 0); assert(pzbHeader); int zbComp = READ_LE_UINT32(pzbHeader + _vm->_resourceHeaderSize); if (zbComp != kWCTNone16Bpp) { error("Wiz::drawAWizPrimEx(): 16-bit uncompressed z-buffers are the only currently supported format"); } } } // Get the header for this state... stateHeader = getWizStateHeaderPrim(globNum, state); if (!stateHeader) error("Wiz::drawAWizPrimEx(): %d state %d missing header", globNum, state); srcComp = READ_LE_UINT32(stateHeader + _vm->_resourceHeaderSize + 0); srcWidth = READ_LE_UINT32(stateHeader + _vm->_resourceHeaderSize + 4); srcHeight = READ_LE_UINT32(stateHeader + _vm->_resourceHeaderSize + 8); if ((!isUncompressedFormatTypeID(srcComp)) && (srcComp != kWCTTRLE)) { error("Wiz::drawAWizPrimEx(): %d has invalid compression type %d", globNum, srcComp); } // Get the data block for this state... srcData = getWizStateDataPrim(globNum, state); if (!srcData) error("Wiz::drawAWizPrimEx(): %d state %d missing data block", globNum, state); // Copy the palette from this state... if (flags & kWRFUsePalette) { srcPtr = getWizStatePaletteDataPrim(globNum, state); if (!srcPtr) error("Wiz::drawAWizPrimEx(): %d state %d missing palette block", globNum, state); _vm->setPaletteFromPtr(srcPtr + _vm->_resourceHeaderSize, 256); } // Remap this wiz state... if (flags & kWRFRemap) { remapPtr = getWizStateRemapDataPrim(globNum, state); if (!remapPtr) error("Wiz::drawAWizPrimEx(): %d state %d is missing a remap palette block", globNum, state); remapId = READ_BE_UINT32(remapPtr + _vm->_resourceHeaderSize); if (_vm->_game.heversion <= 80 || remapId != WIZ_MAGIC_REMAP_NUMBER) { if (remapId != _vm->_paletteChangedCounter) { WRITE_LE_UINT32(remapPtr + _vm->_resourceHeaderSize, _vm->_paletteChangedCounter); srcPtr = getWizStatePaletteDataPrim(globNum, state); if (!srcPtr) error("Wiz::drawAWizPrimEx(): %d state %d missing palette block", globNum, state); const byte *palPtr = nullptr; if (_vm->_game.heversion >= 99) { palPtr = _vm->_hePalettes + _vm->_hePaletteSlot; } else { palPtr = _vm->_currentPalette; } _vm->buildRemapTable(remapPtr + _vm->_resourceHeaderSize + 4, palPtr, srcPtr + _vm->_resourceHeaderSize); } } } // Get the dest pointer... if (flags & (kWRFPrint | kWRFAlloc)) { destWidth = srcWidth; destHeight = srcHeight; if (_uses16BitColor) { destPtr = WizPxShrdBufferD(malloc(destWidth * destHeight * sizeof(WizRawPixel16)), true); } else { destPtr = WizPxShrdBufferD(malloc(destWidth * destHeight * sizeof(WizRawPixel8)), true); } if (!destPtr()) { warning("Wiz::drawAWizPrimEx(): Not enough memory for image operation (print / other)"); return WizPxShrdBuffer(); } else if (flags & kWRFAlloc) { memset8BppConversion( destPtr(), _vm->_game.heversion < 95 ? 0x05 : _vm->VAR(_vm->VAR_WIZ_TRANSPARENT_COLOR), destWidth * destHeight, optionalColorConversionTable); } } else { if (optionalBitmapOverride) { destWidth = optionalBitmapOverride->bitmapWidth; destHeight = optionalBitmapOverride->bitmapHeight; destPtr = optionalBitmapOverride->bufferPtr; markUpdates = false; } else { VirtScreen *pvs = &_vm->_virtscr[kMainVirtScreen]; destWidth = pvs->w; destHeight = pvs->h; if (flags & kWRFForeground) { destPtr = WizPxShrdBuffer(pvs->getPixels(0, pvs->topline), false); } else { destPtr = WizPxShrdBuffer(pvs->getBackPixels(0, pvs->topline), false); } } } // Make the clipping rect equal to the limits of the draw buffer... clipRect.left = 0; clipRect.top = 0; clipRect.right = destWidth - 1; clipRect.bottom = destHeight - 1; if (optionalClipRect && (!(flags & (kWRFPrint | kWRFAlloc)))) { if (!findRectOverlap(&clipRect, optionalClipRect)) { return WizPxShrdBuffer(); } } // Decompress the image... if ((_vm->_game.heversion > 99 || _vm->_isHE995) && zbufferImage) { WizSimpleBitmap sbZBuffer; sbZBuffer.bitmapHeight = 0; sbZBuffer.bitmapWidth = 0; sbZBuffer.bufferPtr = WizPxShrdBuffer(); dwSetSimpleBitmapStructFromImage(zbufferImage, 0, &sbZBuffer); // Validate destination for z-buffer... if ((destWidth != sbZBuffer.bitmapWidth) || (destHeight != sbZBuffer.bitmapHeight)) { error("Wiz::drawAWizPrimEx(): destination size must match z-buffer size d:%dx%d z:%dx%d", destWidth, destHeight, sbZBuffer.bitmapWidth, sbZBuffer.bitmapHeight); } WizSimpleBitmap sbDst; sbDst.bufferPtr = destPtr; sbDst.bitmapWidth = destWidth; sbDst.bitmapHeight = destHeight; if (srcComp != kWCTTRLE) { pgDrawImageWith16BitZBuffer(&sbDst, &sbZBuffer, srcData + _vm->_resourceHeaderSize, x, y, z, srcWidth, srcHeight, &clipRect); } } else if (srcComp == kWCTTRLE) { if (flags & kWRFZPlaneOn) { if (_vm->_game.heversion > 95 && _vm->_gdi->_numZBuffer <= 1) { error("Wiz::drawAWizPrimEx(): No zplane %d (limit 0 to %d)", 1, (_vm->_gdi->_numZBuffer - 1)); } auxDrawZplaneFromTRLEImage(_vm->getMaskBuffer(0, 0, 1), srcData + _vm->_resourceHeaderSize, destWidth, destHeight, x, y, srcWidth, srcHeight, &clipRect, kWZOIgnore, kWZOSet); } else if (flags & kWRFZPlaneOff) { if (_vm->_game.heversion > 95 && _vm->_gdi->_numZBuffer <= 1) { error("Wiz::drawAWizPrimEx(): No zplane %d (limit 0 to %d)", 1, (_vm->_gdi->_numZBuffer - 1)); } auxDrawZplaneFromTRLEImage(_vm->getMaskBuffer(0, 0, 1), srcData + _vm->_resourceHeaderSize, destWidth, destHeight, x, y, srcWidth, srcHeight, &clipRect, kWZOIgnore, kWZOClear); } else if (_vm->_game.heversion <= 98 && !(flags & (kWRFHFlip | kWRFVFlip))) { if (flags & kWRFRemap) { auxDecompRemappedTRLEImage( destPtr(), srcData + _vm->_resourceHeaderSize, destWidth, destHeight, x, y, srcWidth, srcHeight, &clipRect, remapPtr + _vm->_resourceHeaderSize + 4, optionalColorConversionTable); } else if (!shadowPtr) { auxDecompTRLEImage( destPtr(), srcData + _vm->_resourceHeaderSize, destWidth, destHeight, x, y, srcWidth, srcHeight, &clipRect, optionalColorConversionTable); } else { auxDecompMixColorsTRLEImage( destPtr(), srcData + _vm->_resourceHeaderSize, destWidth, destHeight, x, y, srcWidth, srcHeight, &clipRect, shadowPtr, optionalColorConversionTable); } } else { const byte *dataPtr = nullptr; if (shadowPtr) dataPtr = shadowPtr; if (flags & kWRFRemap) dataPtr = remapPtr + _vm->_resourceHeaderSize + 4; trleFLIPDecompressImage( destPtr(), srcData + _vm->_resourceHeaderSize, destWidth, destHeight, x, y, srcWidth, srcHeight, &clipRect, flags, dataPtr, optionalColorConversionTable, optionalICmdPtr); } } else { int transColorOverride; const byte *dataPtr = nullptr; if (shadowPtr) dataPtr = shadowPtr; if (flags & kWRFRemap) dataPtr = remapPtr + _vm->_resourceHeaderSize + 4; if (_vm->_game.heversion >= 95 && doesRawWizStateHaveTransparency(globNum, state)) { transColorOverride = _vm->VAR(_vm->VAR_WIZ_TRANSPARENT_COLOR); } else { transColorOverride = -1; } if (_uses16BitColor && srcComp != kWCTNone16Bpp && srcComp != kWCTNone16BppBigEndian) { if (srcComp == kWCTNone) { pgDraw8BppFormatImage( destPtr(), (byte *)(srcData + _vm->_resourceHeaderSize), destWidth, destHeight, x, y, srcWidth, srcHeight, &clipRect, flags, dataPtr, transColorOverride, optionalColorConversionTable); } else { error("Wiz::drawAWizPrimEx(): Raw data type mismatch for mode %d vs %d", srcComp, kWCTNone16Bpp); } } else { if (_vm->_game.heversion > 99 || _vm->_isHE995) { if (optionalColorConversionTable && ((WizRawPixel *)_vm->getHEPaletteSlot(1) != optionalColorConversionTable)) { flags |= kWRFRemap; dataPtr = (const byte *)optionalColorConversionTable; } } else { if (!_uses16BitColor && srcComp != kWCTNone) { error("Wiz::drawAWizPrimEx(): Raw data type mismatch for mode %d vs %d", srcComp, kWCTNone); } } // Use the native transfer function... pgDrawRawDataFormatImage( destPtr(), (WizRawPixel *)(srcData + _vm->_resourceHeaderSize), destWidth, destHeight, x, y, srcWidth, srcHeight, &clipRect, flags, dataPtr, transColorOverride); } } // Verify if it's a printing operation... if (flags & kWRFPrint) { warning("Wiz::drawAWizPrimEx(): Printing not yet supported"); if (_vm->_game.heversion <= 99 || (flags & kWRFAlloc) == 0) destPtr = WizPxShrdBuffer(); } else { if (!(flags & kWRFAlloc) && markUpdates) { destRect.left = x; destRect.top = y; destRect.right = x + srcWidth - 1; destRect.bottom = y + srcHeight - 1; if (findRectOverlap(&destRect, &clipRect)) { // If neither foreground or background, copy to both if ((flags & kWRFBackground) || ((flags & (kWRFBackground | kWRFForeground)) == 0)) { _vm->backgroundToForegroundBlit(destRect); } else { ++destRect.bottom; _vm->markRectAsDirty(kMainVirtScreen, destRect); } } } } return destPtr; } void Wiz::buildAWiz(const WizPxShrdBuffer &bufPtr, int bufWidth, int bufHeight, const byte *palettePtr, const Common::Rect *rectPtr, int compressionType, int globNum, int transparentColor) { int dataSize, globSize, dataOffset, counter, height, width; Common::Rect compRect; byte *ptr; compRect.left = 0; compRect.top = 0; compRect.right = bufWidth - 1; compRect.bottom = bufHeight - 1; dataSize = 0; if (rectPtr) { if (!findRectOverlap(&compRect, rectPtr)) { error("Wiz::buildAWiz(): Build wiz incorrect size (%d,%d,%d,%d)", rectPtr->left, rectPtr->top, rectPtr->right, rectPtr->bottom); } } // Force the compression type if in hi-color mode... if (_uses16BitColor) { compressionType = kWCTNone16Bpp; } // Estimate the size of the wiz... globSize = (_vm->_resourceHeaderSize * 3) + 12; // AWIZ, WIZH + data (12), WIZD if (palettePtr) { globSize += (_vm->_resourceHeaderSize * 2) + 768 + 256 + 4; // RGBS + 768, RMAP + 256 + 4 } if (compressionType == kWCTTRLE) { dataSize = trleCompressImageArea( nullptr, bufPtr(), bufWidth, compRect.left, compRect.top, compRect.right, compRect.bottom, (WizRawPixel)transparentColor); } else if (isUncompressedFormatTypeID(compressionType)) { dataSize = ((getRectWidth(&compRect) * getRectHeight(&compRect)) * sizeof(WizRawPixel)); } else { error("Wiz::buildAWiz(): Unknown compression type %d", compressionType); } if (_vm->_game.heversion > 90) { // Make sure that the resource is even sized... if (dataSize & 1) { dataSize++; } } // Finalize the resource size... globSize += dataSize; // Finally build the wiz... ptr = (byte *)_vm->_res->createResource(rtImage, globNum, globSize); dataOffset = 0; // AWIZ block WRITE_BE_UINT32(ptr + 0, MKTAG('A', 'W', 'I', 'Z')); WRITE_BE_UINT32(ptr + 4, globSize); dataOffset += _vm->_resourceHeaderSize; // WIZH WRITE_BE_UINT32(ptr + dataOffset + 0, MKTAG('W', 'I', 'Z', 'H')); WRITE_BE_UINT32(ptr + dataOffset + 4, (12 + _vm->_resourceHeaderSize)); dataOffset += _vm->_resourceHeaderSize; WRITE_LE_UINT32(ptr + dataOffset, compressionType); dataOffset += 4; // COMPRESSION-TYPE WRITE_LE_UINT32(ptr + dataOffset, getRectWidth(&compRect)); dataOffset += 4; // WIDTH WRITE_LE_UINT32(ptr + dataOffset, getRectHeight(&compRect)); dataOffset += 4; // HEIGHT if (palettePtr) { // RGBS WRITE_BE_UINT32(ptr + dataOffset + 0, MKTAG('R', 'G', 'B', 'S')); WRITE_BE_UINT32(ptr + dataOffset + 4, (768 + _vm->_resourceHeaderSize)); dataOffset += _vm->_resourceHeaderSize; memcpy(ptr + dataOffset, palettePtr, 768); dataOffset += 768; // RMAP WRITE_BE_UINT32(ptr + dataOffset + 0, MKTAG('R', 'M', 'A', 'P')); WRITE_BE_UINT32(ptr + dataOffset + 4, (256 + 4 + _vm->_resourceHeaderSize)); dataOffset += _vm->_resourceHeaderSize; WRITE_LE_UINT32(ptr + dataOffset, 0); dataOffset += 4; // Remapped flag for (counter = 0; counter < 256; counter++) { *(ptr + dataOffset) = counter; dataOffset++; } } // WIZD WRITE_BE_UINT32(ptr + dataOffset + 0, MKTAG('W', 'I', 'Z', 'D')); WRITE_BE_UINT32(ptr + dataOffset + 4, (dataSize + _vm->_resourceHeaderSize)); dataOffset += _vm->_resourceHeaderSize; if (compressionType == kWCTTRLE) { if (!_uses16BitColor) { trleCompressImageArea( ptr + dataOffset, bufPtr(), bufWidth, compRect.left, compRect.top, compRect.right, compRect.bottom, (byte)transparentColor); } else { error("Wiz::buildAWiz(): Incorrect type %d for current pixel mode 16 bit", compressionType); } } else { WizSimpleBitmap srcBitmap, dstBitmap; Common::Rect dstRect; // Src setup srcBitmap.bufferPtr = bufPtr; srcBitmap.bitmapWidth = bufWidth; srcBitmap.bitmapHeight = bufHeight; // Dst setup width = getRectWidth(&compRect); height = getRectHeight(&compRect); dstBitmap.bufferPtr = WizPxShrdBuffer(ptr + dataOffset, false); dstBitmap.bitmapWidth = width; dstBitmap.bitmapHeight = height; dstRect.left = 0; dstRect.top = 0; dstRect.right = width - 1; dstRect.bottom = height - 1; // Call the blit primitive... pgSimpleBlit(&dstBitmap, &dstRect, &srcBitmap, &compRect); } dataOffset += dataSize; if (globSize != dataOffset) { error("Wiz::buildAWiz(): WIZ size mismatch!"); } } int Wiz::pixelHitTestWiz(int image, int state, int x, int y, int32 flags) { return pixelHitTestWizPrim(image, state, x, y, flags); } int Wiz::pixelHitTestWizPrim(int globNum, int state, int x, int y, int32 flags) { int outValue = 0; if (((ScummEngine_v90he *)_vm)->_logicHE && ((ScummEngine_v90he *)_vm)->_logicHE->overrideImagePixelHitTest(&outValue, globNum, state, x, y, flags)) { return outValue; } int srcComp, srcWidth, srcHeight; byte *srcData; byte *headerPtr; // Get the header (width, height + compression)... headerPtr = getWizStateHeaderPrim(globNum, state); srcComp = READ_LE_UINT32(headerPtr + _vm->_resourceHeaderSize + 0); srcWidth = READ_LE_UINT32(headerPtr + _vm->_resourceHeaderSize + 4); srcHeight = READ_LE_UINT32(headerPtr + _vm->_resourceHeaderSize + 8); // If the compression type is TRLE... if (srcComp == kWCTTRLE) { srcData = getWizStateDataPrim(globNum, state); // Execute the color lookup... int pixel = auxPixelHitTestTRLEImageRelPos( srcData + _vm->_resourceHeaderSize, x, y, srcWidth, srcHeight, _vm->VAR(_vm->VAR_WIZ_TRANSPARENT_COLOR)); if (!(_vm->_game.heversion > 99 || _vm->_isHE995)) { return pixel; } int compType = getWizCompressionType(globNum, state); if (LITTLE_ENDIAN_WIZ(compType)) { return pixel; } else { if (WIZ_16BPP(compType)) { return SWAP_BYTES_16(pixel); } else { return pixel; } } } else if (isUncompressedFormatTypeID(srcComp)) { WizSimpleBitmap srcBitmap; srcData = getWizStateDataPrim(globNum, state); srcBitmap.bufferPtr = WizPxShrdBuffer(srcData + _vm->_resourceHeaderSize, false); srcBitmap.bitmapWidth = srcWidth; srcBitmap.bitmapHeight = srcHeight; int pixel = pgReadPixel(&srcBitmap, x, y, _vm->VAR(_vm->VAR_WIZ_TRANSPARENT_COLOR)); if (!(_vm->_game.heversion > 99 || _vm->_isHE995)) { return pixel; } int compType = getWizCompressionType(globNum, state); if (LITTLE_ENDIAN_WIZ(compType)) { return pixel; } else { if (WIZ_16BPP(compType)) { return SWAP_BYTES_16(pixel); } else { return pixel; } } } else { return _vm->VAR(_vm->VAR_WIZ_TRANSPARENT_COLOR); } } int Wiz::hitTestWiz(int image, int state, int x, int y, int32 flags) { return hitTestWizPrim(image, state, x, y, flags); } int Wiz::hitTestWizPrim(int globNum, int state, int x, int y, int32 flags) { int srcComp, srcWidth, srcHeight; const byte *srcData; const byte *headerPtr; byte *dataTmp = nullptr; int outValue = 0; if (((ScummEngine_v90he *)_vm)->_logicHE && ((ScummEngine_v90he *)_vm)->_logicHE->overrideImageHitTest(&outValue, globNum, state, x, y, flags)) { return outValue; } if (_vm->_game.heversion == 80) { dataTmp = _vm->getResourceAddress(rtImage, globNum); assert(dataTmp); headerPtr = _vm->findResourceData(MKTAG('W', 'I', 'Z', 'H'), dataTmp); assert(headerPtr); } else { headerPtr = getWizStateHeaderPrim(globNum, state); } // Get the header (width, height + compression)... srcComp = READ_LE_UINT32(headerPtr + _vm->_resourceHeaderSize + 0); srcWidth = READ_LE_UINT32(headerPtr + _vm->_resourceHeaderSize + 4); srcHeight = READ_LE_UINT32(headerPtr + _vm->_resourceHeaderSize + 8); if (_vm->_game.heversion == 80) { if (srcComp != kWCTTRLE) error("Wiz::hitTestWizPrim(): resource %d has invalid compression type %d", globNum, srcComp); srcData = _vm->findResourceData(MKTAG('W', 'I', 'Z', 'D'), dataTmp); return auxHitTestTRLEImageRelPos(srcData + _vm->_resourceHeaderSize, x, y, srcWidth, srcHeight); } if (_vm->_game.heversion > 98) { // Flip the test coords if needed and do simple point rejection... if (flags & kWRFHFlip) { x = ((srcWidth - 1) - x); if (x < 0) { return 0; } } else if ((x >= srcWidth) || (x < 0)) { return 0; } if (flags & kWRFVFlip) { y = ((srcHeight - 1) - y); if (y < 0) { return 0; } } else if ((y >= srcHeight) || (y < 0)) { return 0; } } // If the compression type is TRLE... if (srcComp == kWCTTRLE) { srcData = getWizStateDataPrim(globNum, state); return auxHitTestTRLEImageRelPos(srcData + _vm->_resourceHeaderSize, x, y, srcWidth, srcHeight); } else if (_vm->_game.heversion > 98 && isUncompressedFormatTypeID(srcComp)) { WizSimpleBitmap srcBitmap; srcData = getWizStateDataPrim(globNum, state) + _vm->_resourceHeaderSize; srcBitmap.bufferPtr = WizPxShrdBuffer(const_cast(srcData), false); srcBitmap.bitmapWidth = srcWidth; srcBitmap.bitmapHeight = srcHeight; return (_vm->VAR(_vm->VAR_WIZ_TRANSPARENT_COLOR) != pgReadPixel(&srcBitmap, x, y, _vm->VAR(_vm->VAR_WIZ_TRANSPARENT_COLOR))) ? 1 : 0; } else { return 0; } } void Wiz::processWizImagePolyCaptureCmd(const WizImageCommand *params) { int polygon1, polygon2, compressionType, srcImage = 0, shadow = 0, state = 0; bool isHintColor = false; int hintColor = 0; // Get all the options... if (params->actionFlags & kWAFPolygon) { polygon1 = params->polygon; } else { error("Wiz::processWizImagePolyCaptureCmd(): Image capture poly: no polygon 1 specified."); } if (params->actionFlags & kWAFPolygon2) { polygon2 = params->polygon2; } else { polygon2 = polygon1; } if (params->actionFlags & kWAFCompressionType) { compressionType = params->compressionType; } else { compressionType = kWCTNone; } if (params->actionFlags & kWAFShadow) { shadow = params->shadow; } if (params->actionFlags & kWAFDestImage) { error("Wiz::processWizImagePolyCaptureCmd(): destination 'image' not supported, use 'source image'"); } if (params->actionFlags & kWAFSourceImage) { srcImage = params->sourceImage; } if (params->actionFlags & kWAFState) { state = params->state; } if (params->actionFlags & kWAFProperty) { if (params->propertyNumber == 1) { // Color hint property if (!shadow) { debug(7, "Wiz::processWizImagePolyCaptureCmd(): color hint does nothing for an unfiltered scale."); } isHintColor = true; hintColor = params->propertyValue; } } // Validate the parameters... bool poly1Found = false; bool poly2Found = false; for (int polyIndex = 0; polyIndex < ARRAYSIZE(_polygons); ++polyIndex) { if (polygon1 == _polygons[polyIndex].id) { poly1Found = true; polygon1 = polyIndex; if (_polygons[polyIndex].numPoints != 5) error("Wiz::processWizImagePolyCaptureCmd(): Invalid point count"); } if (polygon2 == _polygons[polyIndex].id) { poly2Found = true; polygon2 = polyIndex; if (_polygons[polyIndex].numPoints != 5) error("Wiz::processWizImagePolyCaptureCmd(): Invalid point count"); } if (poly1Found && poly2Found) { break; } } if (!poly1Found) { error("Wiz::processWizImagePolyCaptureCmd(): Polygon %d not defined", polygon1); } if (!poly2Found) { error("Wiz::processWizImagePolyCaptureCmd(): Polygon %d not defined", polygon2); } if (_polygons[polygon1].numPoints != _polygons[polygon2].numPoints) { error("Wiz::processWizImagePolyCaptureCmd(): Polygons MUST have same number of points."); } // Create the buffers to hold the source and destination image bitmaps... WizSimpleBitmap srcBitmap, destBitmap; srcBitmap.bufferPtr = WizPxShrdBuffer(); destBitmap.bufferPtr = WizPxShrdBuffer(); // Build a bounding rect for the polys and set the appropriate sizes in the bitmaps... Common::Rect destPolyRect; Common::Rect srcPolyRect; polyBuildBoundingRect(_polygons[polygon2].points, _polygons[polygon2].numPoints, destPolyRect); destBitmap.bitmapWidth = getRectWidth(&destPolyRect); destBitmap.bitmapHeight = getRectHeight(&destPolyRect); destBitmap.bufferPtr = WizPxShrdBufferD(malloc(destBitmap.bitmapWidth * destBitmap.bitmapHeight * sizeof(WizRawPixel)), true); if (!destBitmap.bufferPtr()) { error("Wiz::processWizImagePolyCaptureCmd(): Could not allocate destination buffer"); } // Fill with transparent color... rawPixelMemset(destBitmap.bufferPtr(), _vm->VAR(_vm->VAR_WIZ_TRANSPARENT_COLOR), destBitmap.bitmapWidth * destBitmap.bitmapHeight); // Get the bound rect for the poly... polyBuildBoundingRect(_polygons[polygon1].points, _polygons[polygon1].numPoints, srcPolyRect); // We need to save the current points so that they can be offset to the correct position in // the source buffer in the case of a screen capture (since the whole screen may not be captured, // and the polygon may be offset into it)... int pointCt; Common::Point srcPoints[5]; for (pointCt = 0; pointCt < 5; ++pointCt) { srcPoints[pointCt].x = _polygons[polygon1].points[pointCt].x; srcPoints[pointCt].y = _polygons[polygon1].points[pointCt].y; } // Check for one to one rectangle, which will set up for an image copy later... bool oneToOneRect = false; // See if they are both rectangles; the '4' notes the maximum amount of vertexes we can have... if (polyIsRectangle(_polygons[polygon1].points, 4) && polyIsRectangle(_polygons[polygon2].points, 4)) { // Check if the points are all the same, and bail if so... for (pointCt = 0; pointCt < 4; ++pointCt) { if ((_polygons[polygon1].points[pointCt].x != _polygons[polygon2].points[pointCt].x) || (_polygons[polygon1].points[pointCt].y != _polygons[polygon2].points[pointCt].y)) { break; } } if (pointCt == 4) { oneToOneRect = true; } } // If there is a source image, get it, otherwise capture from the screen... if (srcImage) { // get the wiz size Common::Rect clipRect; int32 w, h; getWizImageDim(srcImage, state, w, h); clipRect.left = 0; clipRect.top = 0; clipRect.right = w; clipRect.bottom = h; // Make sure the capture area isn't outside or bigger than the source image... Common::Rect testRect; combineRects(&testRect, &srcPolyRect, &clipRect); if ((getRectWidth(&testRect) * getRectHeight(&testRect)) > (getRectWidth(&clipRect) * getRectHeight(&clipRect))) { error("Wiz::processWizImagePolyCaptureCmd(): Specified polygon captures points outside bounds of source image"); } // Clip poly to image and verify it's within the image... if (!findRectOverlap(&srcPolyRect, &clipRect)) { error("Wiz::processWizImagePolyCaptureCmd(): Specified polygon doesn't intersect source image."); } srcBitmap.bitmapWidth = getRectWidth(&srcPolyRect); srcBitmap.bitmapHeight = getRectHeight(&srcPolyRect); if ((srcBitmap.bitmapWidth == 0) || (srcBitmap.bitmapHeight == 0)) { error("Wiz::processWizImagePolyCaptureCmd(): Poly or source image invalid"); } // Create the bitmap... srcBitmap.bufferPtr = WizPxShrdBufferD(malloc(srcBitmap.bitmapWidth * srcBitmap.bitmapHeight * sizeof(WizRawPixel)), true); if (!srcBitmap.bufferPtr()) { error("Wiz::processWizImagePolyCaptureCmd(): Could not allocate source buffer"); } // Set it all to transparent... rawPixelMemset(srcBitmap.bufferPtr(), _vm->VAR(_vm->VAR_WIZ_TRANSPARENT_COLOR), srcBitmap.bitmapWidth * srcBitmap.bitmapHeight); drawAWiz(srcImage, state, 0, 0, 0, 0, 0, 0, &srcPolyRect, 0, &srcBitmap); } else { // If we're here, we are handling a screen capture... VirtScreen *pvs = &_vm->_virtscr[kMainVirtScreen]; int windowWidth = pvs->w; int windowHeight = pvs->h; // Intersect the bound rect and the VirtScreen rect... Common::Rect clipRect; clipRect.left = 0; clipRect.top = 0; clipRect.right = windowWidth - 1; clipRect.bottom = windowHeight - 1; if (!findRectOverlap(&srcPolyRect, &clipRect)) { error("Wiz::processWizImagePolyCaptureCmd(): Specified polygon doesn't intersect screen."); } srcBitmap.bitmapWidth = getRectWidth(&srcPolyRect); srcBitmap.bitmapHeight = getRectHeight(&srcPolyRect); if ((srcBitmap.bitmapWidth == 0) || (srcBitmap.bitmapHeight == 0)) { error("Wiz::processWizImagePolyCaptureCmd(): Specified screen rectangle invalid."); } // Create the bitmap... srcBitmap.bufferPtr = WizPxShrdBufferD(malloc(srcBitmap.bitmapWidth * srcBitmap.bitmapHeight * sizeof(WizRawPixel)), true); if (!srcBitmap.bufferPtr()) { error("Wiz::processWizImagePolyCaptureCmd(): Could not allocate source buffer"); } // Set it all to transparent... rawPixelMemset(srcBitmap.bufferPtr(), _vm->VAR(_vm->VAR_WIZ_TRANSPARENT_COLOR), srcBitmap.bitmapWidth * srcBitmap.bitmapHeight); // Fill it with screen data... WizRawPixel *screenPtr = (WizRawPixel *)pvs->getPixels(srcPolyRect.left, srcPolyRect.top); WizPxShrdBuffer destPtr = srcBitmap.bufferPtr; int screenRowLen = 640; int destRowLen = srcBitmap.bitmapWidth; if (_uses16BitColor) { WizRawPixel16 *screen16 = (WizRawPixel16 *)screenPtr; WizRawPixel16 *dest16 = (WizRawPixel16 *)destPtr(); for (int i = 0; i < srcBitmap.bitmapHeight; ++i) { memcpy(dest16, screen16, destRowLen); screen16 += screenRowLen; dest16 += destRowLen; } } else { WizRawPixel8 *screen8 = (WizRawPixel8 *)screenPtr; WizRawPixel8 *dest8 = (WizRawPixel8 *)destPtr(); for (int i = 0; i < srcBitmap.bitmapHeight; ++i) { memcpy(dest8, screen8, destRowLen); screen8 += screenRowLen; dest8 += destRowLen; } } // Translate the polygon so it is in the correct place in the buffer... int dx = 0, dy = 0; dx = 0 - srcPolyRect.left; dy = 0 - srcPolyRect.top; polyMovePolygonPoints(srcPoints, _polygons[polygon1].numPoints, dx, dy); } // If there is an xmap shadow, perform a filtered warp... if (shadow) { // Het the color map, bypass the header information... const byte *xmapColorTable = getColorMixBlockPtrForWiz(shadow); if (!xmapColorTable) { error("Wiz::processWizImagePolyCaptureCmd(): Shadow specified but not present in image."); } xmapColorTable += _vm->_resourceHeaderSize; WarpWizPoint polypoints[5]; for (int i = 0; i < 5; i++) { WarpWizPoint tmp(_polygons[polygon2].points[i]); polypoints[i] = tmp; } WarpWizPoint srcWarpPoints[5]; for (int i = 0; i < 5; i++) { WarpWizPoint tmp(srcPoints[i]); srcWarpPoints[i] = tmp; } warpNPt2NPtNonClippedWarpFiltered( &destBitmap, polypoints, &srcBitmap, srcWarpPoints, _polygons[polygon1].numPoints, _vm->VAR(_vm->VAR_WIZ_TRANSPARENT_COLOR), xmapColorTable, isHintColor, (WizRawPixel)hintColor); } else if (oneToOneRect) { // If a one to one copy is performed, just copy this bitmap... memcpy(destBitmap.bufferPtr(), srcBitmap.bufferPtr(), destBitmap.bitmapHeight * destBitmap.bitmapWidth); } else { // Otherwise fallback to regular warping... WarpWizPoint polypoints[5]; for (int i = 0; i < 5; i++) { WarpWizPoint tmp(_polygons[polygon2].points[i]); polypoints[i] = tmp; } WarpWizPoint srcWarpPoints[5]; for (int i = 0; i < 5; i++) { WarpWizPoint tmp(srcPoints[i]); srcWarpPoints[i] = tmp; } warpNPt2NPtNonClippedWarp( &destBitmap, polypoints, &srcBitmap, srcWarpPoints, _polygons[polygon1].numPoints, _vm->VAR(_vm->VAR_WIZ_TRANSPARENT_COLOR)); } // Now build a Wiz with the destination bitmap and throw the bitmaps away... srcBitmap.bufferPtr = WizPxShrdBuffer(); uint8 *palPtr = nullptr; if (_vm->_game.heversion >= 99) { palPtr = _vm->_hePalettes + _vm->_hePaletteSlot; } else { palPtr = _vm->_currentPalette; } buildAWiz(destBitmap.bufferPtr, destBitmap.bitmapWidth, destBitmap.bitmapHeight, palPtr, &destPolyRect, compressionType, params->image, _vm->VAR(_vm->VAR_WIZ_TRANSPARENT_COLOR)); destBitmap.bufferPtr = WizPxShrdBuffer(); _vm->_res->setModified(rtImage, params->image); } void Wiz::flushAWizBuffer() { if (_wizBufferIndex == 0) return; for (int i = 0; i < _wizBufferIndex; i++) { drawAWiz( _wizBuffer[i].image, _wizBuffer[i].state, _wizBuffer[i].x, _wizBuffer[i].y, _wizBuffer[i].z, _wizBuffer[i].flags, _wizBuffer[i].shadow, _wizBuffer[i].zbuffer, 0, _wizBuffer[i].palette, 0); } _wizBufferIndex = 0; } void Wiz::loadWizCursor(int resId, int palette, bool useColor) { int32 x, y; getWizSpot(resId, 0, x, y); if (x < 0) { x = 0; } else if (x > 32) { x = 32; } if (y < 0) { y = 0; } else if (y > 32) { y = 32; } WizRawPixel *colorConversionTable = nullptr; if (palette != 0) colorConversionTable = (WizRawPixel *) _vm->getHEPaletteSlot(palette); WizPxShrdBuffer cursorBuffer = drawAWizPrim(resId, 0, 0, 0, 0, 0, 0, nullptr, kWRFAlloc, nullptr, colorConversionTable); byte *cursor = (byte*)cursorBuffer(); int32 cw, ch; getWizImageDim(resId, 0, cw, ch); // Hello! This is a hack :-D // In the original code, when useColor is false, the engine // instructs the OS to set the cursor to black and white only. // We obtain the same effect doing it like this... if (!useColor) { if (_vm->_bytesPerPixel == 1) { for (int i = 0; i < ch * cw; i++) { if (cursor[i] != 0 && cursor[i] != 5) cursor[i] = 15; } } else { for (int i = 0; i < ch * cw; i++) { if (((uint16 *)cursor)[i] != 0 && ((uint16 *)cursor)[i] != 5) ((uint16 *)cursor)[i] = 0x7FFF; } } const byte black[3] = { 0x00, 0x00, 0x00 }; const byte white[3] = { 0xFF, 0xFF, 0xFF }; CursorMan.replaceCursorPalette(black, 0, 1); CursorMan.replaceCursorPalette(white, 15, 1); } _vm->setCursorHotspot(x, y); _vm->setCursorFromBuffer(cursor, cw, ch, cw * _vm->_bytesPerPixel); // Since we set up cursor palette for default cursor, disable it now... if (useColor) { CursorMan.disableCursorPalette(true); } } #define ADD_REQUIRED_IMAGE(whatImageIsRequired) { \ if (ARRAYSIZE(requiredImages) <= (requiredImageCount + 1)) { \ error("Wiz::processWizImageDrawCmd(): Too many required images for image operation (limit %d).", ARRAYSIZE(requiredImages)); \ } \ requiredImages[requiredImageCount] = whatImageIsRequired; \ ++requiredImageCount; \ } void Wiz::processWizImageDrawCmd(const WizImageCommand *params) { int shadowImage, state, angle, scale, paletteNumber, sourceImage; int zbufferImage = 0; const WizRawPixel *colorConversionTablePtr; Common::Rect *optionalRect; WizSimpleBitmap *destBitmap; WizSimpleBitmap fakeBitmap; Common::Rect clipRect; int32 flags; Common::Point pt; int requiredImages[5] = {0, 0, 0, 0, 0}; int requiredImageCount = 0; ADD_REQUIRED_IMAGE(params->image); if (params->actionFlags & kWAFSourceImage) { sourceImage = params->sourceImage; ADD_REQUIRED_IMAGE(sourceImage); } else { sourceImage = 0; } if (params->actionFlags & kWAFPalette) { paletteNumber = params->palette; } else { paletteNumber = 0; } if (params->actionFlags & kWAFScale) { scale = params->scale; } else { scale = 256; } if (params->actionFlags & kWAFAngle) { angle = params->angle; } else { angle = 0; } if (params->actionFlags & kWAFState) { state = params->state; } else { state = 0; } if (params->actionFlags & kWAFFlags) { flags = params->flags; } else { flags = 0; } if (params->actionFlags & kWAFSpot) { pt.x = params->xPos; pt.y = params->yPos; } else { pt.x = 0; pt.y = 0; } if (params->actionFlags & kWAFShadow) { shadowImage = params->shadow; ADD_REQUIRED_IMAGE(shadowImage); } else { shadowImage = 0; } if (_vm->_game.heversion > 99 || _vm->_isHE995) { if (params->actionFlags & kWAFZBufferImage) { zbufferImage = params->zbufferImage; ADD_REQUIRED_IMAGE(zbufferImage); } } if (params->actionFlags & kWAFRect) { clipRect.left = params->box.left; clipRect.top = params->box.top; clipRect.right = params->box.right; clipRect.bottom = params->box.bottom; optionalRect = &clipRect; } else { optionalRect = nullptr; } if (params->actionFlags & kWAFDestImage) { ADD_REQUIRED_IMAGE(params->destImageNumber); } if (requiredImageCount > 0) { // Mark the all the image resources as stuck... for (int i = 0; i < requiredImageCount; i++) { _vm->_res->lock(rtImage, requiredImages[i]); } // Make sure that the resources are in RAM by requesting their address... for (int i = 0; i < requiredImageCount; i++) { _vm->ensureResourceLoaded(rtImage, requiredImages[i]); } // Mark the all the image resources as NOT stuck... for (int i = 0; i < requiredImageCount; i++) { _vm->_res->unlock(rtImage, requiredImages[i]); } // Validate that all of the images are now on the heap! for (int i = 0; i < requiredImageCount; i++) { if (!_vm->getResourceAddress(rtImage, requiredImages[i])) { error("Wiz::processWizImageDrawCmd(): Image %d missing for image operation", requiredImages[i]); } } // Check if the images are in their native format and swap them if needed... if (_vm->_game.heversion > 99) { for (int i = 0; i < requiredImageCount; i++) { ensureNativeFormatImageForState(requiredImages[i], state); } } } if (params->actionFlags & kWAFDestImage) { // Get the rendering surface for this image... if (!dwSetSimpleBitmapStructFromImage(params->destImageNumber, 0, &fakeBitmap)) { error("Wiz::processWizImageDrawCmd(): Image %d is invalid for rendering into", params->destImageNumber); } destBitmap = &fakeBitmap; } else { destBitmap = nullptr; } if ((_vm->_game.heversion > 99 || _vm->_isHE995) && (params->actionFlags & kWAFRemapList)) { processWizImageModifyCmd(params); flags |= kWRFRemap; } // Dispatch the command... if (!_vm->_fullRedraw || destBitmap != 0) { if (sourceImage != 0) { dwAltSourceDrawWiz( params->image, state, pt.x, pt.y, sourceImage, 0, flags, paletteNumber, optionalRect, destBitmap); } else { if (!(params->actionFlags & (kWAFScale | kWAFAngle))) { drawAWizEx( params->image, state, pt.x, pt.y, params->zPos, flags, shadowImage, zbufferImage, optionalRect, paletteNumber, destBitmap, params); } else { if (paletteNumber) { colorConversionTablePtr = (WizRawPixel *)_vm->getHEPaletteSlot(paletteNumber); } else { colorConversionTablePtr = nullptr; } dwHandleComplexImageDraw( params->image, state, pt.x, pt.y, shadowImage, angle, scale, optionalRect, flags, destBitmap, colorConversionTablePtr); } } } else { if (sourceImage != 0 || (params->actionFlags & (kWAFScale | kWAFAngle))) error("Can't do this command in the enter script"); bufferAWiz(params->image, state, pt.x, pt.y, params->zPos, flags, shadowImage, zbufferImage, paletteNumber); } } #undef ADD_REQUIRED_IMAGE void Wiz::dwCreateRawWiz(int imageNum, int w, int h, int flags, int bitsPerPixel, int optionalSpotX, int optionalSpotY) { int compressionType, wizdSize; int globSize = _vm->_resourceHeaderSize; // AWIZ header size globSize += WIZBLOCK_WIZH_SIZE; if (flags & kCWFPalette) { globSize += WIZBLOCK_RGBS_SIZE; } if (flags & kCWFSpot) { globSize += WIZBLOCK_SPOT_SIZE; } if (flags & kCWFRemapTable) { globSize += WIZBLOCK_RMAP_SIZE; } globSize += _vm->_resourceHeaderSize; // WIZD header size wizdSize = (w * h * (bitsPerPixel / 8)); globSize += wizdSize; uint8 *writePtr = _vm->_res->createResource(rtImage, imageNum, globSize); if (!writePtr) { _vm->VAR(_vm->VAR_OPERATION_FAILURE) = -1; return; } else { _vm->VAR(_vm->VAR_OPERATION_FAILURE) = 0; } switch (bitsPerPixel) { case 8: compressionType = kWCTNone; break; case 16: compressionType = kWCTNone16Bpp; break; default: error("Unsupported image bits size %d", bitsPerPixel); break; } WRITE_BE_UINT32(writePtr, MKTAG('A', 'W', 'I', 'Z')); writePtr += 4; WRITE_BE_UINT32(writePtr, globSize); writePtr += 4; WRITE_BE_UINT32(writePtr, MKTAG('W', 'I', 'Z', 'H')); writePtr += 4; WRITE_BE_UINT32(writePtr, WIZBLOCK_WIZH_SIZE); writePtr += 4; WRITE_LE_UINT32(writePtr, compressionType); writePtr += 4; WRITE_LE_UINT32(writePtr, w); writePtr += 4; WRITE_LE_UINT32(writePtr, h); writePtr += 4; if (flags & kCWFPalette) { const uint8 *palPtr; if (_vm->_game.heversion >= 99) { palPtr = _vm->_hePalettes + _vm->_hePaletteSlot; } else { palPtr = _vm->_currentPalette; } WRITE_BE_UINT32(writePtr, MKTAG('R', 'G', 'B', 'S')); writePtr += 4; WRITE_BE_UINT32(writePtr, WIZBLOCK_RGBS_SIZE); writePtr += 4; memcpy(writePtr, palPtr, WIZBLOCK_RGBS_DATA_SIZE); writePtr += WIZBLOCK_RGBS_DATA_SIZE; } if (flags & kCWFSpot) { WRITE_BE_UINT32(writePtr, MKTAG('S', 'P', 'O', 'T')); writePtr += 4; WRITE_BE_UINT32(writePtr, WIZBLOCK_SPOT_SIZE); writePtr += 4; WRITE_LE_UINT32(writePtr + 0, optionalSpotX); WRITE_LE_UINT32(writePtr + 4, optionalSpotY); writePtr += WIZBLOCK_SPOT_DATA_SIZE; } if (flags & kCWFRemapTable) { WRITE_BE_UINT32(writePtr, MKTAG('R', 'M', 'A', 'P')); writePtr += 4; WRITE_BE_UINT32(writePtr, WIZBLOCK_RMAP_SIZE); writePtr += 4; WRITE_LE_UINT32(writePtr, 0); writePtr += 4; for (int i = 0; i < 256; ++i) { *writePtr++ = i; } } WRITE_BE_UINT32(writePtr, MKTAG('W','I', 'Z', 'D')); writePtr += 4; WRITE_BE_UINT32(writePtr, 8 + wizdSize); writePtr += 4; } bool Wiz::dwGetMultiTypeBitmapFromImageState(int imageNum, int imageState, WizMultiTypeBitmap *multiBM) { int compType, imageWidth, imageHeight; byte *wizHeader; byte *dataPtr; // Get the image header... wizHeader = getWizStateHeaderPrim(imageNum, imageState); if (!wizHeader) { memset(multiBM, 0, sizeof(WizMultiTypeBitmap)); return false; } // Double check the image header compression type... compType = READ_LE_UINT32(wizHeader + _vm->_resourceHeaderSize); if (!isUncompressedFormatTypeID(compType)) { memset(multiBM, 0, sizeof(WizMultiTypeBitmap)); return false; } imageWidth = READ_LE_UINT32(wizHeader + _vm->_resourceHeaderSize + 4); imageHeight = READ_LE_UINT32(wizHeader + _vm->_resourceHeaderSize + 8); dataPtr = getWizStateDataPrim(imageNum, imageState); if (!dataPtr) { memset(multiBM, 0, sizeof(WizMultiTypeBitmap)); return false; } // Hook up the image info to the simple bitmap info... multiBM->data = (dataPtr + _vm->_resourceHeaderSize); multiBM->width = imageWidth; multiBM->height = imageHeight; switch (compType) { case kWCTNone: multiBM->bpp = 8; multiBM->format = 8; break; case kWCTNone16Bpp: case kWCTNone16BppBigEndian: multiBM->bpp = 16; multiBM->format = 555; break; default: memset(multiBM, 0, sizeof(WizMultiTypeBitmap)); return false; break; } multiBM->stride = (multiBM->width * multiBM->bpp) / 8; return true; } bool Wiz::dwSetSimpleBitmapStructFromImage(int imageNum, int imageState, WizSimpleBitmap *destBM) { int compType, imageWidth, imageHeight; byte *wizHeader; byte *dataPtr; // Get the image header... wizHeader = (byte *)getWizStateHeaderPrim(imageNum, imageState); if (!wizHeader) { return false; } // Double check the image header compression type... compType = READ_LE_UINT32(wizHeader + _vm->_resourceHeaderSize); if (!isUncompressedFormatTypeID(compType)) { return false; } imageWidth = READ_LE_UINT32(wizHeader + _vm->_resourceHeaderSize + 4); imageHeight = READ_LE_UINT32(wizHeader + _vm->_resourceHeaderSize + 8); // Fetch the data... dataPtr = (byte *)getWizStateDataPrim(imageNum, imageState); if (!dataPtr) { return false; } // Hook up the image info to the simple bitmap info... destBM->bufferPtr = WizPxShrdBuffer(dataPtr + _vm->_resourceHeaderSize, false); destBM->bitmapWidth = imageWidth; destBM->bitmapHeight = imageHeight; return true; } int Wiz::dwTryToLoadWiz(Common::SeekableReadStream *inFile, const WizImageCommand *params) { uint32 blockSize; uint32 blockId; byte *ptr; inFile->seek(0, SEEK_SET); blockId = inFile->readUint32BE(); if ((blockId != MKTAG('A', 'W', 'I', 'Z')) && (blockId != MKTAG('M', 'U', 'L', 'T'))) { return DW_LOAD_NOT_TYPE; } blockSize = inFile->readUint32BE(); inFile->seek(-8, SEEK_CUR); ptr = _vm->_res->createResource(rtImage, params->image, blockSize); if (inFile->read(ptr, blockSize) != blockSize) { _vm->_res->nukeResource(rtImage, params->image); return DW_LOAD_READ_FAILURE; } _vm->_res->setModified(rtImage, params->image); return DW_LOAD_SUCCESS; } void Wiz::dwAltSourceDrawWiz(int maskImage, int maskState, int x, int y, int sourceImage, int sourceState, int32 flags, int paletteNumber, const Common::Rect *optionalClipRect, const WizSimpleBitmap *destBitmapPtr) { int srcBitsPerPixel, sourceCompressionType, maskCompressionType; int32 srcBitmapWidth, srcBitmapHeight, maskWidth, maskHeight; Common::Rect clipRect, destRect; WizSimpleBitmap drawBufferBitmap; WizRawPixel *conversionTable; byte *sourceBufferPtr; byte *maskDataPtr; bool markUpdates; // Get the conversion table if any... if (paletteNumber) { conversionTable = (WizRawPixel *)_vm->getHEPaletteSlot(paletteNumber); } else { if (_uses16BitColor) { conversionTable = (WizRawPixel *)_vm->getHEPaletteSlot(1); // Generic conversion table... } else { conversionTable = nullptr; } } // Get the destination bitmap... if (!destBitmapPtr) { markUpdates = true; if (flags & kWRFForeground) { pgSimpleBitmapFromDrawBuffer(&drawBufferBitmap, false); } else { pgSimpleBitmapFromDrawBuffer(&drawBufferBitmap, true); } destBitmapPtr = &drawBufferBitmap; } else { markUpdates = false; } // Check for overlap with the optional clip rectangle, if any... clipRect.left = 0; clipRect.top = 0; clipRect.right = destBitmapPtr->bitmapWidth - 1; clipRect.bottom = destBitmapPtr->bitmapHeight - 1; if (optionalClipRect) { if (!findRectOverlap(&clipRect, optionalClipRect)) { return; // We're done, there is no update region. } } // Get the source (alt) bitmap pointer, and general info... sourceCompressionType = getWizCompressionType(sourceImage, sourceState); if (!dwIsUncompressedFormatTypeID(sourceCompressionType)) { error("Wiz::dwAltSourceDrawWiz(): Source image %d must be uncompressed", sourceImage); } switch (sourceCompressionType) { default: case kWCTNone: srcBitsPerPixel = 8; // Default break; case kWCTNone16Bpp: case kWCTNone16BppBigEndian: srcBitsPerPixel = 16; break; } // Get the source wiz data pointer... sourceBufferPtr = (byte *)getWizStateDataPrim(sourceImage, sourceState); if (!sourceBufferPtr) { error("Wiz::dwAltSourceDrawWiz(): Image %d missing data block", sourceImage); } sourceBufferPtr += _vm->_resourceHeaderSize; getWizImageDim(sourceImage, sourceState, srcBitmapWidth, srcBitmapHeight); if ((destBitmapPtr->bitmapWidth != srcBitmapWidth) || (destBitmapPtr->bitmapHeight != srcBitmapHeight)) { error( "Wiz::dwAltSourceDrawWiz(): Source image %d and dest image size mismatch (%d,%d) need (%d,%d)", sourceImage, srcBitmapWidth, srcBitmapHeight, destBitmapPtr->bitmapWidth, destBitmapPtr->bitmapHeight); } // Finally get the compressed data pointer... maskCompressionType = getWizCompressionType(maskImage, maskState); if (!dwIsMaskCompatibleCompressionType(maskCompressionType)) { error("Wiz::dwAltSourceDrawWiz(): Mask image %d must be a maskable compression type", maskImage); } maskDataPtr = (byte *)getWizStateDataPrim(maskImage, maskState); if (!maskDataPtr) { error("Wiz::dwAltSourceDrawWiz(): Image %d missing data block", maskImage); } maskDataPtr += _vm->_resourceHeaderSize; getWizImageDim(maskImage, maskState, maskWidth, maskHeight); // Make sure that we have an overlap before we call the decompressor... destRect.left = x; destRect.top = y; destRect.right = x + maskWidth - 1; destRect.bottom = y + maskHeight - 1; if (!findRectOverlap(&destRect, &clipRect)) { // We're done, there is no update region... return; } // Finally call the primitive... if (maskCompressionType == kWCTTRLE) { trleFLIPAltSourceDecompressImage( destBitmapPtr->bufferPtr(), maskDataPtr, destBitmapPtr->bitmapWidth, destBitmapPtr->bitmapHeight, sourceBufferPtr, srcBitmapWidth, srcBitmapHeight, srcBitsPerPixel, x, y, maskWidth, maskHeight, &clipRect, flags, conversionTable, nullptr); } else if (maskCompressionType == kWCTMRLEWithLineSizePrefix) { mrleFLIPAltSourceDecompressImage( destBitmapPtr->bufferPtr(), maskDataPtr, destBitmapPtr->bitmapWidth, destBitmapPtr->bitmapHeight, sourceBufferPtr, srcBitmapWidth, srcBitmapHeight, srcBitsPerPixel, x, y, maskWidth, maskHeight, &clipRect, flags, conversionTable); } // What type of update is necessary? if (!(flags & kWRFAlloc) && markUpdates) { // If neither foreground or background, copy to both... if ((flags & (kWRFBackground | kWRFForeground)) == 0) { _vm->backgroundToForegroundBlit(destRect); } else { ++destRect.bottom; _vm->markRectAsDirty(kMainVirtScreen, destRect); } } } void Wiz::dwHandleComplexImageDraw(int image, int state, int x, int y, int shadow, int angle, int scale, const Common::Rect *clipRect, int32 flags, WizSimpleBitmap *optionalBitmapOverride, const WizRawPixel *optionalColorConversionTable) { int32 w, h; int correctedAngle; Common::Point listOfPoints[4]; const byte *shadowPtr; // Set the optional remap table up to the default if one isn't specified... if (!optionalColorConversionTable) { if (_uses16BitColor) { optionalColorConversionTable = (WizRawPixel *)_vm->getHEPaletteSlot(1); } } // Setup the initial quad (0,0) relative... getWizImageDim(image, state, w, h); listOfPoints[0].x = -w / 2; listOfPoints[0].y = -h / 2; listOfPoints[1].x = listOfPoints[0].x + w - 1; listOfPoints[1].y = listOfPoints[0].y; listOfPoints[2].x = listOfPoints[1].x; listOfPoints[2].y = listOfPoints[0].y + h - 1; listOfPoints[3].x = listOfPoints[0].x; listOfPoints[3].y = listOfPoints[2].y; // Hflip? if (flags & kWRFHFlip) { SWAP(listOfPoints[0].x, listOfPoints[1].x); SWAP(listOfPoints[2].x, listOfPoints[3].x); } // VFlip? if (flags & kWRFVFlip) { SWAP(listOfPoints[0].y, listOfPoints[1].y); SWAP(listOfPoints[2].y, listOfPoints[3].y); } // Scale the points? if (scale != 256) { for (int i = 0; i < 4; i++) { listOfPoints[i].x = (scale * listOfPoints[i].x) / 256; listOfPoints[i].y = (scale * listOfPoints[i].y) / 256; } } // Rotate the points? if (angle) { polyRotatePoints(listOfPoints, 4, angle); } // Offset the points... polyMovePolygonPoints(listOfPoints, 4, x, y); // Special case rotate 0,90,180,270 degree special cases... if (scale == 256) { Common::Rect boundingRect; correctedAngle = abs(angle % 360); if (angle < 0) { correctedAngle = (360 - correctedAngle); } // Get the upper left point so that our blit matches // in position the normal warp drawing function... polyBuildBoundingRect(listOfPoints, 4, boundingRect); x = boundingRect.left; y = boundingRect.top; // Special case renderers don't use shadows... if (!shadow || (_vm->_game.heversion <= 99 && !_vm->_isHE995)) { switch (correctedAngle) { case 0: handleRotate0SpecialCase( image, state, x, y, shadow, correctedAngle, scale, clipRect, flags, optionalBitmapOverride, optionalColorConversionTable); return; case 90: handleRotate90SpecialCase( image, state, x, y, shadow, correctedAngle, scale, clipRect, flags, optionalBitmapOverride, optionalColorConversionTable); return; case 180: handleRotate180SpecialCase( image, state, x, y, shadow, correctedAngle, scale, clipRect, flags, optionalBitmapOverride, optionalColorConversionTable); return; case 270: handleRotate270SpecialCase( image, state, x, y, shadow, correctedAngle, scale, clipRect, flags, optionalBitmapOverride, optionalColorConversionTable); return; } } } // If there is a shadow get it's address... if (shadow) { shadowPtr = getColorMixBlockPtrForWiz(shadow); // Let's replicate whichever bug they had in the interpreter... if (_vm->_game.heversion > 99 || _vm->_isHE995) shadowPtr += _vm->_resourceHeaderSize; } else { shadowPtr = nullptr; } WarpWizPoint listOfWarpPoints[4]; for (int i = 0; i < 4; i++) { WarpWizPoint tmp(listOfPoints[i]); listOfWarpPoints[i] = tmp; } // Finally call the renderer... warpDrawWizTo4Points( image, state, listOfWarpPoints, flags, _vm->VAR(_vm->VAR_WIZ_TRANSPARENT_COLOR), clipRect, optionalBitmapOverride, optionalColorConversionTable, shadowPtr); } bool Wiz::dwIsMaskCompatibleCompressionType(int compressionType) { return (kWCTTRLE == compressionType) || (kWCTMRLEWithLineSizePrefix == compressionType); } bool Wiz::dwIsUncompressedFormatTypeID(int id) { return ((kWCTNone == id) || (kWCTNone16Bpp == id) || (kWCTNone16BppBigEndian == id)); } int Wiz::dwGetImageGeneralProperty(int image, int state, int property) { if (_vm->_isHE995) return 0; switch (property) { case kWIPCompressionType: return getWizCompressionType(image, state); break; case kWIPPaletteBlockPresent: return doesStateContainBlock(image, state, MKTAG('R', 'G', 'B', 'S')) ? 1 : 0; break; case kWIPRemapBlockPresent: return doesStateContainBlock(image, state, MKTAG('R', 'M', 'A', 'P')) ? 1 : 0; break; case kWIPOpaqueBlockPresent: return doesRawWizStateHaveTransparency(image, state) ? 1 : 0; break; case kWIPXMAPBlockPresent: return doesStateContainBlock(image, state, MKTAG('X', 'M', 'A', 'P')) ? 1 : 0; break; default: debug("Wiz::dwGetImageGeneralProperty(): image %d state %d property %d (unknown property id).", image, state, property); break; } return 0; } void Wiz::handleRotate0SpecialCase(int image, int state, int x, int y, int shadow, int angle, int scale, const Common::Rect *clipRect, int32 flags, WizSimpleBitmap *optionalBitmapOverride, const WizRawPixel *optionalColorConversionTable) { drawAWizPrim(image, state, x, y, 0, shadow, 0, clipRect, flags, optionalBitmapOverride, optionalColorConversionTable); } void Wiz::handleRotate90SpecialCase(int image, int state, int x, int y, int shadow, int angle, int scale, const Common::Rect *clipRect, int32 flags, WizSimpleBitmap *optionalBitmapOverride, const WizRawPixel *optionalColorConversionTable) { WizSimpleBitmap srcBitmap, dstBitmap; Common::Rect updateRect; int compressionType; // Make the update rect and check it against the clip rect if one... int32 w, h; getWizImageDim(image, state, w, h); makeSizedRectAt(&updateRect, x, y, h, w); // We are swapping height and width on purpose! if (clipRect) { if (!findRectOverlap(&updateRect, clipRect)) { return; } } // Check to see if this is yet another special case :-) compressionType = getWizCompressionType(image, state); if (compressionType == kWCTTRLE) { int dest_w, dest_h, src_w, src_h; const byte *compressedDataPtr; WizPxShrdBuffer dest_p; // Get the size of the compressed image... src_w = w; src_h = h; // Get the compressed data pointer... compressedDataPtr = (byte *)getWizStateDataPrim(image, state); compressedDataPtr += _vm->_resourceHeaderSize; // Get the write data... if (optionalBitmapOverride) { dest_p = optionalBitmapOverride->bufferPtr; dest_w = optionalBitmapOverride->bitmapWidth; dest_h = optionalBitmapOverride->bitmapHeight; } else { VirtScreen *pvs = &_vm->_virtscr[kMainVirtScreen]; dest_w = pvs->w; dest_h = pvs->h; if (flags & kWRFForeground) { dest_p = WizPxShrdBuffer(pvs->getPixels(0, 0), false); } else { dest_p = WizPxShrdBuffer(pvs->getBackPixels(0, 0), false); } } trleFLIPRotate90DecompressImage( dest_p(), compressedDataPtr, dest_w, dest_h, x, y, src_w, src_h, clipRect, flags, nullptr, optionalColorConversionTable, nullptr); // Update the screen? (If not writing to another bitmap...) if (!optionalBitmapOverride) { if (!(flags & kWRFForeground)) { _vm->backgroundToForegroundBlit(updateRect); } else { ++updateRect.bottom; _vm->markRectAsDirty(kMainVirtScreen, updateRect); } } return; } // Get the image from the basic drawing function... srcBitmap.bufferPtr = drawAWizPrim( image, state, 0, 0, 0, 0, 0, 0, kWRFAlloc, 0, optionalColorConversionTable); srcBitmap.bitmapWidth = w; srcBitmap.bitmapHeight = h; // Get the bitmap to render into... if (optionalBitmapOverride) { dstBitmap = *optionalBitmapOverride; } else { pgSimpleBitmapFromDrawBuffer(&dstBitmap, (kWRFForeground != (kWRFForeground & flags))); } // Call the 90 blit function... if (_vm->VAR(_vm->VAR_WIZ_TRANSPARENT_COLOR) == -1) { pgBlit90DegreeRotate( &dstBitmap, x, y, &srcBitmap, nullptr, clipRect, (flags & kWRFHFlip), (flags & kWRFVFlip)); } else { pgBlit90DegreeRotateTransparent( &dstBitmap, x, y, &srcBitmap, nullptr, clipRect, (flags & kWRFHFlip), (flags & kWRFVFlip), (WizRawPixel)_vm->VAR(_vm->VAR_WIZ_TRANSPARENT_COLOR)); } // Free up the temporary pointer... srcBitmap.bufferPtr = WizPxShrdBuffer(); // Update the screen? (If not writing to another bitmap...) if (!optionalBitmapOverride) { if (!(flags & kWRFForeground)) { _vm->backgroundToForegroundBlit(updateRect); } else { ++updateRect.bottom; _vm->markRectAsDirty(kMainVirtScreen, updateRect); } } } void Wiz::handleRotate180SpecialCase(int image, int state, int x, int y, int shadow, int angle, int scale, const Common::Rect *clipRect, int32 flags, WizSimpleBitmap *optionalBitmapOverride, const WizRawPixel *optionalColorConversionTable) { flags ^= (kWRFVFlip | kWRFHFlip); drawAWizPrim(image, state, x, y, 0, shadow, 0, clipRect, flags, optionalBitmapOverride, optionalColorConversionTable); } void Wiz::handleRotate270SpecialCase(int image, int state, int x, int y, int shadow, int angle, int scale, const Common::Rect *clipRect, int32 flags, WizSimpleBitmap *optionalBitmapOverride, const WizRawPixel *optionalColorConversionTable) { flags ^= (kWRFVFlip | kWRFHFlip); handleRotate90SpecialCase( image, state, x, y, shadow, angle, scale, clipRect, flags, optionalBitmapOverride, optionalColorConversionTable); } void Wiz::processWizImageRenderRectCmd(const WizImageCommand *params) { Common::Rect renderRect, clipRect, workClipRect; int whichState, whichImage; int32 w, h; WizSimpleBitmap renderBitmap; WizRawPixel whatColor; // What state is going to rendered into? if (params->actionFlags & kWAFState) { whichState = params->state; } else { whichState = 0; } whichImage = params->image; // Make the clipping rect for this image / state... getWizImageDim(whichImage, whichState, w, h); makeSizedRectAt(&clipRect, 0, 0, w, h); if (params->actionFlags & kWAFRect) { workClipRect.left = params->box.left; workClipRect.top = params->box.top; workClipRect.right = params->box.right; workClipRect.bottom = params->box.bottom; // Bail out if there isn't overlap between the clipping rects... if (!findRectOverlap(&clipRect, &workClipRect)) { return; } } // Get the rendering coords or assume the entire... if (params->actionFlags & kWAFRenderCoords) { renderRect = params->renderCoords; } else { renderRect = clipRect; } // What is the rendering color? if (params->actionFlags & kWAFColor) { whatColor = params->colorValue; } else { whatColor = _vm->VAR(_vm->VAR_COLOR_BLACK); } // Get the simple bitmap... if (!dwSetSimpleBitmapStructFromImage(whichImage, whichState, &renderBitmap)) { error("Wiz::processWizImageRenderRectCmd(): Image %d state %d invalid for rendering", whichImage, whichState); } // If we're here we must be able to render into the image (clipped)... if (findRectOverlap(&renderRect, &clipRect)) { pgDrawSolidRect(&renderBitmap, &renderRect, whatColor); _vm->_res->setModified(rtImage, params->image); } } void Wiz::processWizImageRenderLineCmd(const WizImageCommand *params) { Common::Rect clipRect, workClipRect; int whichState, whichImage; int32 w, h; WizSimpleBitmap renderBitmap; WizRawPixel whatColor; int propertyNumber = 0, propertyValue = 0; if (!(params->actionFlags & kWAFRenderCoords)) { return; } if (params->actionFlags & kWAFState) { whichState = params->state; } else { whichState = 0; } if (_vm->_game.heversion > 99 || _vm->_isHE995) { if (params->actionFlags & kWAFProperty) { propertyNumber = params->propertyNumber; propertyValue = params->propertyValue; } } whichImage = params->image; getWizImageDim(whichImage, whichState, w, h); makeSizedRectAt(&clipRect, 0, 0, w, h); if (params->actionFlags & kWAFRect) { workClipRect.left = params->box.left; workClipRect.top = params->box.top; workClipRect.right = params->box.right; workClipRect.bottom = params->box.bottom; // Bail out if there isn't overlap between the clipping rects... if (!findRectOverlap(&clipRect, &workClipRect)) { return; } } // What is the rendering color? if (params->actionFlags & kWAFColor) { whatColor = params->colorValue; } else { whatColor = _vm->VAR(_vm->VAR_COLOR_BLACK); } // Get the simple bitmap... if (!dwSetSimpleBitmapStructFromImage(whichImage, whichState, &renderBitmap)) { error("Wiz::processWizImageRenderLineCmd(): Image %d state %d invalid for rendering", whichImage, whichState); } // If we're here we must be able to render into the image (clipped)... if (_vm->_game.heversion > 99 || _vm->_isHE995) { switch (propertyNumber) { case 0: pgClippedLineDraw( &renderBitmap, params->renderCoords.left, params->renderCoords.top, params->renderCoords.right, params->renderCoords.bottom, &clipRect, whatColor); break; case 1: pgClippedThickLineDraw( &renderBitmap, params->renderCoords.left, params->renderCoords.top, params->renderCoords.right, params->renderCoords.bottom, &clipRect, propertyValue, whatColor); break; } _vm->_res->setModified(rtImage, params->image); } else { pgClippedLineDraw( &renderBitmap, params->renderCoords.left, params->renderCoords.top, params->renderCoords.right, params->renderCoords.bottom, &clipRect, whatColor); } } void Wiz::processWizImageRenderPixelCmd(const WizImageCommand *params) { Common::Rect clipRect, workClipRect; int whichState, whichImage; int32 w, h; WizSimpleBitmap renderBitmap; WizRawPixel whatColor; Common::Point pt; if (params->actionFlags & kWAFRenderCoords) { pt.x = params->renderCoords.left; pt.y = params->renderCoords.top; } else { return; } if (params->actionFlags & kWAFState) { whichState = params->state; } else { whichState = 0; } whichImage = params->image; getWizImageDim(whichImage, whichState, w, h); makeSizedRectAt(&clipRect, 0, 0, w, h); if (params->actionFlags & kWAFRect) { workClipRect.left = params->box.left; workClipRect.top = params->box.top; workClipRect.right = params->box.right; workClipRect.bottom = params->box.bottom; // Bail out if there isn't overlap between the clipping rects... if (!findRectOverlap(&clipRect, &workClipRect)) { return; } } if (params->actionFlags & kWAFColor) { whatColor = params->colorValue; } else { whatColor = _vm->VAR(_vm->VAR_COLOR_BLACK); } if (!dwSetSimpleBitmapStructFromImage(whichImage, whichState, &renderBitmap)) { error("Wiz::processWizImageRenderPixelCmd(): Image %d state %d invalid for rendering.", whichImage, whichState); } if (isPointInRect(&clipRect,&pt)) { pgWritePixel(&renderBitmap, pt.x, pt.y, whatColor); _vm->_res->setModified(rtImage, params->image); } } void Wiz::remapImagePrim(int image, int state, int tableCount, const uint8 *remapList, const uint8 *remapTable) { int index; byte *tablePtr; byte *basePtr; // Find the table... basePtr = getWizStateRemapDataPrim(image, state); assert(basePtr); // Set the modified bit... _vm->_res->setModified(rtImage, image); WRITE_BE_UINT32(basePtr + _vm->_resourceHeaderSize, WIZ_MAGIC_REMAP_NUMBER); tablePtr = basePtr + _vm->_resourceHeaderSize + 4; for (int i = 0; i < tableCount; i++) { index = *remapList++; tablePtr[index] = remapTable[index]; } } int Wiz::createHistogramArrayForImage(int image, int state, const Common::Rect *optionalClipRect) { int src_c, src_w, src_h, globNum; Common::Rect realClippedRect; int histogramTable[256]; byte *src_d; byte *pp; globNum = image; _vm->writeVar(0, 0); ((ScummEngine_v72he *)_vm)->defineArray(0, _vm->kDwordArray, 0, 0, 0, 255); if (_vm->readVar(0) != 0) { // Get the header (width, height + compression)... pp = getWizStateHeaderPrim(globNum, state); src_c = READ_LE_UINT32(pp + _vm->_resourceHeaderSize + 0); src_w = READ_LE_UINT32(pp + _vm->_resourceHeaderSize + 4); src_h = READ_LE_UINT32(pp + _vm->_resourceHeaderSize + 8); // Clip the passed in coords to the real dimensions of the image... makeSizedRect(&realClippedRect, src_w, src_h); if (optionalClipRect) { if (!findRectOverlap(&realClippedRect, optionalClipRect)) { return _vm->readVar(0); } } // Get the data pointer... src_d = getWizStateDataPrim(globNum, state); // Start with a clean array... memset(histogramTable, 0, sizeof(histogramTable)); // Handle the different compression types... if (src_c == kWCTTRLE) { // Get the histogram... auxHistogramTRLEPrim(histogramTable, src_d + _vm->_resourceHeaderSize, &realClippedRect); } else if (src_c == kWCTNone) { WizSimpleBitmap srcBitmap; srcBitmap.bufferPtr = WizPxShrdBuffer(src_d + _vm->_resourceHeaderSize, false); srcBitmap.bitmapWidth = src_w; srcBitmap.bitmapHeight = src_h; pgHistogramBitmapSubRect(histogramTable, &srcBitmap, &realClippedRect); } else { warning("Wiz::createHistogramArrayForImage(): Unable to return histogram for type %d", src_c); } // Fill in the scumm array with the values... if (_vm->readVar(0) != 0) { for (int i = 0; i < 256; i++) { ((ScummEngine_v72he *)_vm)->writeArray(0, 0, i, histogramTable[i]); } } } return _vm->readVar(0); } // This function is currently deactivated until it can be assessed // that it doesn't do more damage than good. Currently it does more // damage (e.g. the images get re-byteswapped on every frame)... void Wiz::ensureNativeFormatImageForState(int image, int state) { #if 0 // If AWIZ block is an XMAP, we don't want to do anything with it... if (dwGetImageGeneralProperty(image, state, kWIPXMAPBlockPresent)) { return; } int compType = getWizCompressionType(image, state); bool wiz16bpp = WIZ_16BPP(compType); bool native = NATIVE_WIZ_TYPE(compType); if (wiz16bpp && !native) { uint16 *ptr = (uint16 *)getWizStateDataPrim(image, state); int32 w, h; getWizImageDim(image, state, w, h); int32 pixelCount = w * h; // Number of pixels to twiddle if (pixelCount <= 0) { error("Width or height 0 for image %d state %d", image, state); } uint16 *thisPixel = ptr + 2; // Skip the "WIZD" header for (int i = 0; i < pixelCount; i++) { thisPixel[i] = SWAP_BYTES_16(thisPixel[i]); } int newCompType = compType; switch (compType) { // These were in the original but they appear to be dead code. // Not removing these just yet... // // case kWCTNone16Bpp: // newCompType += kWCTNone16BppBigEndian - kWCTNone16Bpp; // break; // case kWCTTRLE16Bpp: // newCompType += kWCTTRLE16BppBigEndian - kWCTTRLE16Bpp; // break; case kWCTNone16BppBigEndian: newCompType -= kWCTNone16BppBigEndian - kWCTNone16Bpp; break; case kWCTTRLE16BppBigEndian: newCompType -= kWCTTRLE16BppBigEndian - kWCTTRLE16Bpp; break; } // Reset the compression type... setWizCompressionType(image, state, newCompType); } #endif } void Wiz::processWizImageModifyCmd(const WizImageCommand *params) { int state; if (params->actionFlags & kWAFState) { state = params->state; } else { state = 0; } if (params->actionFlags & kWAFRemapList) { remapImagePrim(params->image, state, params->remapCount, params->remapList, params->remapTable); } } void Wiz::processWizImageRenderEllipseCmd(const WizImageCommand *params) { int whichState = 0, propertyValue = 0; int32 width = 0, height = 0; if (params->actionFlags & kWAFProperty) { propertyValue = params->propertyValue; } // What state is going to rendered into? if (params->actionFlags & kWAFState) { whichState = params->state; } int whichImage = params->image; // Make the clipping rect for this image / state... getWizImageDim(whichImage, whichState, width, height); Common::Rect clipRect; makeSizedRectAt(&clipRect, 0, 0, width, height); // Get the simple bitmap... WizSimpleBitmap renderBitmap; if (!dwSetSimpleBitmapStructFromImage(whichImage, whichState, &renderBitmap)) { error("Wiz::processWizImageRenderEllipseCmd(): Image %d state %d invalid for rendering.", whichImage, whichState); } pgDrawClippedEllipse(&renderBitmap, params->ellipseProperties.px, params->ellipseProperties.py, params->ellipseProperties.qx, params->ellipseProperties.qy, params->ellipseProperties.kx, params->ellipseProperties.ky, params->ellipseProperties.lod, &clipRect, propertyValue, params->ellipseProperties.color); _vm->_res->setModified(rtImage, params->image); } void Wiz::processWizImageFontStartCmd(const WizImageCommand *params) { // Used for TTF text in FreddisFunShop/PuttsFunShop/SamsFunShop if (!(((ScummEngine_v99he *)_vm)->_heFont->startFont(params->image))) { warning("Wiz::processWizImageFontStartCmd(): Couldn't start font"); } } void Wiz::processWizImageFontEndCmd(const WizImageCommand *params) { // Used for TTF text in FreddisFunShop/PuttsFunShop/SamsFunShop if (!(((ScummEngine_v99he *)_vm)->_heFont->endFont(params->image))) { warning("Wiz::processWizImageFontEndCmd(): Couldn't end font"); } } void Wiz::processWizImageFontCreateCmd(const WizImageCommand *params) { // Used for TTF text in FreddisFunShop/PuttsFunShop/SamsFunShop if (!(((ScummEngine_v99he *)_vm)->_heFont->createFont(params->image, (char *)params->fontProperties.fontName, params->fontProperties.fgColor, params->fontProperties.bgColor, params->fontProperties.style, params->fontProperties.size))) { warning("Wiz::processWizImageFontCreateCmd(): Couldn't create font"); } } void Wiz::processWizImageFontRenderCmd(const WizImageCommand *params) { // Used for TTF text in FreddisFunShop/PuttsFunShop/SamsFunShop if (!(((ScummEngine_v99he *)_vm)->_heFont->renderString(params->image, params->state, params->fontProperties.xPos, params->fontProperties.yPos, (char *)params->fontProperties.string))) { warning("Wiz::processWizImageFontRenderCmd(): Couldn't render font"); } } void Wiz::processWizImageRenderFloodFillCmd(const WizImageCommand *params) { Common::Rect renderRect, clipRect, workClipRect; int whichState, whichImage; int32 w, h; WizSimpleBitmap renderBitmap; WizRawPixel whatColor; Common::Point pt; // Get the rendering coords or bail if none... if (params->actionFlags & kWAFRenderCoords) { pt.x = params->renderCoords.left; pt.y = params->renderCoords.top; } else { return; } // What state is going to rendered into? if (params->actionFlags & kWAFState) { whichState = params->state; } else { whichState = 0; } whichImage = params->image; // Make the clipping rect for this image / state... getWizImageDim(whichImage, whichState, w, h); makeSizedRectAt(&clipRect, 0, 0, w, h); if (params->actionFlags & kWAFRect) { workClipRect.left = params->box.left; workClipRect.top = params->box.top; workClipRect.right = params->box.right; workClipRect.bottom = params->box.bottom; // Bail out if there isn't overlap between the clipping rects... if (!findRectOverlap(&clipRect, &workClipRect)) { return; } } // What is the rendering color? if (params->actionFlags & kWAFColor) { whatColor = params->colorValue; } else { whatColor = _vm->VAR(_vm->VAR_COLOR_BLACK); } // Get the simple bitmap... if (!dwSetSimpleBitmapStructFromImage(whichImage, whichState, &renderBitmap)) { error("Image %d state %d invalid for rendering.", whichImage, whichState); } // If we're here we must be able to render into the image (clipped)... if (isPointInRect(&clipRect, &pt)) { floodSimpleFill(&renderBitmap, pt.x, pt.y, whatColor, &clipRect, &renderRect); _vm->_res->setModified(rtImage, params->image); } } void Wiz::processNewWizImageCmd(const WizImageCommand *params) { int width, height; int propertyNumber = 0, propertyValue = 0; int hotspotX, hotspotY; if (params->actionFlags & kWAFWidth) { width = params->width; } else { width = 640; } if (params->actionFlags & kWAFHeight) { height = params->height; } else { height = 480; } if (params->actionFlags & kWAFSpot) { hotspotX = params->xPos; hotspotY = params->yPos; } else { hotspotX = 0; hotspotY = 0; } int pixelDepth = (_vm->_game.features & GF_16BIT_COLOR) ? 16 : 8; if (_vm->_game.heversion > 99 || _vm->_isHE995) { // Determine pixel depth... if (params->actionFlags & kWAFProperty) { propertyNumber = params->propertyNumber; propertyValue = params->propertyValue; } if (propertyNumber == 1) { // Color hint property pixelDepth = propertyValue; if ((pixelDepth != 16) && (pixelDepth != 8)) { error("Wiz::processNewWizImageCmd(): The only pixel depths supported for a new image are 16 and 8. You picked %d.", pixelDepth); } } } dwCreateRawWiz(params->image, width, height, kCWFDefault, pixelDepth, hotspotX, hotspotY); _vm->_res->setModified(rtImage, params->image); } void Wiz::processWizImageLoadCmd(const WizImageCommand *params) { Common::SeekableReadStream *inFile; int result; if (params->actionFlags & kWAFFilename) { inFile = _vm->openFileForReading(params->filename); if (!inFile) { _vm->VAR(_vm->VAR_GAME_LOADED) = DW_LOAD_OPEN_FAILURE; _vm->VAR(_vm->VAR_OPERATION_FAILURE) = DW_LOAD_OPEN_FAILURE; debug(0, "Wiz::processWizImageLoadCmd(): Unable to open for read '%s'", params->filename); return; } result = dwTryToLoadWiz(inFile, params); _vm->VAR(_vm->VAR_GAME_LOADED) = result; _vm->VAR(_vm->VAR_OPERATION_FAILURE) = result; if (result == DW_LOAD_SUCCESS) { debug(7, "Wiz::processWizImageLoadCmd(): Correctly loaded file '%s'", params->filename); } else if (result == DW_LOAD_READ_FAILURE) { debug(0, "Wiz::processWizImageLoadCmd(): Got DW_LOAD_READ_FAILURE for file '%s'", params->filename); } delete inFile; } } void Wiz::processWizImageSaveCmd(const WizImageCommand *params) { if (params->actionFlags & kWAFFilename) { if (_vm->_game.heversion <= 99 && !_vm->_isHE995) { // Validate the type if (params->fileType != DW_SAVE_WIZ_FORMAT) { _vm->VAR(_vm->VAR_OPERATION_FAILURE) = DW_SAVE_NOT_TYPE; return; } // Open the file Common::WriteStream *f = _vm->openSaveFileForWriting(params->filename); if (!f) { debug(0, "Unable to open for write '%s'", params->filename); _vm->VAR(_vm->VAR_OPERATION_FAILURE) = DW_SAVE_CREATE_FAILURE; return; } // Get the data size and save out the glob. byte *p = _vm->getResourceAddress(rtImage, params->image); uint32 dataSize = READ_BE_UINT32(p + 4); if (f->write(p, dataSize) == dataSize) { _vm->VAR(_vm->VAR_OPERATION_FAILURE) = DW_SAVE_SUCCESS; } else { _vm->VAR(_vm->VAR_OPERATION_FAILURE) = DW_SAVE_WRITE_FAILURE; } f->finalize(); delete f; } else { switch (params->fileType) { case DW_SAVE_RAW_FORMAT: // Ignore on purpose... _vm->VAR(_vm->VAR_OPERATION_FAILURE) = DW_SAVE_NOT_TYPE; break; case DW_SAVE_PCX_FORMAT: // TODO Write image to file break; case DW_SAVE_WIZ_FORMAT: { Common::WriteStream *f = _vm->openSaveFileForWriting(params->filename); if (!f) { debug(0, "Unable to open for write '%s'", params->filename); _vm->VAR(_vm->VAR_OPERATION_FAILURE) = DW_SAVE_CREATE_FAILURE; } else { byte *p = _vm->getResourceAddress(rtImage, params->image); uint32 size = READ_BE_UINT32(p + 4); if (f->write(p, size) != size) { error("i/o error when writing '%s'", params->filename); _vm->VAR(_vm->VAR_OPERATION_FAILURE) = DW_SAVE_WRITE_FAILURE; } else { _vm->VAR(_vm->VAR_OPERATION_FAILURE) = DW_SAVE_SUCCESS; } f->finalize(); delete f; } break; } default: error("processWizImageCmd: actionType kWASave unhandled fileType %d", params->fileType); } } } } void Wiz::processWizImageCmd(const WizImageCommand *params) { if (((ScummEngine_v90he *)_vm)->_logicHE && ((ScummEngine_v90he *)_vm)->_logicHE->userCodeProcessWizImageCmd(params)) { return; } switch (params->actionType) { case kWAUnknown: // Do nothing... break; case kWADraw: processWizImageDrawCmd(params); break; case kWACapture: processWizImageCaptureCmd(params); break; case kWALoad: processWizImageLoadCmd(params); break; case kWASave: processWizImageSaveCmd(params); break; case kWAGlobalState: // Do nothing... break; case kWAModify: processWizImageModifyCmd(params); break; // HE 99+ case kWAPolyCapture: processWizImagePolyCaptureCmd(params); break; case kWANew: processNewWizImageCmd(params); break; case kWARenderRectangle: processWizImageRenderRectCmd(params); break; case kWARenderLine: processWizImageRenderLineCmd(params); break; case kWARenderPixel: processWizImageRenderPixelCmd(params); break; case kWARenderFloodFill: processWizImageRenderFloodFillCmd(params); break; case kWAFontStart: processWizImageFontStartCmd(params); break; case kWAFontEnd: processWizImageFontEndCmd(params); break; case kWAFontCreate: processWizImageFontCreateCmd(params); break; case kWAFontRender: processWizImageFontRenderCmd(params); break; case kWARenderEllipse: processWizImageRenderEllipseCmd(params); break; default: error("Wiz::processWizImageCmd(): Unhandled processWizImageCmd mode %d", params->actionType); } } bool Wiz::isUncompressedFormatTypeID(int id) { return ((kWCTNone == id) || (kWCTNone16Bpp == id) || (kWCTNone16BppBigEndian == id)); } void Wiz::getWizImageDim(int resNum, int state, int32 &w, int32 &h) { uint8 *dataPtr = _vm->getResourceAddress(rtImage, resNum); assert(dataPtr); getWizImageDim(dataPtr, state, w, h); } void Wiz::getWizImageDim(uint8 *dataPtr, int state, int32 &w, int32 &h) { uint8 *wizh = _vm->findWrappedBlock(MKTAG('W','I','Z','H'), dataPtr, state, 0); assert(wizh); w = READ_LE_UINT32(wizh + 0x4); h = READ_LE_UINT32(wizh + 0x8); } void Wiz::getWizSpot(int resId, int state, int32 &x, int32 &y) { uint8 *dataPtr = _vm->getResourceAddress(rtImage, resId); assert(dataPtr); getWizImageSpot(dataPtr, state, x, y); } void Wiz::getWizSpot(int resId, int32 &x, int32 &y) { uint8 *dataPtr = _vm->getResourceAddress(rtImage, resId); assert(dataPtr); const uint8 *spotPtr = _vm->findResourceData(MKTAG('S', 'P', 'O', 'T'), dataPtr); if (!spotPtr) { x = y = 0; } else { x = READ_LE_UINT32(spotPtr + 0x0); y = READ_LE_UINT32(spotPtr + 0x4); } } void Wiz::getWizImageSpot(uint8 *dataPtr, int state, int32 &x, int32 &y) { uint8 *spotPtr = _vm->findWrappedBlock(MKTAG('S','P','O','T'), dataPtr, state, 0); if (spotPtr) { x = READ_LE_UINT32(spotPtr + 0); y = READ_LE_UINT32(spotPtr + 4); } else { x = 0; y = 0; } } int Wiz::getWizStateCount(int resNum) { const uint8 *dataPtr = _vm->getResourceAddress(rtImage, resNum); assert(dataPtr); return getWizImageStates(dataPtr); } int Wiz::getWizImageStates(const uint8 *dataPtr) { if (READ_BE_UINT32(dataPtr) == MKTAG('M','U','L','T')) { const byte *offs, *wrap; wrap = _vm->findResource(MKTAG('W','R','A','P'), dataPtr); if (wrap == nullptr) return 1; offs = _vm->findResourceData(MKTAG('O','F','F','S'), wrap); if (offs == nullptr) return 1; return _vm->getResourceDataSize(offs) / 4; } else { return 1; } } byte *Wiz::getWizStateHeaderPrim(int resNum, int state) { byte *data = _vm->getResourceAddress(rtImage, resNum); assert(data); return _vm->findWrappedBlock(MKTAG('W', 'I', 'Z', 'H'), data, state, false) - _vm->_resourceHeaderSize; } byte *Wiz::getWizStateDataPrim(int resNum, int state) { byte *data = _vm->getResourceAddress(rtImage, resNum); assert(data); return _vm->findWrappedBlock(MKTAG('W', 'I', 'Z', 'D'), data, state, false) - _vm->_resourceHeaderSize; } byte *Wiz::getWizStatePaletteDataPrim(int globNum, int state) { byte *data = _vm->getResourceAddress(rtImage, globNum); assert(data); return _vm->findWrappedBlock(MKTAG('R', 'G', 'B', 'S'), data, state, false) - _vm->_resourceHeaderSize; } byte *Wiz::getWizStateRemapDataPrim(int globNum, int state) { byte *data = _vm->getResourceAddress(rtImage, globNum); assert(data); return _vm->findWrappedBlock(MKTAG('R', 'M', 'A', 'P'), data, state, false) - _vm->_resourceHeaderSize; } const byte *Wiz::getColorMixBlockPtrForWiz(int image) { byte *data = _vm->getResourceAddress(rtImage, image); assert(data); return _vm->findResourceData(MKTAG('X', 'M', 'A', 'P'), data) - _vm->_resourceHeaderSize; } void Wiz::setWizCompressionType(int image, int state, int newType) { byte *data = getWizStateHeaderPrim(image, state); assert(data); WRITE_LE_UINT32(data + _vm->_resourceHeaderSize, newType); } int Wiz::getWizCompressionType(int image, int state) { byte *data = (byte *)getWizStateHeaderPrim(image, state); assert(data); return READ_LE_UINT32(data + _vm->_resourceHeaderSize); } bool Wiz::doesRawWizStateHaveTransparency(int globNum, int state) { byte *data = _vm->getResourceAddress(rtImage, globNum); assert(data); // Yes, this has to return "true" if the block is not available :-) return _vm->findWrappedBlock(MKTAG('T', 'R', 'N', 'S'), data, state, false) == nullptr; } bool Wiz::doesStateContainBlock(int globNum, int state, uint32 blockID) { byte *data = _vm->getResourceAddress(rtImage, globNum); assert(data); return _vm->findWrappedBlock(blockID, data, state, false) != nullptr; } bool Wiz::collisionCompareImageLines( const byte *imageAData, int aType, int aw, int ah, int32 wizAFlags, int ax, int ay, const byte *imageBData, int bType, int bw, int bh, int32 wizBFlags, int bx, int by, int compareWidth, WizRawPixel transparentColor) { // Get line A's data... rawPixelMemset(_compareBufferA, transparentColor, compareWidth); if (aType == kWCTTRLE) { trleFLIPDecompressImage( _compareBufferA, imageAData, compareWidth, 1, -ax, -ay, aw, ah, nullptr, wizAFlags, nullptr, (WizRawPixel *)_vm->getHEPaletteSlot(1), nullptr); } else { pgDrawRawDataFormatImage( _compareBufferA, (const WizRawPixel *)imageAData, compareWidth, 1, -ax, -ay, aw, ah, nullptr, wizAFlags, nullptr, transparentColor); } // Get line B's data... rawPixelMemset(_compareBufferB, transparentColor, compareWidth); if (bType == kWCTTRLE) { trleFLIPDecompressImage( _compareBufferB, imageBData, compareWidth, 1, -bx, -by, bw, bh, nullptr, wizBFlags, nullptr, (WizRawPixel *)_vm->getHEPaletteSlot(1), nullptr); } else { pgDrawRawDataFormatImage( _compareBufferB, (const WizRawPixel *)imageBData, compareWidth, 1, -bx, -by, bw, bh, nullptr, wizBFlags, nullptr, transparentColor); } // Finally compare the lines... if (compareDoPixelStreamsOverlap(_compareBufferA, _compareBufferB, compareWidth, transparentColor)) { return true; } return false; } } // End of namespace Scumm #endif // ENABLE_HE