/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "common/scummsys.h" #include "common/file.h" #include "common/system.h" #include "common/stream.h" #include "engines/util.h" #include "graphics/decoders/tga.h" #include "zvision/render_manager.h" #include "zvision/lzss_read_stream.h" namespace ZVision { RenderManager::RenderManager(OSystem *system, const Common::Rect workingWindow) : _system(system), _workingWidth(workingWindow.width()), _workingHeight(workingWindow.height()), _workingWindow(workingWindow), _currentBackground(0), _backgroundWidth(0), _backgroundHeight(0), _backgroundInverseVelocity(0), _accumulatedVelocityMilliseconds(0), _renderTable(workingWindow.width(), workingWindow.height()) { } RenderManager::~RenderManager() { if (_currentBackground != 0) { delete _currentBackground; } } void RenderManager::update(uint deltaTimeInMillis) { // An inverse velocity of 0 would be infinitely fast, so we'll let 0 mean no velocity. if (_backgroundInverseVelocity == 0) return; _accumulatedVelocityMilliseconds += deltaTimeInMillis; int absVelocity = abs(_backgroundInverseVelocity); uint numberOfSteps = 0; while (_accumulatedVelocityMilliseconds >= absVelocity) { _accumulatedVelocityMilliseconds -= absVelocity; numberOfSteps++; } // Choose the direction of movement using the sign of the velocity moveBackground(_backgroundInverseVelocity < 0 ? -numberOfSteps : numberOfSteps); } void RenderManager::renderSubRectToScreen(uint16 *buffer, uint32 imageWidth, uint32 imageHeight, uint32 horizontalPitch, uint32 destinationX, uint32 destinationY, Common::Rect subRectangle, bool wrap) { if (wrap) { _backgroundWidth = imageWidth; _backgroundHeight = imageHeight; } // If subRect is empty, use the entire image if (subRectangle.isEmpty()) subRectangle = Common::Rect(subRectangle.left, subRectangle.top, subRectangle.left + imageWidth, subRectangle.top + imageHeight); // Clip destRect to working window bounds Common::Rect destRect(destinationX, destinationY, destinationX + subRectangle.width(), destinationY + subRectangle.height()); destRect.clip(_workingWidth, _workingHeight); // Clip subRect to working window bounds subRectangle.translate(destRect.left - destinationX, destRect.top - destinationY); subRectangle.setWidth(destRect.width()); subRectangle.setHeight(destRect.height()); // Clip to image bounds Common::Point subRectOrigOrigin(subRectangle.left, subRectangle.top); subRectangle.clip(imageWidth, imageHeight); // If the image is to be wrapped, check if it's smaller than destRect // If it is, then call renderSubRectToScreen with a subRect representing wrapping if (wrap && subRectangle.width() < destRect.width()) { uint32 wrapDestX; uint32 wrapDestY; Common::Rect wrapSubRect; if (_backgroundWidth - subRectangle.left < destRect.width()) { wrapDestX = destRect.left + subRectangle.width(); wrapDestY = destRect.top; wrapSubRect = Common::Rect(0, 0, destRect.width() - subRectangle.width(), subRectangle.bottom); } else { wrapDestX = destRect.left; wrapDestY = destRect.top; wrapSubRect = Common::Rect(_backgroundWidth - subRectangle.width(), 0, _backgroundWidth - 1, subRectangle.bottom); } renderSubRectToScreen(buffer, imageWidth, imageHeight, horizontalPitch, wrapDestX, wrapDestY, wrapSubRect, false); } else if (wrap && subRectangle.height() < destRect.height()) { uint32 wrapDestX; uint32 wrapDestY; Common::Rect wrapSubRect; if (_backgroundHeight - subRectangle.top < destRect.height()) { wrapDestX = destRect.left; wrapDestY = destRect.height() - subRectangle.height(); wrapSubRect = Common::Rect(0, 0, subRectangle.right, destRect.height() - subRectangle.height()); } else { wrapDestX = destRect.left; wrapDestY = destRect.top; wrapSubRect = Common::Rect(0, _backgroundHeight - subRectangle.height(), subRectangle.right, _backgroundHeight - 1); } renderSubRectToScreen(buffer, imageWidth, imageHeight, horizontalPitch, wrapDestX, wrapDestY, wrapSubRect, false); } // Clip destRect to image bounds destRect.translate(subRectangle.left - subRectOrigOrigin.x, subRectangle.top - subRectOrigOrigin.y); destRect.setWidth(subRectangle.width()); destRect.setHeight(subRectangle.height()); // Check all Rects for validity if (!subRectangle.isValidRect() || subRectangle.isEmpty() || !destRect.isValidRect() || destRect.isEmpty()) return; if (_renderTable.getRenderState() == RenderTable::FLAT) { // Convert destRect to screen space by adding _workingWindowOffset _system->copyRectToScreen(buffer + subRectangle.top * horizontalPitch + subRectangle.left, horizontalPitch, destRect.left + _workingWindow.left, destRect.top + _workingWindow.top, destRect.width(), destRect.height()); } else { uint16 *destBuffer = new uint16[destRect.width() * destRect.height()]; _renderTable.mutateImage((uint16 *)buffer, destBuffer, imageWidth, imageHeight, subRectangle, destRect); // Convert destRect to screen space by adding _workingWindow offset _system->copyRectToScreen(destBuffer, subRectangle.width() * sizeof(uint16), destRect.left + _workingWindow.left, destRect.top + _workingWindow.top, destRect.width(), destRect.height()); delete[] destBuffer; } } void RenderManager::renderImageToScreen(const Common::String &fileName, uint32 destinationX, uint32 destinationY, Common::Rect subRectangle, bool wrap) { Common::File file; if (!file.open(fileName)) { warning("Could not open file %s", fileName.c_str()); return; } renderImageToScreen(file, destinationX, destinationY, subRectangle); } void RenderManager::renderImageToScreen(Common::SeekableReadStream &stream, uint32 destinationX, uint32 destinationY, Common::Rect subRectangle, bool wrap) { // Read the magic number // Some files are true TGA, while others are TGZ uint32 fileType; fileType = stream.readUint32BE(); // Check for TGZ files if (fileType == MKTAG('T', 'G', 'Z', '\0')) { // TGZ files have a header and then Bitmap data that is compressed with LZSS uint32 decompressedSize = stream.readSint32LE(); uint32 imageWidth = stream.readSint32LE(); uint32 imageHeight = stream.readSint32LE(); LzssReadStream lzssStream(&stream); byte *buffer = new byte[decompressedSize]; lzssStream.read(buffer, decompressedSize); uint32 horizontalPitch = imageWidth * sizeof(uint16); // Panoramas are transposed // The actual data is transposed in mutateImage if (_renderTable.getRenderState() == RenderTable::PANORAMA || _renderTable.getRenderState() == RenderTable::TILT) { uint32 temp = imageHeight; imageHeight = imageWidth; imageWidth = temp; } renderSubRectToScreen((uint16 *)buffer, imageWidth, imageHeight, horizontalPitch, destinationX, destinationY, subRectangle, wrap); delete[] buffer; } else { // Reset the cursor stream.seek(0); // Decode Graphics::TGADecoder tga; if (!tga.loadStream(stream)) { warning("Error while reading TGA image"); return; } const Graphics::Surface *tgaSurface = tga.getSurface(); uint32 imageWidth = tgaSurface->w; uint32 imageHeight = tgaSurface->h; // Panoramas are transposed // The actual data is transposed in mutateImage if (_renderTable.getRenderState() == RenderTable::PANORAMA || _renderTable.getRenderState() == RenderTable::TILT) { uint32 temp = imageHeight; imageHeight = imageWidth; imageWidth = temp; } renderSubRectToScreen((uint16 *)tgaSurface->pixels, tgaSurface->w, tgaSurface->h, tgaSurface->pitch, destinationX, destinationY, subRectangle, wrap); tga.destroy(); } } const Common::Point RenderManager::screenSpaceToImageSpace(const Common::Point &point) { // Convert from screen space to working window space Common::Point newPoint(point - Common::Point(_workingWindow.left, _workingWindow.top)); if (_renderTable.getRenderState() == RenderTable::PANORAMA || _renderTable.getRenderState() == RenderTable::TILT) { newPoint = _renderTable.convertWarpedCoordToFlatCoord(newPoint); } newPoint -= _backgroundOffset; if (newPoint.x < 0) newPoint.x += _backgroundWidth; if (newPoint.y < 0) newPoint.y += _backgroundHeight; return newPoint; } RenderTable *RenderManager::getRenderTable() { return &_renderTable; } void RenderManager::setBackgroundImage(const Common::String &fileName) { Common::File *file = new Common::File; if (!file->open(fileName)) { warning("Could not open file %s", fileName.c_str()); return; } if (_currentBackground != 0) { delete _currentBackground; } _currentBackground = file; // Purposely make the subRectangle empty. renderImageToScreen will then set the width and height automatically. renderImageToScreen(*_currentBackground, 0, 0, Common::Rect(_backgroundOffset.x, _backgroundOffset.y, _backgroundOffset.x, _backgroundOffset.y), true); } void RenderManager::setBackgroundPosition(int offset) { if (_renderTable.getRenderState() == RenderTable::TILT) { _backgroundOffset = Common::Point(0, offset); } else { _backgroundOffset = Common::Point(offset, 0); } } void RenderManager::setBackgroundVelocity(int velocity) { // setBackgroundVelocity(0) will be called quite often, so make sure // _backgroundInverseVelocity isn't already 0 to prevent an extraneous assignment if (velocity == 0) { if (_backgroundInverseVelocity != 0) { _backgroundInverseVelocity = 0; } } else { _backgroundInverseVelocity = 1000 / velocity; } } void RenderManager::moveBackground(int offset) { if (_renderTable.getRenderState() == RenderTable::TILT) { _backgroundOffset += Common::Point(0, offset); } else { _backgroundOffset += Common::Point(offset, 0); } // Make sure the offset is within image bounds if (_backgroundOffset.x < 0) _backgroundOffset.x += _backgroundWidth; if (_backgroundOffset.x > _backgroundWidth) _backgroundOffset.x -= _backgroundWidth; if (_backgroundOffset.y < 0) _backgroundOffset.y += _backgroundHeight; if (_backgroundOffset.y > _backgroundHeight) _backgroundOffset.y -= _backgroundHeight; _currentBackground->seek(0); // Purposely make the subRectangle empty. renderImageToScreen will then set the width and height automatically. renderImageToScreen(*_currentBackground, 0, 0, Common::Rect(_backgroundOffset.x, _backgroundOffset.y, _backgroundOffset.x, _backgroundOffset.y), true); } } // End of namespace ZVision