/* 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, const Graphics::PixelFormat pixelFormat) : _system(system), _workingWidth(workingWindow.width()), _workingHeight(workingWindow.height()), _screenCenterX(_workingWidth / 2), _screenCenterY(_workingHeight / 2), _workingWindow(workingWindow), _pixelFormat(pixelFormat), _currentBackground(0), _backgroundWidth(0), _backgroundHeight(0), _backgroundInverseVelocity(0), _backgroundOffset(0, 0), _accumulatedVelocityMilliseconds(0), _renderTable(_workingWidth, _workingHeight) { _workingWindowBuffer = new uint16[_workingWidth *_workingHeight]; } RenderManager::~RenderManager() { if (_currentBackground != 0) { delete _currentBackground; } delete[] _workingWindowBuffer; } 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) { _accumulatedVelocityMilliseconds += deltaTimeInMillis; uint absVelocity = uint(abs(_backgroundInverseVelocity)); int 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::clearWorkingWindowToColor(uint16 color) { uint32 workingWindowSize = _workingWidth * _workingHeight; for (uint32 i = 0; i < workingWindowSize; i++) { _workingWindowBuffer[i] = color; } _system->copyRectToScreen(_workingWindowBuffer, _workingWidth * sizeof(uint16), _workingWindow.left, _workingWindow.top, _workingWidth, _workingHeight); } void RenderManager::renderSubRectToScreen(Graphics::Surface &surface, int16 destinationX, int16 destinationY, bool wrap, bool isTransposed) { int16 subRectX = 0; int16 subRectY = 0; // Take care of negative destinations if (destinationX < 0) { subRectX = -destinationX; destinationX = 0; } else if (destinationX >= surface.w) { // Take care of extreme positive destinations destinationX -= surface.w; } // Take care of negative destinations if (destinationY < 0) { subRectY = -destinationY; destinationY = 0; } else if (destinationY >= surface.h) { // Take care of extreme positive destinations destinationY -= surface.h; } if (wrap) { _backgroundWidth = surface.w; _backgroundHeight = surface.h; if (destinationX > 0) { // Move destinationX to 0 subRectX = surface.w - destinationX; destinationX = 0; } if (destinationY > 0) { // Move destinationY to 0 subRectX = surface.w - destinationX; destinationY = 0; } } // Clip subRect to working window bounds Common::Rect subRect(subRectX, subRectY, subRectX + _workingWidth, subRectY + _workingHeight); if (!wrap) { // Clip to image bounds subRect.clip(surface.w, surface.h); } // Check destRect for validity if (!subRect.isValidRect() || subRect.isEmpty()) return; if (_renderTable.getRenderState() == RenderTable::FLAT) { _system->copyRectToScreen(surface.getBasePtr(subRect.left, subRect.top), surface.pitch, destinationX + _workingWindow.left, destinationY + _workingWindow.top, subRect.width(), subRect.height()); } else { _renderTable.mutateImage((uint16 *)surface.getBasePtr(0, 0), _workingWindowBuffer, surface.w, surface.h, destinationX, destinationY, subRect, wrap, isTransposed); _system->copyRectToScreen(_workingWindowBuffer, _workingWidth * sizeof(uint16), destinationX + _workingWindow.left, destinationY + _workingWindow.top, subRect.width(), subRect.height()); } } void RenderManager::renderImageToScreen(const Common::String &fileName, int16 destinationX, int16 destinationY, bool wrap) { Common::File file; if (!file.open(fileName)) { warning("Could not open file %s", fileName.c_str()); return; } renderImageToScreen(file, destinationX, destinationY); } void RenderManager::renderImageToScreen(Common::SeekableReadStream &stream, int16 destinationX, int16 destinationY, bool wrap) { // Read the magic number // Some files are true TGA, while others are TGZ uint32 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 pitch = imageWidth * sizeof(uint16); bool isTransposed = _renderTable.getRenderState() == RenderTable::PANORAMA; if (isTransposed) { uint16 temp = imageHeight; imageHeight = imageWidth; imageWidth = temp; } Graphics::Surface surface; surface.init(imageWidth, imageHeight, pitch, buffer, _pixelFormat); renderSubRectToScreen(surface, destinationX, destinationY, wrap, isTransposed); // We have to use delete[] instead of calling surface.free() because we created the memory with new[] delete[] buffer; } else { // Reset the cursor stream.seek(0); // Decode Graphics::TGADecoder tga; if (!tga.loadStream(stream)) { warning("Error while reading TGA image"); return; } Graphics::Surface tgaSurface = *(tga.getSurface()); bool isTransposed = _renderTable.getRenderState() == RenderTable::PANORAMA; if (isTransposed) { uint16 temp = tgaSurface.h; tgaSurface.h = tgaSurface.w; tgaSurface.w = temp; } renderSubRectToScreen(tgaSurface, destinationX, destinationY, wrap, isTransposed); 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)); RenderTable::RenderState state = _renderTable.getRenderState(); if (state == RenderTable::PANORAMA || state == RenderTable::TILT) { newPoint = _renderTable.convertWarpedCoordToFlatCoord(newPoint); } if (state == RenderTable::PANORAMA) { newPoint -= (Common::Point(_screenCenterX, 0) - _backgroundOffset); } else if (state == RenderTable::TILT) { newPoint -= (Common::Point(0, _screenCenterY) - _backgroundOffset); } if (newPoint.x < 0) newPoint.x += _backgroundWidth; else if (newPoint.x >= _backgroundWidth) newPoint.x -= _backgroundWidth; if (newPoint.y < 0) newPoint.y += _backgroundHeight; else if (newPoint.y >= _backgroundHeight) 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; moveBackground(0); } void RenderManager::setBackgroundPosition(int offset) { RenderTable::RenderState state = _renderTable.getRenderState(); if (state == RenderTable::TILT) { _backgroundOffset.x = 0; _backgroundOffset.y = offset; } else if (state == RenderTable::PANORAMA) { _backgroundOffset.x = offset; _backgroundOffset.y = 0; } else { _backgroundOffset.x = 0; _backgroundOffset.y = 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) { _currentBackground->seek(0); RenderTable::RenderState state = _renderTable.getRenderState(); if (state == RenderTable::TILT) { _backgroundOffset += Common::Point(0, offset); if (_backgroundOffset.x <= -_backgroundWidth) _backgroundOffset.x += _backgroundWidth; else if (_backgroundOffset.x >= _backgroundWidth) _backgroundOffset.x += _backgroundWidth; if (_backgroundOffset.y <= -_backgroundHeight) _backgroundOffset.y += _backgroundHeight; else if (_backgroundOffset.y >= _backgroundHeight) _backgroundOffset.y += _backgroundHeight; renderImageToScreen(*_currentBackground, 0, _screenCenterY - _backgroundOffset.y, true); } else if (state == RenderTable::PANORAMA) { _backgroundOffset += Common::Point(offset, 0); if (_backgroundOffset.x <= -_backgroundWidth) _backgroundOffset.x += _backgroundWidth; else if (_backgroundOffset.x >= _backgroundWidth) _backgroundOffset.x += _backgroundWidth; if (_backgroundOffset.y <= -_backgroundHeight) _backgroundOffset.y += _backgroundHeight; else if (_backgroundOffset.y >= _backgroundHeight) _backgroundOffset.y += _backgroundHeight; renderImageToScreen(*_currentBackground, _screenCenterX - _backgroundOffset.x, 0, true); } else { renderImageToScreen(*_currentBackground, 0, 0); } } } // End of namespace ZVision