1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Merge pull request #4874 from Laserlicht/load_prescaled

prescaled image support
This commit is contained in:
Ivan Savenko 2024-11-16 17:28:37 +02:00 committed by GitHub
commit 98a54b61b7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 173 additions and 28 deletions

View File

@ -71,6 +71,7 @@ ImageLocator ImageLocator::copyFile() const
{
ImageLocator result;
result.scalingFactor = 1;
result.preScaledFactor = preScaledFactor;
result.image = image;
result.defFile = defFile;
result.defFrame = defFrame;

View File

@ -33,6 +33,7 @@ struct ImageLocator
bool verticalFlip = false;
bool horizontalFlip = false;
int8_t scalingFactor = 0; // 0 = auto / use default scaling
int8_t preScaledFactor = 1;
EImageLayer layer = EImageLayer::ALL;
ImageLocator() = default;

View File

@ -55,6 +55,59 @@ std::shared_ptr<CDefFile> RenderHandler::getAnimationFile(const AnimationPath &
return result;
}
std::optional<ResourcePath> RenderHandler::getPathForScaleFactor(ResourcePath path, std::string factor)
{
if(path.getType() == EResType::IMAGE)
{
auto p = ImagePath::builtin(path.getName());
if(CResourceHandler::get()->existsResource(p.addPrefix("SPRITES" + factor + "X/")))
return std::optional<ResourcePath>(p.addPrefix("SPRITES" + factor + "X/"));
if(CResourceHandler::get()->existsResource(p.addPrefix("DATA" + factor + "X/")))
return std::optional<ResourcePath>(p.addPrefix("DATA" + factor + "X/"));
}
else
{
auto p = AnimationPath::builtin(path.getName());
auto pJson = p.toType<EResType::JSON>();
if(CResourceHandler::get()->existsResource(p.addPrefix("SPRITES" + factor + "X/")))
return std::optional<ResourcePath>(p.addPrefix("SPRITES" + factor + "X/"));
if(CResourceHandler::get()->existsResource(pJson))
return std::optional<ResourcePath>(p);
if(CResourceHandler::get()->existsResource(pJson.addPrefix("SPRITES" + factor + "X/")))
return std::optional<ResourcePath>(p.addPrefix("SPRITES" + factor + "X/"));
}
return std::nullopt;
}
std::pair<ResourcePath, int> RenderHandler::getScalePath(ResourcePath p)
{
auto path = p;
int scaleFactor = 1;
if(getScalingFactor() > 1)
{
std::vector<int> factorsToCheck = {getScalingFactor(), 4, 3, 2};
for(auto factorToCheck : factorsToCheck)
{
std::string name = boost::algorithm::to_upper_copy(p.getName());
boost::replace_all(name, "SPRITES/", std::string("SPRITES") + std::to_string(factorToCheck) + std::string("X/"));
boost::replace_all(name, "DATA/", std::string("DATA") + std::to_string(factorToCheck) + std::string("X/"));
ResourcePath scaledPath = ImagePath::builtin(name);
if(p.getType() != EResType::IMAGE)
scaledPath = AnimationPath::builtin(name);
auto tmpPath = getPathForScaleFactor(scaledPath, std::to_string(factorToCheck));
if(tmpPath)
{
path = *tmpPath;
scaleFactor = factorToCheck;
break;
}
}
}
return std::pair<ResourcePath, int>(path, scaleFactor);
};
void RenderHandler::initFromJson(AnimationLayoutMap & source, const JsonNode & config)
{
std::string basepath;
@ -96,7 +149,9 @@ void RenderHandler::initFromJson(AnimationLayoutMap & source, const JsonNode & c
RenderHandler::AnimationLayoutMap & RenderHandler::getAnimationLayout(const AnimationPath & path)
{
AnimationPath actualPath = boost::starts_with(path.getName(), "SPRITES") ? path : path.addPrefix("SPRITES/");
auto tmp = getScalePath(path);
auto animPath = AnimationPath::builtin(tmp.first.getName());
AnimationPath actualPath = boost::starts_with(animPath.getName(), "SPRITES") ? animPath : animPath.addPrefix("SPRITES/");
auto it = animationLayouts.find(actualPath);
@ -123,11 +178,15 @@ RenderHandler::AnimationLayoutMap & RenderHandler::getAnimationLayout(const Anim
std::unique_ptr<ui8[]> textData(new ui8[stream->getSize()]);
stream->read(textData.get(), stream->getSize());
const JsonNode config(reinterpret_cast<const std::byte*>(textData.get()), stream->getSize(), path.getOriginalName());
const JsonNode config(reinterpret_cast<const std::byte*>(textData.get()), stream->getSize(), animPath.getOriginalName());
initFromJson(result, config);
}
for(auto & g : result)
for(auto & i : g.second)
i.preScaledFactor = tmp.second;
animationLayouts[actualPath] = result;
return animationLayouts[actualPath];
}
@ -177,13 +236,23 @@ std::shared_ptr<ISharedImage> RenderHandler::loadImageFromFileUncached(const Ima
if (locator.image)
{
// TODO: create EmptySharedImage class that will be instantiated if image does not exists or fails to load
return std::make_shared<SDLImageShared>(*locator.image);
return std::make_shared<SDLImageShared>(*locator.image, locator.preScaledFactor);
}
if (locator.defFile)
{
auto defFile = getAnimationFile(*locator.defFile);
return std::make_shared<SDLImageShared>(defFile.get(), locator.defFrame, locator.defGroup);
int preScaledFactor = locator.preScaledFactor;
if(!defFile) // no prescale for this frame
{
auto tmpPath = (*locator.defFile).getName();
boost::algorithm::replace_all(tmpPath, "SPRITES2X/", "SPRITES/");
boost::algorithm::replace_all(tmpPath, "SPRITES3X/", "SPRITES/");
boost::algorithm::replace_all(tmpPath, "SPRITES4X/", "SPRITES/");
preScaledFactor = 1;
defFile = getAnimationFile(AnimationPath::builtin(tmpPath));
}
return std::make_shared<SDLImageShared>(defFile.get(), locator.defFrame, locator.defGroup, preScaledFactor);
}
throw std::runtime_error("Invalid image locator received!");
@ -257,10 +326,24 @@ std::shared_ptr<ISharedImage> RenderHandler::scaleImage(const ImageLocator & loc
std::shared_ptr<IImage> RenderHandler::loadImage(const ImageLocator & locator, EImageBlitMode mode)
{
if (locator.scalingFactor == 0 && getScalingFactor() != 1 )
ImageLocator adjustedLocator = locator;
if(adjustedLocator.defFile && adjustedLocator.scalingFactor == 0)
{
auto unscaledLocator = locator;
auto scaledLocator = locator;
auto tmp = getScalePath(*adjustedLocator.defFile);
adjustedLocator.defFile = AnimationPath::builtin(tmp.first.getName());
adjustedLocator.preScaledFactor = tmp.second;
}
if(adjustedLocator.image && adjustedLocator.scalingFactor == 0)
{
auto tmp = getScalePath(*adjustedLocator.image);
adjustedLocator.image = ImagePath::builtin(tmp.first.getName());
adjustedLocator.preScaledFactor = tmp.second;
}
if (adjustedLocator.scalingFactor == 0 && getScalingFactor() != 1 )
{
auto unscaledLocator = adjustedLocator;
auto scaledLocator = adjustedLocator;
unscaledLocator.scalingFactor = 1;
scaledLocator.scalingFactor = getScalingFactor();
@ -269,22 +352,36 @@ std::shared_ptr<IImage> RenderHandler::loadImage(const ImageLocator & locator, E
return std::make_shared<ImageScaled>(scaledLocator, unscaledImage, mode);
}
if (locator.scalingFactor == 0)
if (adjustedLocator.scalingFactor == 0)
{
auto scaledLocator = locator;
auto scaledLocator = adjustedLocator;
scaledLocator.scalingFactor = getScalingFactor();
return loadImageImpl(scaledLocator)->createImageReference(mode);
}
else
{
return loadImageImpl(locator)->createImageReference(mode);
if(adjustedLocator.image)
{
std::string imgPath = (*adjustedLocator.image).getName();
if(adjustedLocator.layer == EImageLayer::OVERLAY)
imgPath += "-overlay";
if(adjustedLocator.layer == EImageLayer::SHADOW)
imgPath += "-shadow";
if(CResourceHandler::get()->existsResource(ImagePath::builtin(imgPath)))
adjustedLocator.image = ImagePath::builtin(imgPath);
}
return loadImageImpl(adjustedLocator)->createImageReference(mode);
}
}
std::shared_ptr<IImage> RenderHandler::loadImage(const AnimationPath & path, int frame, int group, EImageBlitMode mode)
{
ImageLocator locator = getLocatorForAnimationFrame(path, frame, group);
auto tmp = getScalePath(path);
ImageLocator locator = getLocatorForAnimationFrame(AnimationPath::builtin(tmp.first.getName()), frame, group);
locator.preScaledFactor = tmp.second;
return loadImage(locator, mode);
}

View File

@ -29,6 +29,8 @@ class RenderHandler : public IRenderHandler
std::map<EFonts, std::shared_ptr<const IFont>> fonts;
std::shared_ptr<CDefFile> getAnimationFile(const AnimationPath & path);
std::optional<ResourcePath> getPathForScaleFactor(ResourcePath path, std::string factor);
std::pair<ResourcePath, int> getScalePath(ResourcePath p);
AnimationLayoutMap & getAnimationLayout(const AnimationPath & path);
void initFromJson(AnimationLayoutMap & layout, const JsonNode & config);

View File

@ -89,11 +89,12 @@ int IImage::height() const
return dimensions().y;
}
SDLImageShared::SDLImageShared(const CDefFile * data, size_t frame, size_t group)
SDLImageShared::SDLImageShared(const CDefFile * data, size_t frame, size_t group, int preScaleFactor)
: surf(nullptr),
margins(0, 0),
fullSize(0, 0),
originalPalette(nullptr)
originalPalette(nullptr),
preScaleFactor(preScaleFactor)
{
SDLImageLoader loader(this);
data->loadFrame(frame, group, loader);
@ -101,11 +102,12 @@ SDLImageShared::SDLImageShared(const CDefFile * data, size_t frame, size_t group
savePalette();
}
SDLImageShared::SDLImageShared(SDL_Surface * from)
SDLImageShared::SDLImageShared(SDL_Surface * from, int preScaleFactor)
: surf(nullptr),
margins(0, 0),
fullSize(0, 0),
originalPalette(nullptr)
originalPalette(nullptr),
preScaleFactor(preScaleFactor)
{
surf = from;
if (surf == nullptr)
@ -118,11 +120,12 @@ SDLImageShared::SDLImageShared(SDL_Surface * from)
fullSize.y = surf->h;
}
SDLImageShared::SDLImageShared(const ImagePath & filename)
SDLImageShared::SDLImageShared(const ImagePath & filename, int preScaleFactor)
: surf(nullptr),
margins(0, 0),
fullSize(0, 0),
originalPalette(nullptr)
originalPalette(nullptr),
preScaleFactor(preScaleFactor)
{
surf = BitmapHandler::loadBitmap(filename);
@ -274,9 +277,18 @@ std::shared_ptr<ISharedImage> SDLImageShared::scaleInteger(int factor, SDL_Palet
if (palette && surf && surf->format->palette)
SDL_SetSurfacePalette(surf, palette);
SDL_Surface * scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, factor, EScalingAlgorithm::XBRZ);
SDL_Surface * scaled = nullptr;
if(preScaleFactor == factor)
{
scaled = CSDL_Ext::newSurface(Point(surf->w, surf->h), surf);
SDL_BlitSurface(surf, nullptr, scaled, nullptr);
}
else if(preScaleFactor == 1)
scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, factor, EScalingAlgorithm::XBRZ);
else
scaled = CSDL_Ext::scaleSurface(surf, (surf->w / preScaleFactor) * factor, (surf->h / preScaleFactor) * factor);
auto ret = std::make_shared<SDLImageShared>(scaled);
auto ret = std::make_shared<SDLImageShared>(scaled, preScaleFactor);
ret->fullSize.x = fullSize.x * factor;
ret->fullSize.y = fullSize.y * factor;
@ -296,8 +308,8 @@ std::shared_ptr<ISharedImage> SDLImageShared::scaleInteger(int factor, SDL_Palet
std::shared_ptr<ISharedImage> SDLImageShared::scaleTo(const Point & size, SDL_Palette * palette) const
{
float scaleX = float(size.x) / dimensions().x;
float scaleY = float(size.y) / dimensions().y;
float scaleX = float(size.x) / fullSize.x;
float scaleY = float(size.y) / fullSize.y;
if (palette && surf->format->palette)
SDL_SetSurfacePalette(surf, palette);
@ -311,7 +323,7 @@ std::shared_ptr<ISharedImage> SDLImageShared::scaleTo(const Point & size, SDL_Pa
else
CSDL_Ext::setDefaultColorKey(scaled);//just in case
auto ret = std::make_shared<SDLImageShared>(scaled);
auto ret = std::make_shared<SDLImageShared>(scaled, preScaleFactor);
ret->fullSize.x = (int) round((float)fullSize.x * scaleX);
ret->fullSize.y = (int) round((float)fullSize.y * scaleY);
@ -355,7 +367,7 @@ bool SDLImageShared::isTransparent(const Point & coords) const
Point SDLImageShared::dimensions() const
{
return fullSize;
return fullSize / preScaleFactor;
}
std::shared_ptr<IImage> SDLImageShared::createImageReference(EImageBlitMode mode)
@ -369,7 +381,7 @@ std::shared_ptr<IImage> SDLImageShared::createImageReference(EImageBlitMode mode
std::shared_ptr<ISharedImage> SDLImageShared::horizontalFlip() const
{
SDL_Surface * flipped = CSDL_Ext::horizontalFlip(surf);
auto ret = std::make_shared<SDLImageShared>(flipped);
auto ret = std::make_shared<SDLImageShared>(flipped, preScaleFactor);
ret->fullSize = fullSize;
ret->margins.x = margins.x;
ret->margins.y = fullSize.y - surf->h - margins.y;
@ -381,7 +393,7 @@ std::shared_ptr<ISharedImage> SDLImageShared::horizontalFlip() const
std::shared_ptr<ISharedImage> SDLImageShared::verticalFlip() const
{
SDL_Surface * flipped = CSDL_Ext::verticalFlip(surf);
auto ret = std::make_shared<SDLImageShared>(flipped);
auto ret = std::make_shared<SDLImageShared>(flipped, preScaleFactor);
ret->fullSize = fullSize;
ret->margins.x = fullSize.x - surf->w - margins.x;
ret->margins.y = margins.y;

View File

@ -35,6 +35,9 @@ class SDLImageShared final : public ISharedImage, public std::enable_shared_from
//total size including borders
Point fullSize;
//pre scaled image
int preScaleFactor;
// Keep the original palette, in order to do color switching operation
void savePalette();
@ -42,11 +45,11 @@ class SDLImageShared final : public ISharedImage, public std::enable_shared_from
public:
//Load image from def file
SDLImageShared(const CDefFile *data, size_t frame, size_t group=0);
SDLImageShared(const CDefFile *data, size_t frame, size_t group=0, int preScaleFactor=1);
//Load from bitmap file
SDLImageShared(const ImagePath & filename);
SDLImageShared(const ImagePath & filename, int preScaleFactor=1);
//Create using existing surface, extraRef will increase refcount on SDL_Surface
SDLImageShared(SDL_Surface * from);
SDLImageShared(SDL_Surface * from, int preScaleFactor=1);
~SDLImageShared();
void draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, uint8_t alpha, EImageBlitMode mode) const override;

View File

@ -0,0 +1,29 @@
# HD Graphics
It's possible to provide alternative HD-Graphics within mods. They will be used if any upscaling filter is activated.
## Preconditions
It's still necessary to add 1x graphics as before. HD graphics are seperate from usual graphics. This allows to partitially use HD for a few graphics in mod. And avoid handling huge graphics if upscaling isn't enabled.
Currently following scaling factors are possible to use: 2x, 3x, 4x. You can also provide multiple of them (increases size of mod, but improves loading performance for player). It's recommend to provide 2x and 3x images.
If user for example selects 3x resolution and only 2x exists in mod then the 2x images are upscaled to 3x (same for other combinations > 1x).
## Mod
For upscaled images you have to use following folders (next to `sprites` and `data` folders):
- `sprites2x`, `sprites3x`, `sprites4x` for sprites
- `data2x`, `data3x`, `data4x` for images
The sprites should have the same name and folder structure as in `sprites` and `data` folder. All images that are missing in the upscaled folders are scaled with the selected upscaling filter instead of using prescaled images.
### Shadows / Overlays
It's also possible (but not necessary) to add HD shadows: Just place a image next to the normal upscaled image with the suffix `-shadow`. E.g. `TestImage.png` and `TestImage-shadow.png`.
Same for overlays with `-overlay`. But overlays are **necessary** for some animation graphics. They will be colorized by VCMI.
Currently needed for:
- flaggable adventure map objects (needs a transparent image with white flags on it)
- creature battle animations (needs a transparent image with white outline of creature for highlighting on mouse hover)