/* 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 "ultima/ultima4/gfx/image.h" #include "ultima/ultima4/gfx/imageloader_u4.h" #include "ultima/ultima4/gfx/imageloader_fmtowns.h" #include "ultima/ultima4/gfx/imagemgr.h" #include "ultima/ultima4/controllers/intro_controller.h" #include "ultima/ultima4/core/config.h" #include "ultima/ultima4/core/settings.h" #include "ultima/ultima4/ultima4.h" #include "image/png.h" namespace Ultima { namespace Ultima4 { bool ImageInfo::hasBlackBackground() { return this->_filetype == "image/x-u4raw"; } class ImageSet { public: ~ImageSet(); Common::String _name; Common::String _location; Common::String _extends; Common::HashMap _info; }; ImageMgr *ImageMgr::_instance = nullptr; ImageMgr *ImageMgr::getInstance() { if (_instance == nullptr) { _instance = new ImageMgr(); _instance->init(); } return _instance; } void ImageMgr::destroy() { if (_instance != nullptr) { delete _instance; _instance = nullptr; } } ImageMgr::ImageMgr() : _baseSet(nullptr), _abyssData(nullptr) { settings.addObserver(this); } ImageMgr::~ImageMgr() { settings.deleteObserver(this); for (auto &i : _imageSets) delete i._value; delete[] _abyssData; } void ImageMgr::init() { /* * register the "screen" image representing the entire screen */ Image *screen = Image::createScreenImage(); _screenInfo._name = "screen"; _screenInfo._filename = ""; _screenInfo._width = screen->width(); _screenInfo._height = screen->height(); _screenInfo._depth = 0; _screenInfo._prescale = 0; _screenInfo._filetype = ""; _screenInfo._tiles = 0; _screenInfo._introOnly = false; _screenInfo._transparentIndex = -1; _screenInfo._xu4Graphic = false; _screenInfo._fixup = FIXUP_NONE; _screenInfo._image = screen; /* * register all the images declared in the config files */ const Config *config = Config::getInstance(); Std::vector graphicsConf = config->getElement("graphics").getChildren(); for (const auto &conf : graphicsConf) { if (conf.getName() == "imageset") { ImageSet *set = loadImageSetFromConf(conf); _imageSets[set->_name] = set; // all image sets include the "screen" image set->_info[_screenInfo._name] = &_screenInfo; } } _imageSetNames.clear(); for (const auto &set : _imageSets) _imageSetNames.push_back(set._key); update(&settings); } ImageSet *ImageMgr::loadImageSetFromConf(const ConfigElement &conf) { ImageSet *set; set = new ImageSet(); set->_name = conf.getString("name"); set->_location = conf.getString("location"); set->_extends = conf.getString("extends"); Std::vector children = conf.getChildren(); for (const auto &i : children) { if (i.getName() == "image") { ImageInfo *info = loadImageInfoFromConf(i); if (set->_info.contains(info->_name)) delete set->_info[info->_name]; set->_info[info->_name] = info; } } return set; } ImageInfo *ImageMgr::loadImageInfoFromConf(const ConfigElement &conf) { ImageInfo *info; static const char *const fixupEnumStrings[] = { "none", "intro", "abyss", "abacus", "dungns", "blackTransparencyHack", "fmtownsscreen", nullptr }; info = new ImageInfo(); info->_name = conf.getString("name"); info->_filename = conf.getString("filename"); info->_width = conf.getInt("width", -1); info->_height = conf.getInt("height", -1); info->_depth = conf.getInt("depth", -1); info->_prescale = conf.getInt("prescale"); info->_filetype = conf.getString("filetype"); info->_tiles = conf.getInt("tiles"); info->_introOnly = conf.getBool("introOnly"); info->_transparentIndex = conf.getInt("transparentIndex", -1); info->_xu4Graphic = conf.getBool("xu4Graphic"); info->_fixup = static_cast(conf.getEnum("fixup", fixupEnumStrings)); info->_image = nullptr; Std::vector children = conf.getChildren(); for (const auto &i : children) { if (i.getName() == "subimage") { SubImage *subimage = loadSubImageFromConf(info, i); info->_subImages[subimage->_name] = subimage; } } return info; } SubImage *ImageMgr::loadSubImageFromConf(const ImageInfo *info, const ConfigElement &conf) { SubImage *subimage; static int x = 0, y = 0, last_width = 0, last_height = 0; subimage = new SubImage(); subimage->_name = conf.getString("name"); subimage->setWidth(conf.getInt("width")); subimage->setHeight(conf.getInt("height")); subimage->_srcImageName = info->_name; if (conf.exists("x") && conf.exists("y")) { x = conf.getInt("x"); y = conf.getInt("y"); subimage->moveTo(x, y); } else { // Automatically increment our position through the base image x += last_width; if (x >= last_width) { x = 0; y += last_height; } subimage->moveTo(x, y); } // "remember" the width and height of this subimage last_width = subimage->width(); last_height = subimage->height(); return subimage; } void ImageMgr::fixupIntro(Image *im, int prescale) { const byte *sigData; int i, x, y; bool alpha = im->isAlphaOn(); RGBA color; sigData = g_intro->getSigData(); im->alphaOff(); if (settings._videoType != "VGA-ALLPNG" && settings._videoType != "new") { /* ---------------------------- * update the position of "and" * ---------------------------- */ im->drawSubRectOn(im, 148 * prescale, 17 * prescale, 153 * prescale, 17 * prescale, 11 * prescale, 4 * prescale); im->drawSubRectOn(im, 159 * prescale, 17 * prescale, 165 * prescale, 18 * prescale, 1 * prescale, 4 * prescale); im->drawSubRectOn(im, 160 * prescale, 17 * prescale, 164 * prescale, 17 * prescale, 16 * prescale, 4 * prescale); /* --------------------------------------------- * update the position of "Origin Systems, Inc." * --------------------------------------------- */ im->drawSubRectOn(im, 86 * prescale, 21 * prescale, 88 * prescale, 21 * prescale, 114 * prescale, 9 * prescale); im->drawSubRectOn(im, 199 * prescale, 21 * prescale, 202 * prescale, 21 * prescale, 6 * prescale, 9 * prescale); im->drawSubRectOn(im, 207 * prescale, 21 * prescale, 208 * prescale, 21 * prescale, 28 * prescale, 9 * prescale); /* --------------------------------------------- * update the position of "Ultima IV" * --------------------------------------------- */ // move this *prior* to moving "present" im->drawSubRectOn(im, 59 * prescale, 33 * prescale, 61 * prescale, 33 * prescale, 204 * prescale, 46 * prescale); /* --------------------------------------------- * update the position of "Quest of the Avatar" * --------------------------------------------- */ im->drawSubRectOn(im, 69 * prescale, 80 * prescale, // quEst 70 * prescale, 80 * prescale, 11 * prescale, 13 * prescale); im->drawSubRectOn(im, 82 * prescale, 80 * prescale, // queST 84 * prescale, 80 * prescale, 27 * prescale, 13 * prescale); im->drawSubRectOn(im, 131 * prescale, 80 * prescale, // oF 132 * prescale, 80 * prescale, 11 * prescale, 13 * prescale); im->drawSubRectOn(im, 150 * prescale, 80 * prescale, // THE 149 * prescale, 80 * prescale, 40 * prescale, 13 * prescale); im->drawSubRectOn(im, 166 * prescale, 80 * prescale, // tHe 165 * prescale, 80 * prescale, 11 * prescale, 13 * prescale); im->drawSubRectOn(im, 200 * prescale, 80 * prescale, // AVATAR 201 * prescale, 80 * prescale, 81 * prescale, 13 * prescale); im->drawSubRectOn(im, 227 * prescale, 80 * prescale, // avAtar 228 * prescale, 80 * prescale, 11 * prescale, 13 * prescale); } /* ----------------------------------------------------------------------------- * copy "present" to new location between "Origin Systems, Inc." and "Ultima IV" * ----------------------------------------------------------------------------- */ // do this *after* moving "Ultima IV" im->drawSubRectOn(im, 132 * prescale, 33 * prescale, 135 * prescale, 0 * prescale, 56 * prescale, 5 * prescale); if (alpha) { im->alphaOn(); } /* ---------------------------- * erase the original "present" * ---------------------------- */ im->fillRect(135 * prescale, 0 * prescale, 56 * prescale, 5 * prescale, 0, 0, 0); /* ------------------------- * update the colors for VGA * ------------------------- */ if (settings._videoType == "VGA") { ImageInfo *borderInfo = imageMgr->get(BKGD_BORDERS, true); // ImageInfo *charsetInfo = imageMgr->get(BKGD_CHARSET); if (!borderInfo) error("ERROR 1001: Unable to load the \"%s\" data file", BKGD_BORDERS); delete borderInfo->_image; borderInfo->_image = nullptr; borderInfo = imageMgr->get(BKGD_BORDERS, true); im->setPaletteFromImage(borderInfo->_image); // update the color of "and" and "present" (void)im->setPaletteIndex(15, 226, 226, 255); // update the color of "Origin Systems, Inc." (void)im->setPaletteIndex(9, 129, 129, 255); #ifdef TODO borderInfo->_image->save("border.png"); #endif // update the border appearance borderInfo->_image->alphaOff(); borderInfo->_image->drawSubRectOn(im, 0, 96, 0, 0, 16, 56); for (i = 0; i < 9; i++) { borderInfo->_image->drawSubRectOn(im, 16 + (i * 32), 96, 144, 0, 48, 48); } im->drawSubRectInvertedOn(im, 0, 144, 0, 104, 320, 40); im->drawSubRectOn(im, 0, 184, 0, 96, 320, 8); borderInfo->_image->alphaOn(); delete borderInfo->_image; borderInfo->_image = nullptr; } /* ----------------------------- * draw "Lord British" signature * ----------------------------- */ color = im->setColor(0, 255, 255); // cyan for EGA int blue[16] = {255, 250, 226, 226, 210, 194, 161, 161, 129, 97, 97, 64, 64, 32, 32, 0 }; i = 0; while (sigData[i] != 0) { /* (x/y) are unscaled coordinates, i.e. in 320x200 */ x = sigData[i] + 0x14; y = 0xBF - sigData[i + 1]; if (settings._videoType != "EGA") { // yellow gradient color = im->setColor(255, (y == 1 ? 250 : 255), blue[y]); } im->fillRect(x * prescale, y * prescale, 2 * prescale, prescale, color.r, color.g, color.b); i += 2; } /* -------------------------------------------------------------- * draw the red line between "Origin Systems, Inc." and "present" * -------------------------------------------------------------- */ /* we're still working with an unscaled surface */ if (settings._videoType != "EGA") { color = im->setColor(0, 0, 161); // dark blue } else { color = im->setColor(128, 0, 0); // dark red for EGA } for (i = 84; i < 236; i++) // 152 px wide im->fillRect(i * prescale, 31 * prescale, prescale, prescale, color.r, color.g, color.b); } void ImageMgr::fixupAbyssVision(Image *im, int prescale) { // Ignore fixups for xu4 PNG images - they're already correct if (im->isIndexed()) return; /* * Each VGA vision components must be XORed with all the previous * vision components to get the actual image. */ if (_abyssData) { for (int y = 0; y < im->height(); y++) { for (int x = 0; x < im->width(); x++) { uint index; im->getPixelIndex(x, y, index); index ^= _abyssData[y * im->width() + x]; im->putPixelIndex(x, y, index); } } } else { _abyssData = new uint[im->width() * im->height()]; } for (int y = 0; y < im->height(); y++) { for (int x = 0; x < im->width(); x++) { uint index; im->getPixelIndex(x, y, index); _abyssData[y * im->width() + x] = index; } } } void ImageMgr::fixupAbacus(Image *im, int prescale) { /* * surround each bead with a row green pixels to avoid artifacts * when scaling */ im->fillRect(7 * prescale, 186 * prescale, prescale, 14 * prescale, 0, 255, 80); /* green */ im->fillRect(16 * prescale, 186 * prescale, prescale, 14 * prescale, 0, 255, 80); /* green */ im->fillRect(8 * prescale, 186 * prescale, prescale * 8, prescale, 0, 255, 80); /* green */ im->fillRect(8 * prescale, 199 * prescale, prescale * 8, prescale, 0, 255, 80); /* green */ im->fillRect(23 * prescale, 186 * prescale, prescale, 14 * prescale, 0, 255, 80); /* green */ im->fillRect(32 * prescale, 186 * prescale, prescale, 14 * prescale, 0, 255, 80); /* green */ im->fillRect(24 * prescale, 186 * prescale, prescale * 8, prescale, 0, 255, 80); /* green */ im->fillRect(24 * prescale, 199 * prescale, prescale * 8, prescale, 0, 255, 80); /* green */ } void ImageMgr::fixupDungNS(Image *im, int prescale) { for (int y = 0; y < im->height(); y++) { for (int x = 0; x < im->width(); x++) { uint index; im->getPixelIndex(x, y, index); if (index == 1) im->putPixelIndex(x, y, 2); else if (index == 2) im->putPixelIndex(x, y, 1); } } } void ImageMgr::fixupFMTowns(Image *im, int prescale) { for (int y = 20; y < im->height(); y++) { for (int x = 0; x < im->width(); x++) { uint index; im->getPixelIndex(x, y, index); im->putPixelIndex(x, y - 20, index); } } } ImageSet *ImageMgr::getSet(const Common::String &setname) { Common::HashMap::iterator i = _imageSets.find(setname); if (i != _imageSets.end()) return i->_value; else return nullptr; } ImageInfo *ImageMgr::getInfo(const Common::String &name) { return getInfoFromSet(name, _baseSet); } ImageInfo *ImageMgr::getInfoFromSet(const Common::String &name, ImageSet *imageset) { if (!imageset) return nullptr; /* if the image set contains the image we want, AND IT EXISTS we are done */ Common::HashMap::iterator i = imageset->_info.find(name); if (i != imageset->_info.end()) if (imageExists(i->_value)) return i->_value; /* otherwise if this image set extends another, check the base image set */ while (imageset->_extends != "") { imageset = getSet(imageset->_extends); return getInfoFromSet(name, imageset); } //warning("Searched recursively from imageset %s through to %s and couldn't find %s", baseSet->name.c_str(), imageset->name.c_str(), name.c_str()); return nullptr; } Common::String ImageMgr::guessFileType(const Common::String &filename) { if (filename.size() >= 4 && filename.hasSuffixIgnoreCase(".png")) { return "image/png"; } else { return ""; } } bool ImageMgr::imageExists(ImageInfo *info) { if (info->_filename.empty()) //If it is an abstract image like "screen" return true; Common::File *file = getImageFile(info); if (file) { delete file; return true; } return false; } Common::File *ImageMgr::getImageFile(ImageInfo *info) { Common::String filename = info->_filename; if (filename.empty()) return nullptr; Common::File *file = new Common::File(); if (!info->_xu4Graphic) { // It's a file in the game folder if (file->open(Common::Path(filename))) return file; } if (file->open(Common::Path("data/graphics/").joinInPlace(filename))) return file; delete file; return nullptr; } ImageInfo *ImageMgr::get(const Common::String &name, bool returnUnscaled) { ImageInfo *info = getInfo(name); if (!info) return nullptr; /* return if already loaded */ if (info->_image != nullptr) return info; Common::File *file = getImageFile(info); Image *unscaled = nullptr; if (file) { if (info->_filetype.empty()) info->_filetype = guessFileType(info->_filename); Common::String filetype = info->_filetype; ::Image::ImageDecoder *decoder = createDecoder(filetype, info->_width, info->_height, info->_depth); if (decoder == nullptr) { warning("can't find loader to load image \"%s\" with type \"%s\"", info->_filename.c_str(), filetype.c_str()); } else { if (!decoder->loadStream(*file)) { warning("can't load image \"%s\" with type \"%s\"", info->_filename.c_str(), filetype.c_str()); } else { const Graphics::Surface *surface = decoder->getSurface(); unscaled = Image::create(surface->w, surface->h, surface->format); unscaled->blitFrom(*surface); const Graphics::Palette &pal = decoder->getPalette(); if (!pal.empty()) { unscaled->setPalette(pal.data(), pal.size()); } if (info->_width == -1) { // Write in the values for later use. info->_width = unscaled->width(); info->_height = unscaled->height(); // ### info->depth = ??? } } delete decoder; } delete file; } else { warning("Failed to open file %s for reading.", info->_filename.c_str()); return nullptr; } if (unscaled == nullptr) return nullptr; if (info->_transparentIndex != -1) unscaled->setTransparentIndex(info->_transparentIndex); if (info->_prescale == 0) info->_prescale = 1; /* * fixup the image before scaling it */ switch (info->_fixup) { case FIXUP_NONE: break; case FIXUP_INTRO: fixupIntro(unscaled, info->_prescale); break; case FIXUP_ABYSS: fixupAbyssVision(unscaled, info->_prescale); break; case FIXUP_ABACUS: fixupAbacus(unscaled, info->_prescale); break; case FIXUP_DUNGNS: fixupDungNS(unscaled, info->_prescale); break; case FIXUP_FMTOWNSSCREEN: fixupFMTowns(unscaled, info->_prescale); break; case FIXUP_BLACKTRANSPARENCYHACK: //Apply transparency shadow hack to ultima4 ega and vga upgrade classic graphics. if (Settings::getInstance()._enhancements && Settings::getInstance()._enhancementsOptions._u4TileTransparencyHack) { // TODO: Pick a more optimal pixel format? Image *unscaled_original = unscaled; unscaled = Image::duplicate(unscaled, Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0)); delete unscaled_original; int transparency_shadow_size = Settings::getInstance()._enhancementsOptions._u4TrileTransparencyHackShadowBreadth; int black_index = 0; int opacity = Settings::getInstance()._enhancementsOptions._u4TileTransparencyHackPixelShadowOpacity; int frames = info->_tiles; for (int f = 0; f < frames; ++f) unscaled->performTransparencyHack(black_index, frames, f, transparency_shadow_size, opacity); } break; } if (returnUnscaled) { info->_image = unscaled; return info; } int imageScale = settings._scale; if ((settings._scale % info->_prescale) != 0) { int orig_scale = settings._scale; settings._scale = info->_prescale; settings.write(); error("image %s is prescaled to an incompatible size: %d\nResetting the scale to %d. Sorry about the inconvenience, please restart.", info->_filename.c_str(), orig_scale, settings._scale); } imageScale /= info->_prescale; info->_image = g_screen->screenScale(unscaled, imageScale, info->_tiles, 1); delete unscaled; return info; } SubImage *ImageMgr::getSubImage(const Common::String &name) { Common::String setname; ImageSet *set = _baseSet; while (set != nullptr) { for (auto &i : set->_info) { ImageInfo *info = (ImageInfo *) i._value; Common::HashMap::iterator j = info->_subImages.find(name); if (j != info->_subImages.end()) return j->_value; } set = getSet(set->_extends); } return nullptr; } void ImageMgr::freeIntroBackgrounds() { for (const auto &i : _imageSets) { ImageSet *set = i._value; for (auto &j : set->_info) { ImageInfo *info = j._value; if (info->_image != nullptr && info->_introOnly) { delete info->_image; info->_image = nullptr; } } } } const Std::vector &ImageMgr::getSetNames() { return _imageSetNames; } void ImageMgr::update(Settings *newSettings) { Common::String setname; setname = newSettings->_videoType; _baseSet = getSet(setname); } ::Image::ImageDecoder *ImageMgr::createDecoder(const Common::String &fileType, int width, int height, int bpp) { if (fileType == "image/png") return new ::Image::PNGDecoder(); if (fileType == "image/x-u4raw") return new U4RawImageDecoder(width, height, bpp); if (fileType == "image/x-u4rle") return new U4RleImageDecoder(width, height, bpp); if (fileType == "image/x-u4lzw") return new U4LzwImageDecoder(width, height, bpp); if (fileType == "image/fmtowns-tif") return new FMTOWNSImageDecoder(width, height, bpp, 510); return nullptr; } ImageSet::~ImageSet() { for (const auto &i : _info) { ImageInfo *imageInfo = i._value; if (imageInfo->_name != "screen") delete imageInfo; } } ImageInfo::~ImageInfo() { for (auto &i : _subImages) delete i._value; if (_image != nullptr) delete _image; } } // End of namespace Ultima4 } // End of namespace Ultima