/* 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 . * */ #include "graphics/macgui/macbutton.h" #include "graphics/surface.h" #include "image/image_decoder.h" #include "video/avi_decoder.h" #include "video/qt_decoder.h" #include "director/director.h" #include "director/cast.h" #include "director/castmember.h" #include "director/cursor.h" #include "director/channel.h" #include "director/movie.h" #include "director/picture.h" #include "director/score.h" #include "director/sprite.h" #include "director/sound.h" #include "director/window.h" #include "director/stxt.h" #include "director/sprite.h" namespace Director { CastMember::CastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream) : Object("CastMember") { _type = kCastTypeNull; _cast = cast; _castId = castId; _hilite = false; _purgePriority = 3; _size = stream.size(); _flags1 = 0; _modified = true; _isChanged = false; _objType = kCastMemberObj; _widget = nullptr; _erase = false; } CastMember::CastMember(Cast *cast, uint16 castId) : Object("CastMember") { _type = kCastTypeNull; _cast = cast; _castId = castId; _hilite = false; _purgePriority = 3; _size = 0; _flags1 = 0; _modified = true; _isChanged = false; _objType = kCastMemberObj; _widget = nullptr; _erase = false; } CastMemberInfo *CastMember::getInfo() { return _cast->getCastMemberInfo(_castId); } void CastMember::setModified(bool modified) { _modified = modified; if (modified) _isChanged = true; } ///////////////////////////////////// // Bitmap ///////////////////////////////////// BitmapCastMember::BitmapCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint32 castTag, uint16 version, uint8 flags1) : CastMember(cast, castId, stream) { _type = kCastBitmap; _picture = nullptr; _ditheredImg = nullptr; _matte = nullptr; _noMatte = false; _bytes = 0; _pitch = 0; _flags2 = 0; _regX = _regY = 0; _clut = 0; _ditheredTargetClut = 0; _bitsPerPixel = 0; _external = false; if (version < kFileVer400) { _flags1 = flags1; // region: 0 - auto, 1 - matte, 2 - disabled _bytes = stream.readUint16(); _initialRect = Movie::readRect(stream); _boundingRect = Movie::readRect(stream); _regY = stream.readUint16(); _regX = stream.readUint16(); if (_bytes & 0x8000) { _bitsPerPixel = stream.readUint16(); _clut = stream.readSint16(); if (_clut <= 0) // builtin palette _clut -= 1; } else { _bitsPerPixel = 1; _clut = kClutSystemMac; } _pitch = _initialRect.width(); if (_pitch % 16) _pitch += 16 - (_initialRect.width() % 16); _pitch *= _bitsPerPixel; _pitch >>= 3; } else if (version >= kFileVer400 && version < kFileVer600) { _flags1 = flags1; _pitch = stream.readUint16(); _pitch &= 0x0fff; _initialRect = Movie::readRect(stream); _boundingRect = Movie::readRect(stream); _regY = stream.readUint16(); _regX = stream.readUint16(); _bitsPerPixel = stream.readUint16(); if (stream.eos()) { _bitsPerPixel = 0; } else { if (version >= kFileVer500) { stream.readSint16(); // is this the castlib? was ff ff } _clut = stream.readSint16(); if (_clut <= 0) // builtin palette _clut -= 1; stream.readUint16(); /* uint16 unk1 = */ stream.readUint16(); stream.readUint16(); stream.readUint32(); stream.readUint32(); _flags2 = stream.readUint16(); } if (_bitsPerPixel == 0) _bitsPerPixel = 1; int tail = 0; byte buf[256]; while (!stream.eos()) { byte c = stream.readByte(); if (tail < 256) buf[tail] = c; tail++; } if (tail) warning("BUILDBOT: BitmapCastMember: %d bytes left", tail); if (tail && debugChannelSet(2, kDebugLoading)) { debug("BitmapCastMember: tail"); Common::hexdump(buf, tail); } } _tag = castTag; } BitmapCastMember::BitmapCastMember(Cast *cast, uint16 castId, Image::ImageDecoder *img, uint8 flags1) : CastMember(cast, castId) { _type = kCastBitmap; _matte = nullptr; _noMatte = false; _bytes = 0; if (img != nullptr) { _picture = new Picture(*img); } _ditheredImg = nullptr; _clut = -1; _ditheredTargetClut = 0; _initialRect = Common::Rect(0, 0, img->getSurface()->w, img->getSurface()->h); _pitch = img->getSurface()->pitch; _bitsPerPixel = img->getSurface()->format.bytesPerPixel * 8; _regY = img->getSurface()->h / 2; _regX = img->getSurface()->w / 2; _flags1 = flags1; _flags2 = 0; _tag = 0; _external = false; } BitmapCastMember::~BitmapCastMember() { delete _picture; if (_ditheredImg) { _ditheredImg->free(); delete _ditheredImg; } if (_matte) delete _matte; } Graphics::MacWidget *BitmapCastMember::createWidget(Common::Rect &bbox, Channel *channel, SpriteType spriteType) { if (!_picture) { warning("BitmapCastMember::createWidget: No picture"); return nullptr; } // skip creating widget when the bbox is not available, maybe we should create it using initialRect if (!bbox.width() || !bbox.height()) return nullptr; // Check if we need to dither the image int dstBpp = g_director->_wm->_pixelformat.bytesPerPixel; int srcBpp = _picture->_surface.format.bytesPerPixel; const byte *pal = _picture->_palette; bool previouslyDithered = _ditheredImg != nullptr; if (_ditheredImg) { _ditheredImg->free(); delete _ditheredImg; _ditheredImg = nullptr; _ditheredTargetClut = 0; } if (dstBpp == 1) { if (srcBpp > 1 // At least early directors were not remapping 8bpp images. But in case it is // needed, here is the code #if 0 || (srcBpp == 1 && memcmp(g_director->_wm->getPalette(), _img->_palette, _img->_paletteSize)) #endif ) { _ditheredImg = _picture->_surface.convertTo(g_director->_wm->_pixelformat, _picture->_palette, _picture->_paletteColors, g_director->_wm->getPalette(), g_director->_wm->getPaletteSize()); pal = g_director->_wm->getPalette(); } else { // Convert indexed image to indexed palette Movie *movie = g_director->getCurrentMovie(); Cast *cast = movie->getCast(); Score *score = movie->getScore(); // Get the current score palette. Note that this is the ID of the palette in the list, not the cast member! int currentPaletteId = score->resolvePaletteId(score->getCurrentPalette()); if (!currentPaletteId) currentPaletteId = cast->_defaultPalette; PaletteV4 *currentPalette = g_director->getPalette(currentPaletteId); if (!currentPalette) { currentPaletteId = kClutSystemMac; currentPalette = g_director->getPalette(currentPaletteId); } int castPaletteId = score->resolvePaletteId(_clut); // It is possible for Director to have saved an invalid ID in _clut; // if this is the case, do no dithering. if (!castPaletteId) castPaletteId = currentPaletteId; // Check if the palette is in the middle of a color fade event bool isColorCycling = score->isPaletteColorCycling(); // First, check if the palettes are different switch (_bitsPerPixel) { // 1bpp - this is preconverted to 0x00 and 0xff, change nothing. case 1: break; // 2bpp - convert to nearest using the standard 2-bit palette. case 2: { const PaletteV4 &srcPal = g_director->getLoaded4Palette(); _ditheredImg = _picture->_surface.convertTo(g_director->_wm->_pixelformat, srcPal.palette, srcPal.length, currentPalette->palette, currentPalette->length, Graphics::kDitherNaive); } break; // 4bpp - if using a builtin palette, use one of the corresponding 4-bit ones. case 4: { const auto pals = g_director->getLoaded16Palettes(); // in D4 you aren't allowed to use custom palettes for 4-bit images, so uh... // I guess default to the mac palette? int palIndex = pals.contains(castPaletteId) ? castPaletteId : kClutSystemMac; const PaletteV4 &srcPal = pals.getVal(palIndex); _ditheredImg = _picture->_surface.convertTo(g_director->_wm->_pixelformat, srcPal.palette, srcPal.length, currentPalette->palette, currentPalette->length, Graphics::kDitherNaive); } break; // 8bpp - if using a different palette, and we're not doing a color cycling operation, convert using nearest colour matching case 8: // Only redither 8-bit images if we have the flag set, or it is external if (!movie->_remapPalettesWhenNeeded && !_external) break; if (_external || (castPaletteId != currentPaletteId && !isColorCycling)) { const auto pals = g_director->getLoadedPalettes(); int palIndex = pals.contains(castPaletteId) ? castPaletteId : kClutSystemMac; const PaletteV4 &srcPal = pals.getVal(palIndex); // If it is an external image, use the included palette. // For BMP images especially, they'll often have the right colors // but in the wrong palette order. const byte *palPtr = _external ? pal : srcPal.palette; int palLength = _external ? _picture->getPaletteSize() : srcPal.length; _ditheredImg = _picture->_surface.convertTo(g_director->_wm->_pixelformat, palPtr, palLength, currentPalette->palette, currentPalette->length, Graphics::kDitherNaive); } break; default: break; } if (_ditheredImg) { debugC(4, kDebugImages, "BitmapCastMember::createWidget(): Dithering image from source palette %d to target palette %d", _clut, score->getCurrentPalette()); // Save the palette ID so we can check if a redraw is required _ditheredTargetClut = currentPaletteId; if (!_external) { // Finally, the first and last colours in the palette are special. No matter what the palette remap // does, we need to scrub those to be the same. const Graphics::Surface *src = &_picture->_surface; for (int y = 0; y < src->h; y++) { for (int x = 0; x < src->w; x++) { const int test = *(const byte *)src->getBasePtr(x, y); if (test == 0 || test == (1 << _bitsPerPixel) - 1) { *(byte *)_ditheredImg->getBasePtr(x, y) = test == 0 ? 0x00 : 0xff; } } } } } else if (previouslyDithered) { debugC(4, kDebugImages, "BitmapCastMember::createWidget(): Removed dithered image, score palette %d matches cast member", score->getCurrentPalette()); } } } Graphics::MacWidget *widget = new Graphics::MacWidget(g_director->getCurrentWindow(), bbox.left, bbox.top, bbox.width(), bbox.height(), g_director->_wm, false); // scale for drawing a different size sprite copyStretchImg(widget->getSurface()->surfacePtr(), bbox, pal); return widget; } void BitmapCastMember::copyStretchImg(Graphics::Surface *surface, const Common::Rect &bbox, const byte *pal) { const Graphics::Surface *srcSurf; if (_ditheredImg) srcSurf = _ditheredImg; else srcSurf = &_picture->_surface; if (bbox.width() != _initialRect.width() || bbox.height() != _initialRect.height()) { int scaleX = SCALE_THRESHOLD * _initialRect.width() / bbox.width(); int scaleY = SCALE_THRESHOLD * _initialRect.height() / bbox.height(); for (int y = 0, scaleYCtr = 0; y < bbox.height(); y++, scaleYCtr += scaleY) { if (g_director->_wm->_pixelformat.bytesPerPixel == 1) { for (int x = 0, scaleXCtr = 0; x < bbox.width(); x++, scaleXCtr += scaleX) { const byte *src = (const byte *)srcSurf->getBasePtr(scaleXCtr / SCALE_THRESHOLD, scaleYCtr / SCALE_THRESHOLD); *(byte *)surface->getBasePtr(x, y) = *src; } } else { for (int x = 0, scaleXCtr = 0; x < bbox.width(); x++, scaleXCtr += scaleX) { const void *ptr = srcSurf->getBasePtr(scaleXCtr / SCALE_THRESHOLD, scaleYCtr / SCALE_THRESHOLD); int32 color; switch (srcSurf->format.bytesPerPixel) { case 1: { color = *(const byte *)ptr * 3; color = surface->format.RGBToColor(pal[color], pal[color + 1], pal[color + 2]); } break; case 4: color = *(const int32 *)ptr; break; default: error("Unimplemented src bpp: %d", srcSurf->format.bytesPerPixel); } *(int32 *)surface->getBasePtr(x, y) = color; } } } } else { surface->copyFrom(*srcSurf); } if (g_director->_debugDraw & kDebugDrawCast) { surface->frameRect(Common::Rect(0, 0, surface->w, surface->h), g_director->_wm->_colorWhite); const Graphics::Font *font = FontMan.getFontByUsage(Graphics::FontManager::kConsoleFont); font->drawString(surface, Common::String::format("%d", _castId), 2, 2, 10, g_director->_wm->_colorWhite); } } bool BitmapCastMember::isModified() { if (CastMember::isModified()) { // Let's us use "setChanged" when changing the picture through Lingo return true; } // Check for palette changes. // If a bitmap has a custom palette assigned to it, createWidget() // will dither the image so that it fits within the current palette. // When the score palette changes, we need to flag that the widget needs // to be recreated. if (_clut) { Movie *movie = g_director->getCurrentMovie(); Cast *cast = movie->getCast(); Score *score = movie->getScore(); int currentPaletteId = score->resolvePaletteId(score->getCurrentPalette()); if (!currentPaletteId) currentPaletteId = cast->_defaultPalette; PaletteV4 *currentPalette = g_director->getPalette(currentPaletteId); if (!currentPalette) { currentPaletteId = kClutSystemMac; currentPalette = g_director->getPalette(currentPaletteId); } int castPaletteId = score->resolvePaletteId(_clut); if (!castPaletteId) castPaletteId = cast->_defaultPalette; if (currentPaletteId == castPaletteId) { return _ditheredTargetClut != 0; } else { return _ditheredTargetClut != currentPaletteId; } } return false; } void BitmapCastMember::createMatte(Common::Rect &bbox) { // Like background trans, but all white pixels NOT ENCLOSED by coloured pixels // are transparent Graphics::Surface tmp; tmp.create(bbox.width(), bbox.height(), g_director->_pixelformat); copyStretchImg(&tmp, bbox); _noMatte = true; // Searching white color in the corners uint32 whiteColor = 0; bool colorFound = false; if (g_director->_pixelformat.bytesPerPixel == 1) { for (int y = 0; y < tmp.h; y++) { for (int x = 0; x < tmp.w; x++) { byte color = *(byte *)tmp.getBasePtr(x, y); if (g_director->getPalette()[color * 3 + 0] == 0xff && g_director->getPalette()[color * 3 + 1] == 0xff && g_director->getPalette()[color * 3 + 2] == 0xff) { whiteColor = color; colorFound = true; break; } } } } else { whiteColor = g_director->_wm->_colorWhite; colorFound = true; } if (!colorFound) { debugC(1, kDebugImages, "BitmapCastMember::createMatte(): No white color for matte image"); } else { delete _matte; _matte = new Graphics::FloodFill(&tmp, whiteColor, 0, true); for (int yy = 0; yy < tmp.h; yy++) { _matte->addSeed(0, yy); _matte->addSeed(tmp.w - 1, yy); } for (int xx = 0; xx < tmp.w; xx++) { _matte->addSeed(xx, 0); _matte->addSeed(xx, tmp.h - 1); } _matte->fillMask(); _noMatte = false; } tmp.free(); } Graphics::Surface *BitmapCastMember::getMatte(Common::Rect &bbox) { // Lazy loading of mattes if (!_matte && !_noMatte) { createMatte(bbox); } // check for the scale matte Graphics::Surface *surface = _matte ? _matte->getMask() : nullptr; if (surface && (surface->w != bbox.width() || surface->h != bbox.height())) { createMatte(bbox); } return _matte ? _matte->getMask() : nullptr; } Common::String BitmapCastMember::formatInfo() { return Common::String::format( "initialRect: %dx%d@%d,%d, boundingRect: %dx%d@%d,%d, foreColor: %d, backColor: %d, regX: %d, regY: %d, pitch: %d, bitsPerPixel: %d, palette: %d", _initialRect.width(), _initialRect.height(), _initialRect.left, _initialRect.top, _boundingRect.width(), _boundingRect.height(), _boundingRect.left, _boundingRect.top, getForeColor(), getBackColor(), _regX, _regY, _pitch, _bitsPerPixel, _clut ); } PictureReference *BitmapCastMember::getPicture() const { auto picture = new PictureReference; // Not sure if we can make the assumption that the owning // BitmapCastMember will live as long as any reference, // so we'll make a copy of the Picture. picture->_picture = new Picture(*_picture); return picture; } void BitmapCastMember::setPicture(PictureReference &picture) { delete _picture; _picture = new Picture(*picture._picture); // Force redither delete _ditheredImg; _ditheredImg = nullptr; // Make sure we get redrawn setModified(true); // TODO: Should size be adjusted? } void BitmapCastMember::setPicture(Image::ImageDecoder &image, bool adjustSize) { delete _picture; _picture = new Picture(image); if (adjustSize) { auto surf = image.getSurface(); _size = surf->pitch * surf->h + _picture->getPaletteSize(); } // Make sure we get redrawn setModified(true); } ///////////////////////////////////// // DigitalVideo ///////////////////////////////////// DigitalVideoCastMember::DigitalVideoCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version) : CastMember(cast, castId, stream) { _type = kCastDigitalVideo; _video = nullptr; _lastFrame = nullptr; _channel = nullptr; _getFirstFrame = false; _duration = 0; _initialRect = Movie::readRect(stream); _vflags = stream.readUint32(); _frameRate = (_vflags >> 24) & 0xff; _frameRateType = kFrameRateDefault; if (_vflags & 0x0800) { _frameRateType = (FrameRateType)((_vflags & 0x3000) >> 12); } _qtmovie = _vflags & 0x8000; _avimovie = _vflags & 0x4000; _preload = _vflags & 0x0400; _enableVideo = !(_vflags & 0x0200); _pausedAtStart = _vflags & 0x0100; _showControls = _vflags & 0x40; _directToStage = _vflags & 0x20; _looping = _vflags & 0x10; _enableSound = _vflags & 0x08; _crop = !(_vflags & 0x02); _center = _vflags & 0x01; if (debugChannelSet(2, kDebugLoading)) _initialRect.debugPrint(2, "DigitalVideoCastMember(): rect:"); debugC(2, kDebugLoading, "DigitalVideoCastMember(): flags: (%d 0x%04x)", _vflags, _vflags); debugC(2, kDebugLoading, "_frameRate: %d", _frameRate); debugC(2, kDebugLoading, "_frameRateType: %d, _preload: %d, _enableVideo %d, _pausedAtStart %d", _frameRateType, _preload, _enableVideo, _pausedAtStart); debugC(2, kDebugLoading, "_showControls: %d, _looping: %d, _enableSound: %d, _crop %d, _center: %d, _directToStage: %d", _showControls, _looping, _enableSound, _crop, _center, _directToStage); debugC(2, kDebugLoading, "_avimovie: %d, _qtmovie: %d", _avimovie, _qtmovie); } DigitalVideoCastMember::~DigitalVideoCastMember() { if (_lastFrame) { _lastFrame->free(); delete _lastFrame; } if (_video) delete _video; } bool DigitalVideoCastMember::loadVideo(Common::String path) { // TODO: detect file type (AVI, QuickTime, FLIC) based on magic number, // insert the right video decoder if (_video) delete _video; _filename = path; _video = new Video::QuickTimeDecoder(); Common::String path1 = pathMakeRelative(path); debugC(2, kDebugLoading | kDebugImages, "Loading video %s -> %s", path.c_str(), path1.c_str()); bool result = _video->loadFile(Common::Path(path1, g_director->_dirSeparator)); if (!result) { delete _video; _video = new Video::AVIDecoder(); result = _video->loadFile(Common::Path(path1, g_director->_dirSeparator)); if (!result) { warning("DigitalVideoCastMember::loadVideo(): format not supported, skipping"); delete _video; _video = nullptr; } } if (result && g_director->_pixelformat.bytesPerPixel == 1) { // Director supports playing back RGB and paletted video in 256 colour mode. // In both cases they are dithered to match the Director palette. byte palette[256 * 3]; g_system->getPaletteManager()->grabPalette(palette, 0, 256); _video->setDitheringPalette(palette); } return result; } bool DigitalVideoCastMember::isModified() { if (!_video || !_video->isVideoLoaded()) return true; if (_getFirstFrame) return true; if (_channel->_movieRate == 0.0) return false; return _video->needsUpdate(); } void DigitalVideoCastMember::startVideo(Channel *channel) { _channel = channel; if (!_video || !_video->isVideoLoaded()) { warning("DigitalVideoCastMember::startVideo: No video %s", !_video ? "decoder" : "loaded"); return; } if (_pausedAtStart) { _getFirstFrame = true; } else { if (_channel->_movieRate == 0.0) _channel->_movieRate = 1.0; } if (_video->isPlaying()) _video->rewind(); else _video->start(); debugC(2, kDebugImages, "STARTING VIDEO %s", _filename.c_str()); if (_channel->_stopTime == 0) _channel->_stopTime = getMovieTotalTime(); _duration = getMovieTotalTime(); } void DigitalVideoCastMember::stopVideo() { if (!_video || !_video->isVideoLoaded()) { warning("DigitalVideoCastMember::stopVideo: No video decoder"); return; } _video->stop(); debugC(2, kDebugImages, "STOPPING VIDEO %s", _filename.c_str()); } void DigitalVideoCastMember::rewindVideo() { if (!_video || !_video->isVideoLoaded()) { warning("DigitalVideoCastMember::rewindVideo: No video decoder"); return; } _video->rewind(); debugC(2, kDebugImages, "REWINDING VIDEO %s", _filename.c_str()); } Graphics::MacWidget *DigitalVideoCastMember::createWidget(Common::Rect &bbox, Channel *channel, SpriteType spriteType) { Graphics::MacWidget *widget = new Graphics::MacWidget(g_director->getCurrentWindow(), bbox.left, bbox.top, bbox.width(), bbox.height(), g_director->_wm, false); _channel = channel; if (!_video || !_video->isVideoLoaded()) { warning("DigitalVideoCastMember::createWidget: No video decoder"); delete widget; return nullptr; } // Do not render stopped videos if (_channel->_movieRate == 0.0 && !_getFirstFrame && _lastFrame) { widget->getSurface()->blitFrom(*_lastFrame); return widget; } const Graphics::Surface *frame = _video->decodeNextFrame(); debugC(1, kDebugImages, "Video time: %d rate: %f", _channel->_movieTime, _channel->_movieRate); if (frame) { if (_lastFrame) { _lastFrame->free(); delete _lastFrame; } _lastFrame = frame->convertTo(g_director->_pixelformat, g_director->getPalette()); } if (_lastFrame) widget->getSurface()->blitFrom(*_lastFrame); if (_getFirstFrame) { _video->stop(); _getFirstFrame = false; } if (_video->endOfVideo()) { if (_looping) { _video->rewind(); } else { _channel->_movieRate = 0.0; } } return widget; } uint DigitalVideoCastMember::getDuration() { if (!_video || !_video->isVideoLoaded()) { Common::String path = getCast()->getVideoPath(_castId); if (!path.empty()) loadVideo(pathMakeRelative(path)); _duration = getMovieTotalTime(); } return _duration; } uint DigitalVideoCastMember::getMovieCurrentTime() { if (!_video) return 0; int stamp = MIN(_video->getTime() * 60 / 1000, getMovieTotalTime()); return stamp; } uint DigitalVideoCastMember::getMovieTotalTime() { if (!_video) return 0; int stamp = _video->getDuration().msecs() * 60 / 1000; return stamp; } void DigitalVideoCastMember::seekMovie(int stamp) { if (!_video) return; _channel->_startTime = stamp; Audio::Timestamp dur = _video->getDuration(); _video->seek(Audio::Timestamp(_channel->_startTime * 1000 / 60, dur.framerate())); } void DigitalVideoCastMember::setStopTime(int stamp) { if (!_video) return; _channel->_stopTime = stamp; Audio::Timestamp dur = _video->getDuration(); _video->setEndTime(Audio::Timestamp(_channel->_stopTime * 1000 / 60, dur.framerate())); } void DigitalVideoCastMember::setMovieRate(double rate) { if (!_video) return; _channel->_movieRate = rate; if (rate < 0.0) warning("STUB: DigitalVideoCastMember::setMovieRate(%g)", rate); else _video->setRate(Common::Rational((int)(rate * 100.0), 100)); if (_video->endOfVideo()) _video->rewind(); } void DigitalVideoCastMember::setFrameRate(int rate) { if (!_video) return; warning("STUB: DigitalVideoCastMember::setFrameRate(%d)", rate); } Common::String DigitalVideoCastMember::formatInfo() { return Common::String::format( "initialRect: %dx%d@%d,%d, boundingRect: %dx%d@%d,%d, filename: \"%s\", duration: %d, enableVideo: %d, enableSound: %d, looping: %d, crop: %d, center: %d, showControls: %d", _initialRect.width(), _initialRect.height(), _initialRect.left, _initialRect.top, _boundingRect.width(), _boundingRect.height(), _boundingRect.left, _boundingRect.top, _filename.c_str(), _duration, _enableVideo, _enableSound, _looping, _crop, _center, _showControls ); } ///////////////////////////////////// // MovieCasts ///////////////////////////////////// MovieCastMember::MovieCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version) : CastMember(cast, castId, stream) { _type = kCastMovie; _initialRect = Movie::readRect(stream); _flags = stream.readUint32(); _looping = !(_flags & 0x20); _enableScripts = _flags & 0x10; _enableSound = _flags & 0x08; _crop = !(_flags & 0x02); _center = _flags & 0x01; if (debugChannelSet(2, kDebugLoading)) _initialRect.debugPrint(2, "MovieCastMember(): rect:"); debugC(2, kDebugLoading, "MovieCastMember(): flags: (%d 0x%04x)", _flags, _flags); debugC(2, kDebugLoading, "_looping: %d, _enableScripts %d, _enableSound: %d, _crop %d, _center: %d", _looping, _enableScripts, _enableSound, _crop, _center); } Common::String MovieCastMember::formatInfo() { return Common::String::format( "initialRect: %dx%d@%d,%d, boundingRect: %dx%d@%d,%d, enableScripts: %d, enableSound: %d, looping: %d, crop: %d, center: %d", _initialRect.width(), _initialRect.height(), _initialRect.left, _initialRect.top, _boundingRect.width(), _boundingRect.height(), _boundingRect.left, _boundingRect.top, _enableScripts, _enableSound, _looping, _crop, _center ); } ///////////////////////////////////// // Film loops ///////////////////////////////////// FilmLoopCastMember::FilmLoopCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version) : CastMember(cast, castId, stream) { _type = kCastFilmLoop; _looping = true; _enableSound = true; _crop = false; _center = false; } FilmLoopCastMember::~FilmLoopCastMember() { } bool FilmLoopCastMember::isModified() { if (_frames.size()) return true; if (_initialRect.width() && _initialRect.height()) return true; return false; } Common::Array *FilmLoopCastMember::getSubChannels(Common::Rect &bbox, Channel *channel) { Common::Rect widgetRect(bbox.width() ? bbox.width() : _initialRect.width(), bbox.height() ? bbox.height() : _initialRect.height()); _subchannels.clear(); if (channel->_filmLoopFrame >= _frames.size()) { warning("Film loop frame %d requested, only %d available", channel->_filmLoopFrame, _frames.size()); return &_subchannels; } // get the list of sprite IDs for this frame Common::Array spriteIds; for (Common::HashMap::iterator iter = _frames[channel->_filmLoopFrame].sprites.begin(); iter != _frames[channel->_filmLoopFrame].sprites.end(); ++iter) { spriteIds.push_back(iter->_key); } Common::sort(spriteIds.begin(), spriteIds.end()); // copy the sprites in order to the list for (Common::Array::iterator iter = spriteIds.begin(); iter != spriteIds.end(); ++iter) { Sprite src = _frames[channel->_filmLoopFrame].sprites[*iter]; if (!src._cast) continue; // translate sprite relative to the global bounding box int16 relX = (src._startPoint.x - _initialRect.left) * widgetRect.width() / _initialRect.width(); int16 relY = (src._startPoint.y - _initialRect.top) * widgetRect.height() / _initialRect.height(); int16 absX = relX + bbox.left; int16 absY = relY + bbox.top; int16 width = src._width * widgetRect.width() / _initialRect.width(); int16 height = src._height * widgetRect.height() / _initialRect.height(); Channel chan(&src); chan._currentPoint = Common::Point(absX, absY); chan._width = width; chan._height = height; _subchannels.push_back(chan); } // Initialise the widgets on all of the subchannels. // This has to be done once the list has been constructed, otherwise // the list grow operation will erase the widgets as they aren't // part of the Channel assignment constructor. for (auto &iter : _subchannels) { iter.replaceWidget(); } return &_subchannels; } void FilmLoopCastMember::loadFilmLoopData(Common::SeekableReadStreamEndian &stream) { _initialRect = Common::Rect(); _frames.clear(); uint32 size = stream.readUint32BE(); if (debugChannelSet(5, kDebugLoading)) { debugC(5, kDebugLoading, "SCVW body:"); uint32 pos = stream.pos(); stream.seek(0); stream.hexdump(size); stream.seek(pos); } uint16 channelSize = 16; FilmLoopFrame newFrame; while (stream.pos() < size) { uint16 frameSize = stream.readUint16BE() - 2; if (debugChannelSet(5, kDebugLoading)) { debugC(5, kDebugLoading, "Frame entry:"); stream.hexdump(frameSize); } while (frameSize > 0) { int msgWidth = stream.readByte() * 2; int order = stream.readByte() * 2 - 0x20; frameSize -= 2; debugC(8, kDebugLoading, "Message: msgWidth %d, order %d", msgWidth, order); if (debugChannelSet(8, kDebugLoading)) { stream.hexdump(msgWidth); } int fieldPosition = order; int finishPosition = order + msgWidth; while (fieldPosition < finishPosition) { int channel = (fieldPosition / channelSize); int channelOffset = fieldPosition % channelSize; Sprite sprite(nullptr); sprite._movie = g_director->getCurrentMovie(); if (newFrame.sprites.contains(channel)) { sprite = newFrame.sprites.getVal(channel); } sprite._spriteType = kCastMemberSprite; sprite._puppet = 1; sprite._stretch = 1; switch (channelOffset) { case kSpritePositionUnk1: stream.readByte(); fieldPosition++; break; case kSpritePositionEnabled: sprite._enabled = stream.readByte() != 0; fieldPosition++; break; case kSpritePositionUnk2: stream.readUint16BE(); fieldPosition += 2; break; case kSpritePositionFlags: sprite._thickness = stream.readByte(); sprite._inkData = stream.readByte(); sprite._ink = static_cast(sprite._inkData & 0x3f); if (sprite._inkData & 0x40) sprite._trails = 1; else sprite._trails = 0; fieldPosition += 2; break; case kSpritePositionCastId: sprite.setCast(CastMemberID(stream.readUint16(), 0)); fieldPosition += 2; break; case kSpritePositionY: sprite._startPoint.y = stream.readUint16(); fieldPosition += 2; break; case kSpritePositionX: sprite._startPoint.x = stream.readUint16(); fieldPosition += 2; break; case kSpritePositionWidth: sprite._width = stream.readUint16(); fieldPosition += 2; break; case kSpritePositionHeight: sprite._height = stream.readUint16(); fieldPosition += 2; break; default: stream.readUint16BE(); fieldPosition += 2; break; } newFrame.sprites.setVal(channel, sprite); } frameSize -= msgWidth; } for (Common::HashMap::iterator s = newFrame.sprites.begin(); s != newFrame.sprites.end(); ++s) { debugC(5, kDebugLoading, "Sprite: channel %d, castId %s, bbox %d %d %d %d", s->_key, s->_value._castId.asString().c_str(), s->_value._startPoint.x, s->_value._startPoint.y, s->_value._width, s->_value._height); Common::Point topLeft = s->_value._startPoint + s->_value.getRegistrationOffset(); Common::Rect spriteBbox( topLeft.x, topLeft.y, topLeft.x + s->_value._width, topLeft.y + s->_value._height ); if (!((spriteBbox.width() == 0) && (spriteBbox.height() == 0))) { if ((_initialRect.width() == 0) && (_initialRect.height() == 0)) { _initialRect = spriteBbox; } else { _initialRect.extend(spriteBbox); } } debugC(8, kDebugLoading, "New bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height()); } _frames.push_back(newFrame); } debugC(5, kDebugLoading, "Full bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height()); } void FilmLoopCastMember::loadFilmLoopDataV4(Common::SeekableReadStreamEndian &stream) { _initialRect = Common::Rect(); _frames.clear(); uint32 size = stream.readUint32BE(); if (debugChannelSet(5, kDebugLoading)) { debugC(5, kDebugLoading, "SCVW body:"); uint32 pos = stream.pos(); stream.seek(0); stream.hexdump(size); stream.seek(pos); } uint32 framesOffset = stream.readUint32BE(); if (debugChannelSet(5, kDebugLoading)) { debugC(5, kDebugLoading, "SCVW header:"); stream.hexdump(framesOffset - 8); } stream.skip(6); uint16 channelSize = stream.readUint16BE(); // should be 20! stream.skip(framesOffset - 16); FilmLoopFrame newFrame; while (stream.pos() < size) { uint16 frameSize = stream.readUint16BE() - 2; if (debugChannelSet(5, kDebugLoading)) { debugC(5, kDebugLoading, "Frame entry:"); stream.hexdump(frameSize); } while (frameSize > 0) { uint16 msgWidth = stream.readUint16BE(); uint16 order = stream.readUint16BE(); frameSize -= 4; int channel = (order / channelSize) - 1; int channelOffset = order % channelSize; Sprite sprite(nullptr); sprite._movie = g_director->getCurrentMovie(); if (newFrame.sprites.contains(channel)) { sprite = newFrame.sprites.getVal(channel); } debugC(8, kDebugLoading, "Message: msgWidth %d, channel %d, channelOffset %d", msgWidth, channel, channelOffset); if (debugChannelSet(8, kDebugLoading)) { stream.hexdump(msgWidth); } sprite._puppet = 1; sprite._stretch = 1; int fieldPosition = channelOffset; int finishPosition = channelOffset + msgWidth; while (fieldPosition < finishPosition) { switch (fieldPosition) { case kSpritePositionUnk1: stream.readByte(); fieldPosition++; break; case kSpritePositionEnabled: sprite._enabled = stream.readByte() != 0; fieldPosition++; break; case kSpritePositionUnk2: stream.readUint16BE(); fieldPosition += 2; break; case kSpritePositionFlags: sprite._thickness = stream.readByte(); sprite._inkData = stream.readByte(); sprite._ink = static_cast(sprite._inkData & 0x3f); if (sprite._inkData & 0x40) sprite._trails = 1; else sprite._trails = 0; fieldPosition += 2; break; case kSpritePositionCastId: sprite.setCast(CastMemberID(stream.readUint16(), 0)); fieldPosition += 2; break; case kSpritePositionY: sprite._startPoint.y = stream.readUint16(); fieldPosition += 2; break; case kSpritePositionX: sprite._startPoint.x = stream.readUint16(); fieldPosition += 2; break; case kSpritePositionWidth: sprite._width = stream.readUint16(); fieldPosition += 2; break; case kSpritePositionHeight: sprite._height = stream.readUint16(); fieldPosition += 2; break; default: stream.readUint16BE(); fieldPosition += 2; break; } } frameSize -= msgWidth; newFrame.sprites.setVal(channel, sprite); } for (Common::HashMap::iterator s = newFrame.sprites.begin(); s != newFrame.sprites.end(); ++s) { debugC(5, kDebugLoading, "Sprite: channel %d, castId %s, bbox %d %d %d %d", s->_key, s->_value._castId.asString().c_str(), s->_value._startPoint.x, s->_value._startPoint.y, s->_value._width, s->_value._height); Common::Point topLeft = s->_value._startPoint + s->_value.getRegistrationOffset(); Common::Rect spriteBbox( topLeft.x, topLeft.y, topLeft.x + s->_value._width, topLeft.y + s->_value._height ); if (!((spriteBbox.width() == 0) && (spriteBbox.height() == 0))) { if ((_initialRect.width() == 0) && (_initialRect.height() == 0)) { _initialRect = spriteBbox; } else { _initialRect.extend(spriteBbox); } } debugC(8, kDebugLoading, "New bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height()); } _frames.push_back(newFrame); } debugC(5, kDebugLoading, "Full bounding box: %d %d %d %d", _initialRect.left, _initialRect.top, _initialRect.width(), _initialRect.height()); } Common::String FilmLoopCastMember::formatInfo() { return Common::String::format( "initialRect: %dx%d@%d,%d, boundingRect: %dx%d@%d,%d, frameCount: %d, subchannelCount: %d, enableSound: %d, looping: %d, crop: %d, center: %d", _initialRect.width(), _initialRect.height(), _initialRect.left, _initialRect.top, _boundingRect.width(), _boundingRect.height(), _boundingRect.left, _boundingRect.top, _frames.size(), _subchannels.size(), _enableSound, _looping, _crop, _center ); } ///////////////////////////////////// // Sound ///////////////////////////////////// SoundCastMember::SoundCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version) : CastMember(cast, castId, stream) { _type = kCastSound; _audio = nullptr; _looping = 0; } SoundCastMember::~SoundCastMember() { if (_audio) delete _audio; } Common::String SoundCastMember::formatInfo() { return Common::String::format( "looping: %d", _looping ); } ///////////////////////////////////// // Text ///////////////////////////////////// TextCastMember::TextCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version, uint8 flags1, bool asButton) : CastMember(cast, castId, stream) { _type = kCastText; _borderSize = kSizeNone; _gutterSize = kSizeNone; _boxShadow = kSizeNone; _buttonType = kTypeButton; _editable = false; _maxHeight = _textHeight = 0; _bgcolor = 0; _fgcolor = 0xff; _textFlags = 0; _scroll = 0; _fontId = 1; _fontSize = 12; _textType = kTextTypeFixed; _textAlign = kTextAlignLeft; _textShadow = kSizeNone; _textSlant = 0; _bgpalinfo1 = _bgpalinfo2 = _bgpalinfo3 = 0; _fgpalinfo1 = _fgpalinfo2 = _fgpalinfo3 = 0xff; // seems like the line spacing is default to 1 in D4 _lineSpacing = g_director->getVersion() >= 400 ? 1 : 0; if (version < kFileVer400) { _flags1 = flags1; // region: 0 - auto, 1 - matte, 2 - disabled _borderSize = static_cast(stream.readByte()); _gutterSize = static_cast(stream.readByte()); _boxShadow = static_cast(stream.readByte()); _textType = static_cast(stream.readByte()); _textAlign = static_cast(stream.readUint16()); _bgpalinfo1 = stream.readUint16(); _bgpalinfo2 = stream.readUint16(); _bgpalinfo3 = stream.readUint16(); uint32 pad2; uint16 pad3; uint16 pad4 = 0; uint16 totalTextHeight; if (version < kFileVer300) { pad2 = stream.readUint16(); if (pad2 != 0) { // In D2 there are values warning("TextCastMember: pad2: %x", pad2); } _initialRect = Movie::readRect(stream); pad3 = stream.readUint16(); _textShadow = static_cast(stream.readByte()); _textFlags = stream.readByte(); if (_textFlags & 0xf8) warning("Unprocessed text cast flags: %x", _textFlags & 0xf8); totalTextHeight = stream.readUint16(); } else { pad2 = stream.readUint16(); _initialRect = Movie::readRect(stream); pad3 = stream.readUint16(); _textFlags = stream.readUint16(); // 1: editable, 2: auto tab, 4: don't wrap _editable = _textFlags & 0x1; totalTextHeight = stream.readUint16(); } debugC(2, kDebugLoading, "TextCastMember(): flags1: %d, border: %d gutter: %d shadow: %d textType: %d align: %04x", _flags1, _borderSize, _gutterSize, _boxShadow, _textType, _textAlign); debugC(2, kDebugLoading, "TextCastMember(): background rgb: 0x%04x 0x%04x 0x%04x, pad2: %x pad3: %d pad4: %d shadow: %d flags: %d totHeight: %d", _bgpalinfo1, _bgpalinfo2, _bgpalinfo3, pad2, pad3, pad4, _textShadow, _textFlags, totalTextHeight); if (debugChannelSet(2, kDebugLoading)) { _initialRect.debugPrint(2, "TextCastMember(): rect:"); } } else if (version >= kFileVer400 && version < kFileVer500) { _flags1 = flags1; _borderSize = static_cast(stream.readByte()); _gutterSize = static_cast(stream.readByte()); _boxShadow = static_cast(stream.readByte()); _textType = static_cast(stream.readByte()); _textAlign = static_cast(stream.readSint16()); // this is because 'right' is -1? or should that be 255? _bgpalinfo1 = stream.readUint16(); _bgpalinfo2 = stream.readUint16(); _bgpalinfo3 = stream.readUint16(); _scroll = stream.readUint16(); _fontId = 1; // this is in STXT _initialRect = Movie::readRect(stream); _maxHeight = stream.readUint16(); _textShadow = static_cast(stream.readByte()); _textFlags = stream.readByte(); // 1: editable, 2: auto tab 4: don't wrap _editable = _textFlags & 0x1; _textHeight = stream.readUint16(); _textSlant = 0; debugC(2, kDebugLoading, "TextCastMember(): flags1: %d, border: %d gutter: %d shadow: %d textType: %d align: %04x", _flags1, _borderSize, _gutterSize, _boxShadow, _textType, _textAlign); debugC(2, kDebugLoading, "TextCastMember(): background rgb: 0x%04x 0x%04x 0x%04x, shadow: %d flags: %d textHeight: %d", _bgpalinfo1, _bgpalinfo2, _bgpalinfo3, _textShadow, _textFlags, _textHeight); if (debugChannelSet(2, kDebugLoading)) { _initialRect.debugPrint(2, "TextCastMember(): rect:"); } } else { _fontId = 1; stream.readUint32(); stream.readUint32(); stream.readUint32(); stream.readUint32(); uint16 skip = stream.readUint16(); for (int i = 0; i < skip; i++) stream.readUint32(); stream.readUint32(); stream.readUint32(); stream.readUint32(); stream.readUint32(); stream.readUint32(); stream.readUint32(); _initialRect = Movie::readRect(stream); _boundingRect = Movie::readRect(stream); stream.readUint32(); stream.readUint16(); stream.readUint16(); } if (asButton) { _type = kCastButton; if (version < kFileVer500) { _buttonType = static_cast(stream.readUint16BE() - 1); } else { warning("TextCastMember(): Attempting to initialize >D4 button castmember"); _buttonType = kTypeButton; } } _bgcolor = g_director->_wm->findBestColor(_bgpalinfo1 & 0xff, _bgpalinfo2 & 0xff, _bgpalinfo3 & 0xff); _modified = true; } void TextCastMember::setColors(uint32 *fgcolor, uint32 *bgcolor) { if (fgcolor) _fgcolor = *fgcolor; if (bgcolor) _bgcolor = *bgcolor; // if we want to keep the format unchanged, then we need to modify _ftext as well if (_widget) ((Graphics::MacText *)_widget)->setColors(_fgcolor, _bgcolor); else _modified = true; } Graphics::TextAlign TextCastMember::getAlignment() { switch (_textAlign) { case kTextAlignRight: return Graphics::kTextAlignRight; case kTextAlignCenter: return Graphics::kTextAlignCenter; case kTextAlignLeft: default: return Graphics::kTextAlignLeft; } } void TextCastMember::setBackColor(uint32 bgCol) { _bgcolor = bgCol; _modified = true; } void TextCastMember::setForeColor(uint32 fgCol) { _fgcolor = fgCol; _modified = true; } void TextCastMember::importStxt(const Stxt *stxt) { _fontId = stxt->_style.fontId; _textSlant = stxt->_style.textSlant; _fontSize = stxt->_style.fontSize; _fgpalinfo1 = stxt->_style.r; _fgpalinfo2 = stxt->_style.g; _fgpalinfo3 = stxt->_style.b; _ftext = stxt->_ftext; _ptext = stxt->_ptext; _rtext = stxt->_rtext; // Rectifying _fontId in case of a fallback font Graphics::MacFont macFont(_fontId, _fontSize, _textSlant); g_director->_wm->_fontMan->getFont(&macFont); _fontId = macFont.getId(); } Graphics::MacWidget *TextCastMember::createWidget(Common::Rect &bbox, Channel *channel, SpriteType spriteType) { Graphics::MacFont *macFont = new Graphics::MacFont(_fontId, _fontSize, _textSlant); Graphics::MacWidget *widget = nullptr; Common::Rect dims(bbox); CastType type = _type; ButtonType buttonType = _buttonType; // WORKAROUND: In D2/D3 there can be text casts that have button // information set in the sprite. if (type == kCastText && isButtonSprite(spriteType)) { type = kCastButton; buttonType = ButtonType(spriteType - 8); } switch (type) { case kCastText: // for mactext, we can expand now, but we can't shrink. so we may pass the small size when we have adjustToFit text style if (_textType == kTextTypeAdjustToFit) { dims.right = MIN(dims.right, dims.left + _initialRect.width()); dims.bottom = MIN(dims.bottom, dims.top + _initialRect.height()); } else if (_textType == kTextTypeFixed){ // use initialRect to create widget for fixed style text, this maybe related to version. dims.right = MAX(dims.right, dims.left + _initialRect.width()); dims.bottom = MAX(dims.bottom, dims.top + _initialRect.height()); } widget = new Graphics::MacText(g_director->getCurrentWindow(), bbox.left, bbox.top, dims.width(), dims.height(), g_director->_wm, _ftext, macFont, getForeColor(), getBackColor(), _initialRect.width(), getAlignment(), _lineSpacing, _borderSize, _gutterSize, _boxShadow, _textShadow, _textType == kTextTypeFixed); ((Graphics::MacText *)widget)->setSelRange(g_director->getCurrentMovie()->_selStart, g_director->getCurrentMovie()->_selEnd); ((Graphics::MacText *)widget)->setEditable(channel->_sprite->_editable); ((Graphics::MacText *)widget)->draw(); // since we disable the ability of setActive in setEdtiable, then we need to set active widget manually if (channel->_sprite->_editable) { Graphics::MacWidget *activeWidget = g_director->_wm->getActiveWidget(); if (activeWidget == nullptr || !activeWidget->isEditable()) g_director->_wm->setActiveWidget(widget); } break; case kCastButton: // note that we use _initialRect for the dimensions of the button; // the values provided in the sprite bounding box are ignored widget = new Graphics::MacButton(Graphics::MacButtonType(buttonType), getAlignment(), g_director->getCurrentWindow(), bbox.left, bbox.top, _initialRect.width(), _initialRect.height(), g_director->_wm, _ftext, macFont, getForeColor(), g_director->_wm->_colorWhite); widget->_focusable = true; ((Graphics::MacButton *)widget)->setHilite(_hilite); ((Graphics::MacButton *)widget)->setCheckBoxType(g_director->getCurrentMovie()->_checkBoxType); ((Graphics::MacButton *)widget)->draw(); break; default: break; } delete macFont; return widget; } void TextCastMember::importRTE(byte *text) { //assert(rteList.size() == 3); //child0 is probably font data. //child1 is the raw text. _rtext = _ptext = _ftext = Common::String((char*)text); //child2 is positional? } void TextCastMember::setRawText(const Common::String &text) { // Do nothing if text did not change if (_rtext.equals(text)) return; _rtext = text; _ptext = Common::U32String(text); // If text has changed, use the cached formatting from first STXT in this castmember. Common::U32String formatting = Common::String::format("\001\016%04x%02x%04x%04x%04x%04x", _fontId, _textSlant, _fontSize, _fgpalinfo1, _fgpalinfo2, _fgpalinfo3); _ftext = formatting + _ptext; _modified = true; } // D4 dictionary book said this is line spacing int TextCastMember::getTextHeight() { if (_widget) return ((Graphics::MacText *)_widget)->getLineSpacing(); else return _lineSpacing; return 0; } // this should be amend when we have some where using this function int TextCastMember::getTextSize() { if (_widget) return ((Graphics::MacText *)_widget)->getTextSize(); else return _fontSize; return 0; } Common::U32String TextCastMember::getText() { return _ptext; } Common::String TextCastMember::getRawText() { return _rtext; } void TextCastMember::setTextSize(int textSize) { if (_widget) { ((Graphics::MacText *)_widget)->setTextSize(textSize); ((Graphics::MacText *)_widget)->draw(); } else { _fontSize = textSize; _modified = true; } } void TextCastMember::updateFromWidget(Graphics::MacWidget *widget) { if (widget && _type == kCastText) { _ptext = ((Graphics::MacText *)widget)->getEditedString(); } } Common::String TextCastMember::formatInfo() { Common::String format = formatStringForDump(_ptext.encode()); return Common::String::format( "initialRect: %dx%d@%d,%d, boundingRect: %dx%d@%d,%d, foreColor: %d, backColor: %d, editable: %d, text: \"%s\"", _initialRect.width(), _initialRect.height(), _initialRect.left, _initialRect.top, _boundingRect.width(), _boundingRect.height(), _boundingRect.left, _boundingRect.top, getForeColor(), getBackColor(), _editable, format.c_str() ); } ///////////////////////////////////// // Shape ///////////////////////////////////// ShapeCastMember::ShapeCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version) : CastMember(cast, castId, stream) { _type = kCastShape; byte unk1; _ink = kInkTypeCopy; if (version < kFileVer400) { unk1 = stream.readByte(); _shapeType = static_cast(stream.readByte()); _initialRect = Movie::readRect(stream); _pattern = stream.readUint16BE(); // Normalize D2 and D3 colors from -128 ... 127 to 0 ... 255. _fgCol = g_director->transformColor((128 + stream.readByte()) & 0xff); _bgCol = g_director->transformColor((128 + stream.readByte()) & 0xff); _fillType = stream.readByte(); _ink = static_cast(_fillType & 0x3f); _lineThickness = stream.readByte(); _lineDirection = stream.readByte(); } else if (version >= kFileVer400 && version < kFileVer500) { unk1 = stream.readByte(); _shapeType = static_cast(stream.readByte()); _initialRect = Movie::readRect(stream); _pattern = stream.readUint16BE(); _fgCol = g_director->transformColor((uint8)stream.readByte()); _bgCol = g_director->transformColor((uint8)stream.readByte()); _fillType = stream.readByte(); _ink = static_cast(_fillType & 0x3f); _lineThickness = stream.readByte(); _lineDirection = stream.readByte(); } else { stream.readByte(); // FIXME: Was this copied from D4 by mistake? unk1 = stream.readByte(); _initialRect = Movie::readRect(stream); _boundingRect = Movie::readRect(stream); _shapeType = kShapeRectangle; _pattern = 0; _fgCol = _bgCol = 0; _fillType = 0; _lineThickness = 1; _lineDirection = 0; } _modified = false; debugC(3, kDebugLoading, "ShapeCastMember: unk1: %x type: %d pat: %d fg: %d bg: %d fill: %d thick: %d dir: %d", unk1, _shapeType, _pattern, _fgCol, _bgCol, _fillType, _lineThickness, _lineDirection); if (debugChannelSet(3, kDebugLoading)) _initialRect.debugPrint(0, "ShapeCastMember: rect:"); } void ShapeCastMember::setBackColor(uint32 bgCol) { _bgCol = bgCol; _modified = true; } void ShapeCastMember::setForeColor(uint32 fgCol) { _fgCol = fgCol; _modified = true; } Common::String ShapeCastMember::formatInfo() { return Common::String::format( "initialRect: %dx%d@%d,%d, boundingRect: %dx%d@%d,%d, foreColor: %d, backColor: %d, shapeType: %d, pattern: %d, fillType: %d, lineThickness: %d, lineDirection: %d, ink: %d", _initialRect.width(), _initialRect.height(), _initialRect.left, _initialRect.top, _boundingRect.width(), _boundingRect.height(), _boundingRect.left, _boundingRect.top, getForeColor(), getBackColor(), _shapeType, _pattern, _fillType, _lineThickness, _lineDirection, _ink ); } ///////////////////////////////////// // Script ///////////////////////////////////// ScriptCastMember::ScriptCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version) : CastMember(cast, castId, stream) { _type = kCastLingoScript; _scriptType = kNoneScript; if (debugChannelSet(5, kDebugLoading)) { debugC(5, kDebugLoading, "ScriptCastMember::ScriptCastMember(): Contents"); stream.hexdump(stream.size()); } if (version < kFileVer400) { error("Unhandled Script cast"); } else if (version >= kFileVer400 && version < kFileVer600) { byte unk1 = stream.readByte(); byte type = stream.readByte(); switch (type) { case 1: _scriptType = kScoreScript; break; case 3: _scriptType = kMovieScript; break; default: error("ScriptCastMember: Unprocessed script type: %d", type); } debugC(3, kDebugLoading, "CASt: Script type: %s (%d), unk1: %d", scriptType2str(_scriptType), type, unk1); stream.readByte(); // There should be no more data assert(stream.eos()); } } ///////////////////////////////////// // RTE ///////////////////////////////////// RTECastMember::RTECastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version) : TextCastMember(cast, castId, stream, version) { _type = kCastRTE; } void RTECastMember::loadChunks() { //TODO: Actually load RTEs correctly, don't just make fake STXT. #if 0 Common::SeekableReadStream *rte1 = _movieArchive->getResource(res->children[child].tag, res->children[child].index); byte *buffer = new byte[rte1->size() + 2]; rte1->read(buffer, rte1->size()); buffer[rte1->size()] = '\n'; buffer[rte1->size() + 1] = '\0'; _loadedText->getVal(id)->importRTE(buffer); delete rte1; #endif } ///////////////////////////////////// // Palette ///////////////////////////////////// PaletteCastMember::PaletteCastMember(Cast *cast, uint16 castId, Common::SeekableReadStreamEndian &stream, uint16 version) : CastMember(cast, castId, stream) { _type = kCastPalette; _palette = nullptr; } Common::String PaletteCastMember::formatInfo() { Common::String result; if (_palette) { result = "data: "; for (size_t i = 0; i < (size_t)_palette->length; i++) { result += Common::String::format("%02X%02X%02X", _palette->palette[3 * i], _palette->palette[3 * i + 1], _palette->palette[3 * i + 2]); } } return result; } } // End of namespace Director