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:
commit
98a54b61b7
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
29
docs/modders/HD_Graphics.md
Normal file
29
docs/modders/HD_Graphics.md
Normal 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)
|
Loading…
Reference in New Issue
Block a user