/* 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 "mohawk/myst.h" #include "mohawk/myst_graphics.h" #include "mohawk/resource.h" #include "common/substream.h" #include "common/system.h" #include "common/textconsole.h" #include "engines/util.h" #include "graphics/jpeg.h" #include "graphics/pict.h" namespace Mohawk { MystGraphics::MystGraphics(MohawkEngine_Myst* vm) : GraphicsManager(), _vm(vm) { _bmpDecoder = new MystBitmap(); _viewport = Common::Rect(544, 332); // The original version of Myst could run in 8bpp color too. // However, it dithered videos to 8bpp and they looked considerably // worse (than they already did :P). So we're not even going to // support 8bpp mode in Myst (Myst ME required >8bpp anyway). initGraphics(_viewport.width(), _viewport.height(), true, NULL); // What an odd screen size! _pixelFormat = _vm->_system->getScreenFormat(); if (_pixelFormat.bytesPerPixel == 1) error("Myst requires greater than 256 colors to run"); if (_vm->getFeatures() & GF_ME) { _jpegDecoder = new Graphics::JPEG(); _pictDecoder = new Graphics::PictDecoder(_pixelFormat); } else { _jpegDecoder = NULL; _pictDecoder = NULL; } _pictureFile.entries = NULL; // Initialize our buffer _backBuffer = new Graphics::Surface(); _backBuffer->create(_vm->_system->getWidth(), _vm->_system->getHeight(), _pixelFormat); _nextAllowedDrawTime = _vm->_system->getMillis(); _enableDrawingTimeSimulation = 0; } MystGraphics::~MystGraphics() { delete _bmpDecoder; delete _jpegDecoder; delete _pictDecoder; delete[] _pictureFile.entries; _backBuffer->free(); delete _backBuffer; } static const char *s_picFileNames[] = { "CHpics", "", "", "DUpics", "INpics", "", "MEpics", "MYpics", "SEpics", "", "", "STpics" }; void MystGraphics::loadExternalPictureFile(uint16 stack) { if (_vm->getPlatform() != Common::kPlatformMacintosh) return; if (_pictureFile.picFile.isOpen()) _pictureFile.picFile.close(); delete[] _pictureFile.entries; if (!scumm_stricmp(s_picFileNames[stack], "")) return; if (!_pictureFile.picFile.open(s_picFileNames[stack])) error ("Could not open external picture file \'%s\'", s_picFileNames[stack]); _pictureFile.pictureCount = _pictureFile.picFile.readUint32BE(); _pictureFile.entries = new PictureFile::PictureEntry[_pictureFile.pictureCount]; for (uint32 i = 0; i < _pictureFile.pictureCount; i++) { _pictureFile.entries[i].offset = _pictureFile.picFile.readUint32BE(); _pictureFile.entries[i].size = _pictureFile.picFile.readUint32BE(); _pictureFile.entries[i].id = _pictureFile.picFile.readUint16BE(); _pictureFile.entries[i].type = _pictureFile.picFile.readUint16BE(); _pictureFile.entries[i].width = _pictureFile.picFile.readUint16BE(); _pictureFile.entries[i].height = _pictureFile.picFile.readUint16BE(); } } MohawkSurface *MystGraphics::decodeImage(uint16 id) { MohawkSurface *mhkSurface = 0; // Myst ME uses JPEG/PICT images instead of compressed Windows Bitmaps for room images, // though there are a few weird ones that use that format. For further nonsense with images, // the Macintosh version stores images in external "picture files." We check them before // going to check for a PICT resource. if (_vm->getFeatures() & GF_ME && _vm->getPlatform() == Common::kPlatformMacintosh && _pictureFile.picFile.isOpen()) { for (uint32 i = 0; i < _pictureFile.pictureCount; i++) if (_pictureFile.entries[i].id == id) { if (_pictureFile.entries[i].type == 0) { Common::SeekableReadStream *stream = new Common::SeekableSubReadStream(&_pictureFile.picFile, _pictureFile.entries[i].offset, _pictureFile.entries[i].offset + _pictureFile.entries[i].size); if (!_jpegDecoder->read(stream)) error("Could not decode Myst ME Mac JPEG"); mhkSurface = new MohawkSurface(_jpegDecoder->getSurface(_pixelFormat)); delete stream; } else if (_pictureFile.entries[i].type == 1) { mhkSurface = new MohawkSurface(_pictDecoder->decodeImage(new Common::SeekableSubReadStream(&_pictureFile.picFile, _pictureFile.entries[i].offset, _pictureFile.entries[i].offset + _pictureFile.entries[i].size))); } else error ("Unknown Picture File type %d", _pictureFile.entries[i].type); break; } } // We're not using the external Mac files, so it's time to delve into the main Mohawk // archives. However, we still don't know if it's a PICT or WDIB resource. If it's Myst // ME it's most likely a PICT, and if it's original it's definitely a WDIB. However, // Myst ME throws us another curve ball in that PICT resources can contain WDIB's instead // of PICT's. if (!mhkSurface) { bool isPict = false; Common::SeekableReadStream *dataStream = NULL; if (_vm->getFeatures() & GF_ME && _vm->hasResource(ID_PICT, id)) { // The PICT resource exists. However, it could still contain a MystBitmap // instead of a PICT image... dataStream = _vm->getResource(ID_PICT, id); } else // No PICT, so the WDIB must exist. Let's go grab it. dataStream = _vm->getResource(ID_WDIB, id); if (_vm->getFeatures() & GF_ME) { // Here we detect whether it's really a PICT or a WDIB. Since a MystBitmap // would be compressed, there's no way to detect for the BM without a hack. // So, we search for the PICT version opcode for detection. dataStream->seek(512 + 10); // 512 byte pict header isPict = (dataStream->readUint32BE() == 0x001102FF); dataStream->seek(0); } if (isPict) mhkSurface = new MohawkSurface(_pictDecoder->decodeImage(dataStream)); else { mhkSurface = _bmpDecoder->decodeImage(dataStream); mhkSurface->convertToTrueColor(); } } assert(mhkSurface); return mhkSurface; } void MystGraphics::copyImageSectionToScreen(uint16 image, Common::Rect src, Common::Rect dest) { Graphics::Surface *surface = findImage(image)->getSurface(); // Make sure the image is bottom aligned in the dest rect dest.top = dest.bottom - MIN(surface->h, dest.height()); // Convert from bitmap coordinates to surface coordinates uint16 top = surface->h - (src.top + MIN(surface->h, dest.height())); // Do not draw the top pixels if the image is too tall if (dest.height() > _viewport.height()) top += dest.height() - _viewport.height(); // Clip the destination rect to the screen if (dest.right > _vm->_system->getWidth() || dest.bottom > _vm->_system->getHeight()) dest.debugPrint(4, "Clipping destination rect to the screen"); dest.right = CLIP(dest.right, 0, _vm->_system->getWidth()); dest.bottom = CLIP(dest.bottom, 0, _vm->_system->getHeight()); uint16 width = MIN(surface->w, dest.width()); uint16 height = MIN(surface->h, dest.height()); // Clamp Width and Height to within src surface dimensions if (src.left + width > surface->w) width = surface->w - src.left; if (src.top + height > surface->h) height = surface->h - src.top; debug(3, "MystGraphics::copyImageSectionToScreen()"); debug(3, "\tImage: %d", image); debug(3, "\tsrc.left: %d", src.left); debug(3, "\tsrc.top: %d", src.top); debug(3, "\tdest.left: %d", dest.left); debug(3, "\tdest.top: %d", dest.top); debug(3, "\twidth: %d", width); debug(3, "\theight: %d", height); simulatePreviousDrawDelay(dest); _vm->_system->copyRectToScreen((byte *)surface->getBasePtr(src.left, top), surface->pitch, dest.left, dest.top, width, height); } void MystGraphics::copyImageSectionToBackBuffer(uint16 image, Common::Rect src, Common::Rect dest) { Graphics::Surface *surface = findImage(image)->getSurface(); // Make sure the image is bottom aligned in the dest rect dest.top = dest.bottom - MIN(surface->h, dest.height()); // Convert from bitmap coordinates to surface coordinates uint16 top = surface->h - (src.top + MIN(surface->h, dest.height())); // Do not draw the top pixels if the image is too tall if (dest.height() > _viewport.height()) { top += dest.height() - _viewport.height(); } // Clip the destination rect to the screen if (dest.right > _vm->_system->getWidth() || dest.bottom > _vm->_system->getHeight()) dest.debugPrint(4, "Clipping destination rect to the screen"); dest.right = CLIP(dest.right, 0, _vm->_system->getWidth()); dest.bottom = CLIP(dest.bottom, 0, _vm->_system->getHeight()); uint16 width = MIN(surface->w, dest.width()); uint16 height = MIN(surface->h, dest.height()); // Clamp Width and Height to within src surface dimensions if (src.left + width > surface->w) width = surface->w - src.left; if (src.top + height > surface->h) height = surface->h - src.top; debug(3, "MystGraphics::copyImageSectionToBackBuffer()"); debug(3, "\tImage: %d", image); debug(3, "\tsrc.left: %d", src.left); debug(3, "\tsrc.top: %d", src.top); debug(3, "\tdest.left: %d", dest.left); debug(3, "\tdest.top: %d", dest.top); debug(3, "\twidth: %d", width); debug(3, "\theight: %d", height); for (uint16 i = 0; i < height; i++) memcpy(_backBuffer->getBasePtr(dest.left, i + dest.top), surface->getBasePtr(src.left, top + i), width * surface->format.bytesPerPixel); } void MystGraphics::copyImageToScreen(uint16 image, Common::Rect dest) { copyImageSectionToScreen(image, Common::Rect(544, 333), dest); } void MystGraphics::copyImageToBackBuffer(uint16 image, Common::Rect dest) { copyImageSectionToBackBuffer(image, Common::Rect(544, 333), dest); } void MystGraphics::copyBackBufferToScreen(Common::Rect r) { r.clip(_viewport); simulatePreviousDrawDelay(r); _vm->_system->copyRectToScreen((byte *)_backBuffer->getBasePtr(r.left, r.top), _backBuffer->pitch, r.left, r.top, r.width(), r.height()); } void MystGraphics::runTransition(uint16 type, Common::Rect rect, uint16 steps, uint16 delay) { // Do not artificially delay during transitions int oldEnableDrawingTimeSimulation = _enableDrawingTimeSimulation; _enableDrawingTimeSimulation = 0; switch (type) { case 0: { debugC(kDebugScript, "Left to Right"); uint16 step = (rect.right - rect.left) / steps; Common::Rect area = rect; for (uint i = 0; i < steps; i++) { area.left = rect.left + step * i; area.right = area.left + step; _vm->_system->delayMillis(delay); copyBackBufferToScreen(area); _vm->_system->updateScreen(); } if (area.right < rect.right) { area.left = area.right; area.right = rect.right; copyBackBufferToScreen(area); _vm->_system->updateScreen(); } } break; case 1: { debugC(kDebugScript, "Right to Left"); uint16 step = (rect.right - rect.left) / steps; Common::Rect area = rect; for (uint i = 0; i < steps; i++) { area.right = rect.right - step * i; area.left = area.right - step; _vm->_system->delayMillis(delay); copyBackBufferToScreen(area); _vm->_system->updateScreen(); } if (area.left > rect.left) { area.right = area.left; area.left = rect.left; copyBackBufferToScreen(area); _vm->_system->updateScreen(); } } break; case 5: { debugC(kDebugScript, "Top to Bottom"); uint16 step = (rect.bottom - rect.top) / steps; Common::Rect area = rect; for (uint i = 0; i < steps; i++) { area.top = rect.top + step * i; area.bottom = area.top + step; _vm->_system->delayMillis(delay); copyBackBufferToScreen(area); _vm->_system->updateScreen(); } if (area.bottom < rect.bottom) { area.top = area.bottom; area.bottom = rect.bottom; copyBackBufferToScreen(area); _vm->_system->updateScreen(); } } break; case 6: { debugC(kDebugScript, "Bottom to Top"); uint16 step = (rect.bottom - rect.top) / steps; Common::Rect area = rect; for (uint i = 0; i < steps; i++) { area.bottom = rect.bottom - step * i; area.top = area.bottom - step; _vm->_system->delayMillis(delay); copyBackBufferToScreen(area); _vm->_system->updateScreen(); } if (area.top > rect.top) { area.bottom = area.top; area.top = rect.top; copyBackBufferToScreen(area); _vm->_system->updateScreen(); } } break; default: warning("Unknown Update Direction"); //TODO: Replace minimal implementation copyBackBufferToScreen(rect); _vm->_system->updateScreen(); break; } _enableDrawingTimeSimulation = oldEnableDrawingTimeSimulation; } void MystGraphics::drawRect(Common::Rect rect, RectState state) { rect.clip(_viewport); // Useful with debugging. Shows where hotspots are on the screen and whether or not they're active. if (!rect.isValidRect() || rect.width() == 0 || rect.height() == 0) return; Graphics::Surface *screen = _vm->_system->lockScreen(); if (state == kRectEnabled) screen->frameRect(rect, _pixelFormat.RGBToColor(0, 255, 0)); else if (state == kRectUnreachable) screen->frameRect(rect, _pixelFormat.RGBToColor(0, 0, 255)); else screen->frameRect(rect, _pixelFormat.RGBToColor(255, 0, 0)); _vm->_system->unlockScreen(); } void MystGraphics::drawLine(const Common::Point &p1, const Common::Point &p2, uint32 color) { _backBuffer->drawLine(p1.x, p1.y, p2.x, p2.y, color); } void MystGraphics::enableDrawingTimeSimulation(bool enable) { if (enable) _enableDrawingTimeSimulation++; else _enableDrawingTimeSimulation--; if (_enableDrawingTimeSimulation < 0) _enableDrawingTimeSimulation = 0; } void MystGraphics::simulatePreviousDrawDelay(const Common::Rect &dest) { uint32 time = 0; if (_enableDrawingTimeSimulation) { time = _vm->_system->getMillis(); // Do not draw anything new too quickly after the previous draw call // so that images stay at least a little while on screen // This is enabled only for scripted draw calls if (time < _nextAllowedDrawTime) _vm->_system->delayMillis(_nextAllowedDrawTime - time); } // Next draw call allowed at DELAY + AERA * COEFF milliseconds from now time = _vm->_system->getMillis(); _nextAllowedDrawTime = time + _constantDrawDelay + dest.height() * dest.width() / _proportionalDrawDelay; } void MystGraphics::copyBackBufferToScreenWithSaturation(int16 saturation) { Graphics::Surface *screen = _vm->_system->lockScreen(); for (uint16 y = 0; y < _viewport.height(); y++) for (uint16 x = 0; x < _viewport.width(); x++) { uint32 color; uint8 r, g, b; if (_pixelFormat.bytesPerPixel == 2) color = *(const uint16 *)_backBuffer->getBasePtr(x, y); else color = *(const uint32 *)_backBuffer->getBasePtr(x, y); _pixelFormat.colorToRGB(color, r, g, b); r = CLIP((int16)r - saturation, 0, 255); g = CLIP((int16)g - saturation, 0, 255); b = CLIP((int16)b - saturation, 0, 255); color = _pixelFormat.RGBToColor(r, g, b); if (_pixelFormat.bytesPerPixel == 2) { uint16 *dst = (uint16 *)screen->getBasePtr(x, y); *dst = color; } else { uint32 *dst = (uint32 *)screen->getBasePtr(x, y); *dst = color; } } _vm->_system->unlockScreen(); _vm->_system->updateScreen(); } void MystGraphics::fadeToBlack() { for (int16 i = 0; i < 256; i += 32) { copyBackBufferToScreenWithSaturation(i); } } void MystGraphics::fadeFromBlack() { for (int16 i = 256; i >= 0; i -= 32) { copyBackBufferToScreenWithSaturation(i); } } } // End of namespace Mohawk