mirror of
https://github.com/vcmi/vcmi.git
synced 2025-03-21 21:17:49 +02:00
All assets generation (large spellbook, terrain animations, etc) are now done in memory and used as it, without saving to disk. This should slightly improve load times since there is no encode png / decode png, and should help with avoiding strange bug when vcmi fails to load recently saved assets. If needed, such assets can be force-dumped on disk using already existing console command
406 lines
10 KiB
C++
406 lines
10 KiB
C++
/*
|
|
* SDLImage.cpp, part of VCMI engine
|
|
*
|
|
* Authors: listed in file AUTHORS in main folder
|
|
*
|
|
* License: GNU General Public License v2.0 or later
|
|
* Full text of license available in license.txt file, in main folder
|
|
*
|
|
*/
|
|
#include "StdInc.h"
|
|
#include "SDLImage.h"
|
|
|
|
#include "SDLImageLoader.h"
|
|
#include "SDLImageScaler.h"
|
|
#include "SDL_Extensions.h"
|
|
|
|
#include "../render/ColorFilter.h"
|
|
#include "../render/CBitmapHandler.h"
|
|
#include "../render/CDefFile.h"
|
|
#include "../gui/CGuiHandler.h"
|
|
#include "../render/IScreenHandler.h"
|
|
|
|
#include <tbb/parallel_for.h>
|
|
#include <tbb/task_arena.h>
|
|
|
|
#include <SDL_image.h>
|
|
#include <SDL_surface.h>
|
|
#include <SDL_version.h>
|
|
|
|
class SDLImageLoader;
|
|
|
|
int IImage::width() const
|
|
{
|
|
return dimensions().x;
|
|
}
|
|
|
|
int IImage::height() const
|
|
{
|
|
return dimensions().y;
|
|
}
|
|
|
|
SDLImageShared::SDLImageShared(const CDefFile * data, size_t frame, size_t group)
|
|
: surf(nullptr),
|
|
margins(0, 0),
|
|
fullSize(0, 0),
|
|
originalPalette(nullptr)
|
|
{
|
|
SDLImageLoader loader(this);
|
|
data->loadFrame(frame, group, loader);
|
|
|
|
savePalette();
|
|
}
|
|
|
|
SDLImageShared::SDLImageShared(SDL_Surface * from)
|
|
: surf(nullptr),
|
|
margins(0, 0),
|
|
fullSize(0, 0),
|
|
originalPalette(nullptr)
|
|
{
|
|
surf = from;
|
|
if (surf == nullptr)
|
|
return;
|
|
|
|
savePalette();
|
|
|
|
surf->refcount++;
|
|
fullSize.x = surf->w;
|
|
fullSize.y = surf->h;
|
|
}
|
|
|
|
SDLImageShared::SDLImageShared(const ImagePath & filename)
|
|
: surf(nullptr),
|
|
margins(0, 0),
|
|
fullSize(0, 0),
|
|
originalPalette(nullptr)
|
|
{
|
|
surf = BitmapHandler::loadBitmap(filename);
|
|
|
|
if(surf == nullptr)
|
|
{
|
|
logGlobal->error("Error: failed to load image %s", filename.getOriginalName());
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
savePalette();
|
|
fullSize.x = surf->w;
|
|
fullSize.y = surf->h;
|
|
|
|
optimizeSurface();
|
|
}
|
|
}
|
|
|
|
void SDLImageShared::scaledDraw(SDL_Surface * where, SDL_Palette * palette, const Point & scaleTo, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, uint8_t alpha, EImageBlitMode mode) const
|
|
{
|
|
assert(upscalingInProgress == false);
|
|
if (!surf)
|
|
return;
|
|
|
|
Rect sourceRect(0, 0, surf->w, surf->h);
|
|
Point destShift(0, 0);
|
|
Point destScale = Point(surf->w, surf->h) * scaleTo / dimensions();
|
|
Point marginsScaled = margins * scaleTo / dimensions();
|
|
|
|
if(src)
|
|
{
|
|
Rect srcUnscaled(Point(src->topLeft() * dimensions() / scaleTo), Point(src->dimensions() * dimensions() / scaleTo));
|
|
|
|
if(srcUnscaled.x < margins.x)
|
|
destShift.x += marginsScaled.x - src->x;
|
|
|
|
if(srcUnscaled.y < margins.y)
|
|
destShift.y += marginsScaled.y - src->y;
|
|
|
|
sourceRect = Rect(srcUnscaled).intersect(Rect(margins.x, margins.y, surf->w, surf->h));
|
|
|
|
destScale.x = std::min(destScale.x, sourceRect.w * scaleTo.x / dimensions().x);
|
|
destScale.y = std::min(destScale.y, sourceRect.h * scaleTo.y / dimensions().y);
|
|
|
|
sourceRect -= margins;
|
|
}
|
|
else
|
|
destShift = marginsScaled;
|
|
|
|
destShift += dest;
|
|
|
|
SDL_SetSurfaceColorMod(surf, colorMultiplier.r, colorMultiplier.g, colorMultiplier.b);
|
|
SDL_SetSurfaceAlphaMod(surf, alpha);
|
|
|
|
if (alpha != SDL_ALPHA_OPAQUE || (mode != EImageBlitMode::OPAQUE && surf->format->Amask != 0))
|
|
SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_BLEND);
|
|
else
|
|
SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_NONE);
|
|
|
|
if (palette && surf->format->palette)
|
|
SDL_SetSurfacePalette(surf, palette);
|
|
|
|
SDL_Rect srcRect = CSDL_Ext::toSDL(sourceRect);
|
|
SDL_Rect dstRect = CSDL_Ext::toSDL(Rect(destShift, destScale));
|
|
|
|
if (sourceRect.dimensions() * scaleTo / dimensions() != destScale)
|
|
logGlobal->info("???");
|
|
|
|
SDL_Surface * tempSurface = SDL_ConvertSurface(surf, where->format, 0);
|
|
int result = SDL_BlitScaled(tempSurface, &srcRect, where, &dstRect);
|
|
|
|
SDL_FreeSurface(tempSurface);
|
|
if (result != 0)
|
|
logGlobal->error("SDL_BlitScaled failed! %s", SDL_GetError());
|
|
|
|
if (surf->format->palette)
|
|
SDL_SetSurfacePalette(surf, originalPalette);
|
|
}
|
|
|
|
void SDLImageShared::draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, const ColorRGBA & colorMultiplier, uint8_t alpha, EImageBlitMode mode) const
|
|
{
|
|
assert(upscalingInProgress == false);
|
|
if (!surf)
|
|
return;
|
|
|
|
Rect sourceRect(0, 0, surf->w, surf->h);
|
|
|
|
Point destShift(0, 0);
|
|
|
|
if(src)
|
|
{
|
|
if(src->x < margins.x)
|
|
destShift.x += margins.x - src->x;
|
|
|
|
if(src->y < margins.y)
|
|
destShift.y += margins.y - src->y;
|
|
|
|
sourceRect = Rect(*src).intersect(Rect(margins.x, margins.y, surf->w, surf->h));
|
|
|
|
sourceRect -= margins;
|
|
}
|
|
else
|
|
destShift = margins;
|
|
|
|
destShift += dest;
|
|
|
|
SDL_SetSurfaceColorMod(surf, colorMultiplier.r, colorMultiplier.g, colorMultiplier.b);
|
|
SDL_SetSurfaceAlphaMod(surf, alpha);
|
|
|
|
if (alpha != SDL_ALPHA_OPAQUE || (mode != EImageBlitMode::OPAQUE && surf->format->Amask != 0))
|
|
SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_BLEND);
|
|
else
|
|
SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_NONE);
|
|
|
|
if (palette && surf->format->palette)
|
|
SDL_SetSurfacePalette(surf, palette);
|
|
|
|
if(surf->format->palette && mode != EImageBlitMode::OPAQUE && mode != EImageBlitMode::COLORKEY)
|
|
{
|
|
CSDL_Ext::blit8bppAlphaTo24bpp(surf, sourceRect, where, destShift, alpha);
|
|
}
|
|
else
|
|
{
|
|
CSDL_Ext::blitSurface(surf, sourceRect, where, destShift);
|
|
}
|
|
|
|
if (surf->format->palette)
|
|
SDL_SetSurfacePalette(surf, originalPalette);
|
|
}
|
|
|
|
void SDLImageShared::optimizeSurface()
|
|
{
|
|
assert(upscalingInProgress == false);
|
|
if (!surf)
|
|
return;
|
|
|
|
SDLImageOptimizer optimizer(surf, Rect(margins, fullSize));
|
|
|
|
optimizer.optimizeSurface(surf);
|
|
SDL_FreeSurface(surf);
|
|
|
|
surf = optimizer.acquireResultSurface();
|
|
margins = optimizer.getResultDimensions().topLeft();
|
|
fullSize = optimizer.getResultDimensions().dimensions();
|
|
}
|
|
|
|
std::shared_ptr<const ISharedImage> SDLImageShared::scaleInteger(int factor, SDL_Palette * palette, EImageBlitMode mode) const
|
|
{
|
|
assert(upscalingInProgress == false);
|
|
if (factor <= 0)
|
|
throw std::runtime_error("Unable to scale by integer value of " + std::to_string(factor));
|
|
|
|
if (!surf)
|
|
return shared_from_this();
|
|
|
|
if (palette && surf->format->palette)
|
|
SDL_SetSurfacePalette(surf, palette);
|
|
|
|
// simple heuristics to differentiate tileable UI elements from map object / combat assets
|
|
EScalingAlgorithm algorithm;
|
|
if (mode == EImageBlitMode::OPAQUE || mode == EImageBlitMode::COLORKEY || mode == EImageBlitMode::SIMPLE)
|
|
algorithm = EScalingAlgorithm::XBRZ_OPAQUE;
|
|
else
|
|
algorithm = EScalingAlgorithm::XBRZ_ALPHA;
|
|
|
|
auto result = std::make_shared<SDLImageShared>(this, factor, algorithm);
|
|
|
|
if (surf->format->palette)
|
|
SDL_SetSurfacePalette(surf, originalPalette);
|
|
|
|
return result;
|
|
}
|
|
|
|
SDLImageShared::SDLImageShared(const SDLImageShared * from, int integerScaleFactor, EScalingAlgorithm algorithm)
|
|
{
|
|
static tbb::task_arena upscalingArena;
|
|
|
|
upscalingInProgress = true;
|
|
|
|
auto scaler = std::make_shared<SDLImageScaler>(from->surf, Rect(from->margins, from->fullSize), true);
|
|
|
|
const auto & scalingTask = [this, algorithm, scaler]()
|
|
{
|
|
scaler->scaleSurfaceIntegerFactor(GH.screenHandler().getScalingFactor(), algorithm);
|
|
surf = scaler->acquireResultSurface();
|
|
fullSize = scaler->getResultDimensions().dimensions();
|
|
margins = scaler->getResultDimensions().topLeft();
|
|
|
|
upscalingInProgress = false;
|
|
};
|
|
|
|
upscalingArena.enqueue(scalingTask);
|
|
}
|
|
|
|
bool SDLImageShared::isLoading() const
|
|
{
|
|
return upscalingInProgress;
|
|
}
|
|
|
|
std::shared_ptr<const ISharedImage> SDLImageShared::scaleTo(const Point & size, SDL_Palette * palette) const
|
|
{
|
|
assert(upscalingInProgress == false);
|
|
if (palette && surf->format->palette)
|
|
SDL_SetSurfacePalette(surf, palette);
|
|
|
|
SDLImageScaler scaler(surf, Rect(margins, fullSize), true);
|
|
|
|
scaler.scaleSurface(size, EScalingAlgorithm::XBRZ_ALPHA);
|
|
|
|
auto scaled = scaler.acquireResultSurface();
|
|
|
|
if (scaled->format && scaled->format->palette) // fix color keying, because SDL loses it at this point
|
|
CSDL_Ext::setColorKey(scaled, scaled->format->palette->colors[0]);
|
|
else if(scaled->format && scaled->format->Amask)
|
|
SDL_SetSurfaceBlendMode(scaled, SDL_BLENDMODE_BLEND);//just in case
|
|
else
|
|
CSDL_Ext::setDefaultColorKey(scaled);//just in case
|
|
|
|
auto ret = std::make_shared<SDLImageShared>(scaled);
|
|
ret->fullSize = scaler.getResultDimensions().dimensions();
|
|
ret->margins = scaler.getResultDimensions().topLeft();
|
|
|
|
// erase our own reference
|
|
SDL_FreeSurface(scaled);
|
|
|
|
if (surf->format->palette)
|
|
SDL_SetSurfacePalette(surf, originalPalette);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void SDLImageShared::exportBitmap(const boost::filesystem::path& path, SDL_Palette * palette) const
|
|
{
|
|
auto directory = path;
|
|
directory.remove_filename();
|
|
boost::filesystem::create_directories(directory);
|
|
|
|
assert(upscalingInProgress == false);
|
|
if (!surf)
|
|
return;
|
|
|
|
if (palette && surf->format->palette)
|
|
SDL_SetSurfacePalette(surf, palette);
|
|
IMG_SavePNG(surf, path.string().c_str());
|
|
if (palette && surf->format->palette)
|
|
SDL_SetSurfacePalette(surf, originalPalette);
|
|
}
|
|
|
|
bool SDLImageShared::isTransparent(const Point & coords) const
|
|
{
|
|
assert(upscalingInProgress == false);
|
|
if (surf)
|
|
return CSDL_Ext::isTransparent(surf, coords.x - margins.x, coords.y - margins.y);
|
|
else
|
|
return true;
|
|
}
|
|
|
|
Rect SDLImageShared::contentRect() const
|
|
{
|
|
assert(upscalingInProgress == false);
|
|
auto tmpMargins = margins;
|
|
auto tmpSize = Point(surf->w, surf->h);
|
|
return Rect(tmpMargins, tmpSize);
|
|
}
|
|
|
|
const SDL_Palette * SDLImageShared::getPalette() const
|
|
{
|
|
assert(upscalingInProgress == false);
|
|
if (!surf)
|
|
return nullptr;
|
|
return surf->format->palette;
|
|
}
|
|
|
|
Point SDLImageShared::dimensions() const
|
|
{
|
|
assert(upscalingInProgress == false);
|
|
return fullSize;
|
|
}
|
|
|
|
std::shared_ptr<const ISharedImage> SDLImageShared::horizontalFlip() const
|
|
{
|
|
assert(upscalingInProgress == false);
|
|
if (!surf)
|
|
return shared_from_this();
|
|
|
|
SDL_Surface * flipped = CSDL_Ext::horizontalFlip(surf);
|
|
auto ret = std::make_shared<SDLImageShared>(flipped);
|
|
ret->fullSize = fullSize;
|
|
ret->margins.x = margins.x;
|
|
ret->margins.y = fullSize.y - surf->h - margins.y;
|
|
ret->fullSize = fullSize;
|
|
|
|
return ret;
|
|
}
|
|
|
|
std::shared_ptr<const ISharedImage> SDLImageShared::verticalFlip() const
|
|
{
|
|
assert(upscalingInProgress == false);
|
|
if (!surf)
|
|
return shared_from_this();
|
|
|
|
SDL_Surface * flipped = CSDL_Ext::verticalFlip(surf);
|
|
auto ret = std::make_shared<SDLImageShared>(flipped);
|
|
ret->fullSize = fullSize;
|
|
ret->margins.x = fullSize.x - surf->w - margins.x;
|
|
ret->margins.y = margins.y;
|
|
ret->fullSize = fullSize;
|
|
|
|
return ret;
|
|
}
|
|
|
|
// Keep the original palette, in order to do color switching operation
|
|
void SDLImageShared::savePalette()
|
|
{
|
|
assert(upscalingInProgress == false);
|
|
// For some images that don't have palette, skip this
|
|
if(surf->format->palette == nullptr)
|
|
return;
|
|
|
|
if(originalPalette == nullptr)
|
|
originalPalette = SDL_AllocPalette(surf->format->palette->ncolors);
|
|
|
|
SDL_SetPaletteColors(originalPalette, surf->format->palette->colors, 0, surf->format->palette->ncolors);
|
|
}
|
|
|
|
SDLImageShared::~SDLImageShared()
|
|
{
|
|
SDL_FreeSurface(surf);
|
|
SDL_FreePalette(originalPalette);
|
|
}
|