diff --git a/client/renderSDL/RenderHandler.cpp b/client/renderSDL/RenderHandler.cpp index 2812e1696..7d7f2e8a9 100644 --- a/client/renderSDL/RenderHandler.cpp +++ b/client/renderSDL/RenderHandler.cpp @@ -26,6 +26,7 @@ #include "../../lib/CThreadHelper.h" #include "../../lib/filesystem/Filesystem.h" #include "../../lib/VCMIDirs.h" +#include "../../lib/constants/StringConstants.h" #include #include @@ -218,8 +219,6 @@ void RenderHandler::storeCachedImage(const ImageLocator & locator, std::shared_p std::shared_ptr RenderHandler::loadScaledImage(const ImageLocator & locator) { - assert(locator.scalingFactor > 1); - static constexpr std::array scaledDataPath = { "", // 0x "DATA/", @@ -260,6 +259,10 @@ std::shared_ptr RenderHandler::loadScaledImage(const ImageLocato imagePathString += "-OVERLAY"; if(locator.layer == EImageBlitMode::ONLY_SHADOW) imagePathString += "-SHADOW"; + if(locator.playerColored.isValidPlayer()) + imagePathString += "-" + boost::to_upper_copy(GameConstants::PLAYER_COLOR_NAMES[locator.playerColored.getNum()]); + if(locator.playerColored == PlayerColor::NEUTRAL) + imagePathString += "-NEUTRAL"; auto imagePath = ImagePath::builtin(imagePathString); auto imagePathSprites = ImagePath::builtin(imagePathString).addPrefix(scaledSpritesPath.at(locator.scalingFactor)); diff --git a/client/renderSDL/ScalableImage.cpp b/client/renderSDL/ScalableImage.cpp index dd22b41cf..93ec8d803 100644 --- a/client/renderSDL/ScalableImage.cpp +++ b/client/renderSDL/ScalableImage.cpp @@ -202,30 +202,30 @@ void ScalableImageParameters::adjustPalette(const SDL_Palette * originalPalette, ScalableImageShared::ScalableImageShared(const SharedImageLocator & locator, const std::shared_ptr & baseImage) :locator(locator) { - base[0] = baseImage; - assert(base[0] != nullptr); + scaled[1].body[0] = baseImage; + assert(scaled[1].body[0] != nullptr); loadScaledImages(GH.screenHandler().getScalingFactor(), PlayerColor::CANNOT_DETERMINE); } Point ScalableImageShared::dimensions() const { - return base[0]->dimensions(); + return scaled[1].body[0]->dimensions(); } void ScalableImageShared::exportBitmap(const boost::filesystem::path & path, const ScalableImageParameters & parameters) const { - base[0]->exportBitmap(path, parameters.palette); + scaled[1].body[0]->exportBitmap(path, parameters.palette); } bool ScalableImageShared::isTransparent(const Point & coords) const { - return base[0]->isTransparent(coords); + return scaled[1].body[0]->isTransparent(coords); } Rect ScalableImageShared::contentRect() const { - return base[0]->contentRect(); + return scaled[1].body[0]->contentRect(); } void ScalableImageShared::draw(SDL_Surface * where, const Point & dest, const Rect * src, const ScalableImageParameters & parameters, int scalingFactor) @@ -256,44 +256,37 @@ void ScalableImageShared::draw(SDL_Surface * where, const Point & dest, const Re getFlippedImage(images)->draw(where, parameters.palette, dest, src, colorMultiplier, alphaValue, locator.layer); }; - if (scalingFactor == 1) + bool shadowLoading = scaled.at(scalingFactor).shadow.at(0) && scaled.at(scalingFactor).shadow.at(0)->isLoading(); + bool bodyLoading = scaled.at(scalingFactor).body.at(0) && scaled.at(scalingFactor).body.at(0)->isLoading(); + bool overlayLoading = scaled.at(scalingFactor).overlay.at(0) && scaled.at(scalingFactor).overlay.at(0)->isLoading(); + bool playerLoading = parameters.player != PlayerColor::CANNOT_DETERMINE && scaled.at(scalingFactor).playerColored.at(1+parameters.player.getNum()) && scaled.at(scalingFactor).playerColored.at(1+parameters.player.getNum())->isLoading(); + + if (shadowLoading || bodyLoading || overlayLoading || playerLoading) { - flipAndDraw(base, parameters.colorMultiplier, parameters.alphaValue); + getFlippedImage(scaled[1].body)->scaledDraw(where, parameters.palette, dimensions() * scalingFactor, dest, src, parameters.colorMultiplier, parameters.alphaValue, locator.layer); + return; + } + + if (scaled.at(scalingFactor).shadow.at(0)) + flipAndDraw(scaled.at(scalingFactor).shadow, Colors::WHITE_TRUE, parameters.alphaValue); + + if (parameters.player != PlayerColor::CANNOT_DETERMINE && scaled.at(scalingFactor).playerColored.at(1+parameters.player.getNum())) + { + scaled.at(scalingFactor).playerColored.at(1+parameters.player.getNum())->draw(where, parameters.palette, dest, src, Colors::WHITE_TRUE, parameters.alphaValue, locator.layer); } else { - bool shadowLoading = scaled.at(scalingFactor).shadow.at(0) && scaled.at(scalingFactor).shadow.at(0)->isLoading(); - bool bodyLoading = scaled.at(scalingFactor).body.at(0) && scaled.at(scalingFactor).body.at(0)->isLoading(); - bool overlayLoading = scaled.at(scalingFactor).overlay.at(0) && scaled.at(scalingFactor).overlay.at(0)->isLoading(); - bool playerLoading = parameters.player.isValidPlayer() && scaled.at(scalingFactor).playerColored.at(parameters.player.getNum()) && scaled.at(scalingFactor).playerColored.at(parameters.player.getNum())->isLoading(); - - if (shadowLoading || bodyLoading || overlayLoading || playerLoading) - { - getFlippedImage(base)->scaledDraw(where, parameters.palette, dimensions() * scalingFactor, dest, src, parameters.colorMultiplier, parameters.alphaValue, locator.layer); - return; - } - - if (scaled.at(scalingFactor).shadow.at(0)) - flipAndDraw(scaled.at(scalingFactor).shadow, Colors::WHITE_TRUE, parameters.alphaValue); - - if (parameters.player.isValidPlayer()) - { - scaled.at(scalingFactor).playerColored.at(parameters.player.getNum())->draw(where, parameters.palette, dest, src, Colors::WHITE_TRUE, parameters.alphaValue, locator.layer); - } - else - { - if (scaled.at(scalingFactor).body.at(0)) - flipAndDraw(scaled.at(scalingFactor).body, parameters.colorMultiplier, parameters.alphaValue); - } - - if (scaled.at(scalingFactor).overlay.at(0)) - flipAndDraw(scaled.at(scalingFactor).overlay, parameters.ovelayColorMultiplier, static_cast(parameters.alphaValue) * parameters.ovelayColorMultiplier.a / 255); + if (scaled.at(scalingFactor).body.at(0)) + flipAndDraw(scaled.at(scalingFactor).body, parameters.colorMultiplier, parameters.alphaValue); } + + if (scaled.at(scalingFactor).overlay.at(0)) + flipAndDraw(scaled.at(scalingFactor).overlay, parameters.ovelayColorMultiplier, static_cast(parameters.alphaValue) * parameters.ovelayColorMultiplier.a / 255); } const SDL_Palette * ScalableImageShared::getPalette() const { - return base[0]->getPalette(); + return scaled[1].body[0]->getPalette(); } std::shared_ptr ScalableImageShared::createImageReference() @@ -400,7 +393,7 @@ void ScalableImageInstance::verticalFlip() parameters.flipVertical = !parameters.flipVertical; } -std::shared_ptr ScalableImageShared::loadOrGenerateImage(EImageBlitMode mode, int8_t scalingFactor, PlayerColor color) const +std::shared_ptr ScalableImageShared::loadOrGenerateImage(EImageBlitMode mode, int8_t scalingFactor, PlayerColor color, ImageType upscalingSource) const { ImageLocator loadingLocator; @@ -417,68 +410,90 @@ std::shared_ptr ScalableImageShared::loadOrGenerateImage(EIm if (loadedImage) return loadedImage; + if (scalingFactor == 1) + { + // optional images for 1x resolution - only try load them, don't attempt to generate + // this block should never be called for 'body' layer - that image is loaded unconditionally before construction + assert(mode == EImageBlitMode::ONLY_SHADOW || mode == EImageBlitMode::ONLY_OVERLAY || color != PlayerColor::CANNOT_DETERMINE); + return nullptr; + } + // alternatively, find largest pre-scaled image, load it and rescale to desired scaling - Point targetSize = base[0]->dimensions() * scalingFactor; - for (int8_t scaling = 4; scaling > 1; --scaling) + for (int8_t scaling = 4; scaling > 0; --scaling) { loadingLocator.scalingFactor = scaling; auto loadedImage = GH.renderHandler().loadScaledImage(loadingLocator); if (loadedImage) - return loadedImage->scaleTo(targetSize, nullptr); + { + if (scaling == 1) + { + if (mode == EImageBlitMode::ONLY_SHADOW || mode == EImageBlitMode::ONLY_OVERLAY || color != PlayerColor::CANNOT_DETERMINE) + { + ScalableImageParameters parameters(getPalette(), mode); + return loadedImage->scaleInteger(scalingFactor, parameters.palette, mode); + } + } + else + { + Point targetSize = scaled[1].body[0]->dimensions() * scalingFactor; + return loadedImage->scaleTo(targetSize, nullptr); + } + } } - // if all else fails - use base (presumably, indexed) image and convert it to desired form ScalableImageParameters parameters(getPalette(), mode); + // if all else fails - use base (presumably, indexed) image and convert it to desired form if (color != PlayerColor::CANNOT_DETERMINE) parameters.playerColored(color); - return base[0]->scaleInteger(scalingFactor, parameters.palette, mode); + + if (upscalingSource) + return upscalingSource->scaleInteger(scalingFactor, parameters.palette, mode); + else + return scaled[1].body[0]->scaleInteger(scalingFactor, parameters.palette, mode); } void ScalableImageShared::loadScaledImages(int8_t scalingFactor, PlayerColor color) { - if (scalingFactor == 1) - return; // no op. TODO: consider loading 1x images for mods, as alternative to palette-based animations - - if (scaled[scalingFactor].body[0] == nullptr) + if (scaled[scalingFactor].body[0] == nullptr && scalingFactor != 1) { switch(locator.layer) { case EImageBlitMode::OPAQUE: case EImageBlitMode::COLORKEY: case EImageBlitMode::SIMPLE: - scaled[scalingFactor].body[0] = loadOrGenerateImage(locator.layer, scalingFactor, PlayerColor::CANNOT_DETERMINE); + scaled[scalingFactor].body[0] = loadOrGenerateImage(locator.layer, scalingFactor, PlayerColor::CANNOT_DETERMINE, scaled[1].body[0]); break; case EImageBlitMode::WITH_SHADOW_AND_OVERLAY: case EImageBlitMode::ONLY_BODY: - scaled[scalingFactor].body[0] = loadOrGenerateImage(EImageBlitMode::ONLY_BODY, scalingFactor, PlayerColor::CANNOT_DETERMINE); + scaled[scalingFactor].body[0] = loadOrGenerateImage(EImageBlitMode::ONLY_BODY, scalingFactor, PlayerColor::CANNOT_DETERMINE, scaled[1].body[0]); break; case EImageBlitMode::WITH_SHADOW: case EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY: - scaled[scalingFactor].body[0] = loadOrGenerateImage(EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY, scalingFactor, PlayerColor::CANNOT_DETERMINE); + scaled[scalingFactor].body[0] = loadOrGenerateImage(EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY, scalingFactor, PlayerColor::CANNOT_DETERMINE, scaled[1].body[0]); break; } } - if (color.isValidPlayer() && scaled[scalingFactor].playerColored[color.getNum()] == nullptr) + if (color != PlayerColor::CANNOT_DETERMINE && scaled[scalingFactor].playerColored[1+color.getNum()] == nullptr) { switch(locator.layer) { case EImageBlitMode::OPAQUE: case EImageBlitMode::COLORKEY: case EImageBlitMode::SIMPLE: - scaled[scalingFactor].playerColored[color.getNum()] = loadOrGenerateImage(locator.layer, scalingFactor, color); + scaled[scalingFactor].playerColored[1+color.getNum()] = loadOrGenerateImage(locator.layer, scalingFactor, color, scaled[1].playerColored[1+color.getNum()]); break; case EImageBlitMode::WITH_SHADOW_AND_OVERLAY: case EImageBlitMode::ONLY_BODY: - scaled[scalingFactor].playerColored[color.getNum()] = loadOrGenerateImage(EImageBlitMode::ONLY_BODY, scalingFactor, color); + scaled[scalingFactor].playerColored[1+color.getNum()] = loadOrGenerateImage(EImageBlitMode::ONLY_BODY, scalingFactor, color, scaled[1].playerColored[1+color.getNum()]); break; case EImageBlitMode::WITH_SHADOW: case EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY: - scaled[scalingFactor].playerColored[color.getNum()] = loadOrGenerateImage(EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY, scalingFactor, color); + scaled[scalingFactor].playerColored[1+color.getNum()] = loadOrGenerateImage(EImageBlitMode::ONLY_BODY_IGNORE_OVERLAY, scalingFactor, color, scaled[1].playerColored[1+color.getNum()]); break; } } @@ -490,7 +505,7 @@ void ScalableImageShared::loadScaledImages(int8_t scalingFactor, PlayerColor col case EImageBlitMode::WITH_SHADOW: case EImageBlitMode::ONLY_SHADOW: case EImageBlitMode::WITH_SHADOW_AND_OVERLAY: - scaled[scalingFactor].shadow[0] = loadOrGenerateImage(EImageBlitMode::ONLY_SHADOW, scalingFactor, PlayerColor::CANNOT_DETERMINE); + scaled[scalingFactor].shadow[0] = loadOrGenerateImage(EImageBlitMode::ONLY_SHADOW, scalingFactor, PlayerColor::CANNOT_DETERMINE, scaled[1].shadow[0]); break; default: break; @@ -503,7 +518,7 @@ void ScalableImageShared::loadScaledImages(int8_t scalingFactor, PlayerColor col { case EImageBlitMode::ONLY_OVERLAY: case EImageBlitMode::WITH_SHADOW_AND_OVERLAY: - scaled[scalingFactor].overlay[0] = loadOrGenerateImage(EImageBlitMode::ONLY_OVERLAY, scalingFactor, PlayerColor::CANNOT_DETERMINE); + scaled[scalingFactor].overlay[0] = loadOrGenerateImage(EImageBlitMode::ONLY_OVERLAY, scalingFactor, PlayerColor::CANNOT_DETERMINE, scaled[1].overlay[0]); break; default: break; diff --git a/client/renderSDL/ScalableImage.h b/client/renderSDL/ScalableImage.h index 31ce0f303..d662f9a01 100644 --- a/client/renderSDL/ScalableImage.h +++ b/client/renderSDL/ScalableImage.h @@ -46,11 +46,12 @@ struct ScalableImageParameters : boost::noncopyable class ScalableImageShared final : public std::enable_shared_from_this, boost::noncopyable { - static constexpr int scalingSize = 5; // 0-4 range. TODO: switch to either 2-4 or 1-4 + static constexpr int scalingSize = 5; // 0-4 range. TODO: switch to 1-4 since there is no '0' scaling static constexpr int maxFlips = 4; - using FlippedImages = std::array, maxFlips>; - using PlayerColoredImages = std::array, PlayerColor::PLAYER_LIMIT_I>; + using ImageType = std::shared_ptr; + using FlippedImages = std::array; + using PlayerColoredImages = std::array; // all valid colors+neutral struct ScaledImage { @@ -67,16 +68,13 @@ class ScalableImageShared final : public std::enable_shared_from_this scaled; /// Locator of this image, for loading additional (e.g. upscaled) images const SharedImageLocator locator; - std::shared_ptr loadOrGenerateImage(EImageBlitMode mode, int8_t scalingFactor, PlayerColor color) const; + std::shared_ptr loadOrGenerateImage(EImageBlitMode mode, int8_t scalingFactor, PlayerColor color, ImageType upscalingSource) const; void loadScaledImages(int8_t scalingFactor, PlayerColor color); diff --git a/docs/modders/HD_Graphics.md b/docs/modders/HD_Graphics.md index c5a8d3d2a..0b3f697d9 100644 --- a/docs/modders/HD_Graphics.md +++ b/docs/modders/HD_Graphics.md @@ -20,7 +20,7 @@ For upscaled images you have to use following folders (next to `sprites`, `data` The sprites should have the same name and folder structure as in `sprites`, `data` and `video` folder. All images that are missing in the upscaled folders are scaled with the selected upscaling filter instead of using prescaled images. -### Shadows / Overlays +### Shadows / Overlays / Player-colored images It's also possible (but not necessary) to add high-definition shadows: Just place a image next to the normal upscaled image with the suffix `-shadow`. E.g. `TestImage.png` and `TestImage-shadow.png`. In future, such shadows will likely become required to correctly exclude shadow from effects such as Clone spell. @@ -36,3 +36,11 @@ Currently needed for: - Flaggable adventure map objects. Overlay must contain a transparent image with white flags on it and will be used to colorize flags to owning player - Creature battle animations, idle and mouse hover group. Overlay must contain a transparent image with white outline of creature for highlighting on mouse hover + +For images that are used for player-colored interface, it is possible to provide custom images for each player. For example `HeroScr4-red.png` will be used for hero window of red player. + +- Currently needed for all UI elements that are player-colored in HoMM3. +- Can NOT be used for player-owned adventure objects. Use `-overlay` images for such objects. +- Possible suffixes are `red`, `blue`, `tan`, `green`, `orange`, `purple`, `teal`, `pink`, `neutral` (used only for turn order queue in combat) + +It is possible to use such additional images for both upscaled (xbrz) graphics, as well as for original / 1x images. When using this feature for original / 1x image, make sure that your base image (without suffix) is rgb/rgba image, and not indexed / with palette