mirror of
https://github.com/vcmi/vcmi.git
synced 2025-08-10 22:31:40 +02:00
Support for shadow, overlay and player-colored premade images for 1x
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
#include "../../lib/CThreadHelper.h"
|
||||
#include "../../lib/filesystem/Filesystem.h"
|
||||
#include "../../lib/VCMIDirs.h"
|
||||
#include "../../lib/constants/StringConstants.h"
|
||||
|
||||
#include <vcmi/ArtifactService.h>
|
||||
#include <vcmi/CreatureService.h>
|
||||
@@ -218,8 +219,6 @@ void RenderHandler::storeCachedImage(const ImageLocator & locator, std::shared_p
|
||||
|
||||
std::shared_ptr<SDLImageShared> RenderHandler::loadScaledImage(const ImageLocator & locator)
|
||||
{
|
||||
assert(locator.scalingFactor > 1);
|
||||
|
||||
static constexpr std::array scaledDataPath = {
|
||||
"", // 0x
|
||||
"DATA/",
|
||||
@@ -260,6 +259,10 @@ std::shared_ptr<SDLImageShared> 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));
|
||||
|
@@ -202,30 +202,30 @@ void ScalableImageParameters::adjustPalette(const SDL_Palette * originalPalette,
|
||||
ScalableImageShared::ScalableImageShared(const SharedImageLocator & locator, const std::shared_ptr<const ISharedImage> & 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,29 +256,23 @@ 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)
|
||||
{
|
||||
flipAndDraw(base, parameters.colorMultiplier, parameters.alphaValue);
|
||||
}
|
||||
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();
|
||||
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)
|
||||
{
|
||||
getFlippedImage(base)->scaledDraw(where, parameters.palette, dimensions() * scalingFactor, dest, src, parameters.colorMultiplier, parameters.alphaValue, locator.layer);
|
||||
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.isValidPlayer())
|
||||
if (parameters.player != PlayerColor::CANNOT_DETERMINE && scaled.at(scalingFactor).playerColored.at(1+parameters.player.getNum()))
|
||||
{
|
||||
scaled.at(scalingFactor).playerColored.at(parameters.player.getNum())->draw(where, parameters.palette, dest, src, Colors::WHITE_TRUE, parameters.alphaValue, locator.layer);
|
||||
scaled.at(scalingFactor).playerColored.at(1+parameters.player.getNum())->draw(where, parameters.palette, dest, src, Colors::WHITE_TRUE, parameters.alphaValue, locator.layer);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -289,11 +283,10 @@ void ScalableImageShared::draw(SDL_Surface * where, const Point & dest, const Re
|
||||
if (scaled.at(scalingFactor).overlay.at(0))
|
||||
flipAndDraw(scaled.at(scalingFactor).overlay, parameters.ovelayColorMultiplier, static_cast<int>(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<ScalableImageInstance> ScalableImageShared::createImageReference()
|
||||
@@ -400,7 +393,7 @@ void ScalableImageInstance::verticalFlip()
|
||||
parameters.flipVertical = !parameters.flipVertical;
|
||||
}
|
||||
|
||||
std::shared_ptr<const ISharedImage> ScalableImageShared::loadOrGenerateImage(EImageBlitMode mode, int8_t scalingFactor, PlayerColor color) const
|
||||
std::shared_ptr<const ISharedImage> ScalableImageShared::loadOrGenerateImage(EImageBlitMode mode, int8_t scalingFactor, PlayerColor color, ImageType upscalingSource) const
|
||||
{
|
||||
ImageLocator loadingLocator;
|
||||
|
||||
@@ -417,68 +410,90 @@ std::shared_ptr<const ISharedImage> 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)
|
||||
{
|
||||
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;
|
||||
|
@@ -46,11 +46,12 @@ struct ScalableImageParameters : boost::noncopyable
|
||||
|
||||
class ScalableImageShared final : public std::enable_shared_from_this<ScalableImageShared>, 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<std::shared_ptr<const ISharedImage>, maxFlips>;
|
||||
using PlayerColoredImages = std::array<std::shared_ptr<const ISharedImage>, PlayerColor::PLAYER_LIMIT_I>;
|
||||
using ImageType = std::shared_ptr<const ISharedImage>;
|
||||
using FlippedImages = std::array<ImageType, maxFlips>;
|
||||
using PlayerColoredImages = std::array<ImageType, PlayerColor::PLAYER_LIMIT_I + 1>; // all valid colors+neutral
|
||||
|
||||
struct ScaledImage
|
||||
{
|
||||
@@ -67,16 +68,13 @@ class ScalableImageShared final : public std::enable_shared_from_this<ScalableIm
|
||||
PlayerColoredImages playerColored;
|
||||
};
|
||||
|
||||
/// 1x image, usually indexed. Array of 4 images for every potential flip
|
||||
FlippedImages base;
|
||||
|
||||
/// 1x-4x images. 1x are currently unused, but can be used for providing non-palette version of these images
|
||||
/// 1x-4x images. body for 1x scaling is guaranteed to be loaded
|
||||
std::array<ScaledImage, scalingSize> scaled;
|
||||
|
||||
/// Locator of this image, for loading additional (e.g. upscaled) images
|
||||
const SharedImageLocator locator;
|
||||
|
||||
std::shared_ptr<const ISharedImage> loadOrGenerateImage(EImageBlitMode mode, int8_t scalingFactor, PlayerColor color) const;
|
||||
std::shared_ptr<const ISharedImage> loadOrGenerateImage(EImageBlitMode mode, int8_t scalingFactor, PlayerColor color, ImageType upscalingSource) const;
|
||||
|
||||
void loadScaledImages(int8_t scalingFactor, PlayerColor color);
|
||||
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user