mirror of
https://github.com/vcmi/vcmi.git
synced 2025-08-10 22:31:40 +02:00
Moved image scaling & optimization logic to separate classes
This commit is contained in:
@@ -104,6 +104,7 @@ set(vcmiclientcommon_SRCS
|
||||
renderSDL/RenderHandler.cpp
|
||||
renderSDL/SDLImage.cpp
|
||||
renderSDL/SDLImageLoader.cpp
|
||||
renderSDL/SDLImageScaler.cpp
|
||||
renderSDL/SDLRWwrapper.cpp
|
||||
renderSDL/ScreenHandler.cpp
|
||||
renderSDL/SDL_Extensions.cpp
|
||||
@@ -313,6 +314,7 @@ set(vcmiclientcommon_HEADERS
|
||||
renderSDL/RenderHandler.h
|
||||
renderSDL/SDLImage.h
|
||||
renderSDL/SDLImageLoader.h
|
||||
renderSDL/SDLImageScaler.h
|
||||
renderSDL/SDLRWwrapper.h
|
||||
renderSDL/ScreenHandler.h
|
||||
renderSDL/SDL_Extensions.h
|
||||
|
@@ -13,6 +13,7 @@
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../render/IScreenHandler.h"
|
||||
#include "../renderSDL/SDL_Extensions.h"
|
||||
#include "../renderSDL/SDLImageScaler.h"
|
||||
|
||||
#include <SDL_image.h>
|
||||
#include <SDL_surface.h>
|
||||
@@ -35,9 +36,10 @@ void CanvasImage::scaleTo(const Point & size, EScalingAlgorithm algorithm)
|
||||
{
|
||||
Point scaledSize = size * GH.screenHandler().getScalingFactor();
|
||||
|
||||
auto newSurface = CSDL_Ext::scaleSurface(surface, scaledSize.x, scaledSize.y, algorithm);
|
||||
SDLImageScaler scaler(surface);
|
||||
scaler.scaleSurface(scaledSize, algorithm);
|
||||
SDL_FreeSurface(surface);
|
||||
surface = newSurface;
|
||||
surface = scaler.acquireResultSurface();
|
||||
}
|
||||
|
||||
void CanvasImage::exportBitmap(const boost::filesystem::path & path) const
|
||||
|
@@ -11,6 +11,8 @@
|
||||
#include "CBitmapFont.h"
|
||||
|
||||
#include "SDL_Extensions.h"
|
||||
#include "SDLImageScaler.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../render/Colors.h"
|
||||
@@ -207,9 +209,10 @@ CBitmapFont::CBitmapFont(const std::string & filename):
|
||||
|
||||
auto filterName = settings["video"]["fontUpscalingFilter"].String();
|
||||
EScalingAlgorithm algorithm = filterNameToEnum.at(filterName);
|
||||
auto scaledSurface = CSDL_Ext::scaleSurfaceIntegerFactor(atlasImage, GH.screenHandler().getScalingFactor(), algorithm);
|
||||
SDLImageScaler scaler(atlasImage);
|
||||
scaler.scaleSurfaceIntegerFactor(GH.screenHandler().getScalingFactor(), algorithm);
|
||||
SDL_FreeSurface(atlasImage);
|
||||
atlasImage = scaledSurface;
|
||||
atlasImage = scaler.acquireResultSurface();
|
||||
}
|
||||
|
||||
logGlobal->debug("Loaded BMP font: '%s', height %d, ascent %d",
|
||||
|
@@ -12,6 +12,7 @@
|
||||
#include "CursorHardware.h"
|
||||
|
||||
#include "SDL_Extensions.h"
|
||||
#include "SDLImageScaler.h"
|
||||
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../render/IScreenHandler.h"
|
||||
@@ -60,7 +61,10 @@ void CursorHardware::setImage(std::shared_ptr<IImage> image, const Point & pivot
|
||||
CSDL_Ext::fillSurface(cursorSurface, CSDL_Ext::toSDL(Colors::TRANSPARENCY));
|
||||
|
||||
image->draw(cursorSurface, Point(0,0), nullptr, GH.screenHandler().getScalingFactor());
|
||||
auto cursorSurfaceScaled = CSDL_Ext::scaleSurface(cursorSurface, cursorDimensionsScaled.x, cursorDimensionsScaled.y, EScalingAlgorithm::BILINEAR );
|
||||
|
||||
SDLImageScaler scaler(cursorSurface);
|
||||
scaler.scaleSurface(cursorDimensionsScaled, EScalingAlgorithm::BILINEAR);
|
||||
SDL_Surface * cursorSurfaceScaled = scaler.acquireResultSurface();
|
||||
|
||||
auto oldCursor = cursor;
|
||||
cursor = SDL_CreateColorCursor(cursorSurfaceScaled, pivotOffsetScaled.x, pivotOffsetScaled.y);
|
||||
|
@@ -248,11 +248,12 @@ std::shared_ptr<SDLImageShared> RenderHandler::loadScaledImage(const ImageLocato
|
||||
pathToLoad = *remappedLocator.image;
|
||||
}
|
||||
|
||||
if(!locator.image)
|
||||
return nullptr;
|
||||
|
||||
if(locator.image)
|
||||
pathToLoad = *locator.image;
|
||||
|
||||
if (pathToLoad.empty())
|
||||
return nullptr;
|
||||
|
||||
std::string imagePathString = pathToLoad.getName();
|
||||
|
||||
if(locator.layer == EImageBlitMode::ONLY_OVERLAY)
|
||||
|
@@ -11,14 +11,12 @@
|
||||
#include "SDLImage.h"
|
||||
|
||||
#include "SDLImageLoader.h"
|
||||
#include "SDLImageScaler.h"
|
||||
#include "SDL_Extensions.h"
|
||||
|
||||
#include "../render/ColorFilter.h"
|
||||
#include "../render/Colors.h"
|
||||
#include "../render/CBitmapHandler.h"
|
||||
#include "../render/CDefFile.h"
|
||||
#include "../render/Graphics.h"
|
||||
#include "../xBRZ/xbrz.h"
|
||||
#include "../gui/CGuiHandler.h"
|
||||
#include "../render/IScreenHandler.h"
|
||||
|
||||
@@ -145,82 +143,14 @@ void SDLImageShared::optimizeSurface()
|
||||
if (!surf)
|
||||
return;
|
||||
|
||||
int left = surf->w;
|
||||
int top = surf->h;
|
||||
int right = 0;
|
||||
int bottom = 0;
|
||||
SDLImageOptimizer optimizer(surf, Rect(margins, fullSize));
|
||||
|
||||
// locate fully-transparent area around image
|
||||
// H3 hadles this on format level, but mods or images scaled in runtime do not
|
||||
if (surf->format->palette)
|
||||
{
|
||||
for (int y = 0; y < surf->h; ++y)
|
||||
{
|
||||
const uint8_t * row = static_cast<uint8_t *>(surf->pixels) + y * surf->pitch;
|
||||
for (int x = 0; x < surf->w; ++x)
|
||||
{
|
||||
if (row[x] != 0)
|
||||
{
|
||||
// opaque or can be opaque (e.g. disabled shadow)
|
||||
top = std::min(top, y);
|
||||
left = std::min(left, x);
|
||||
right = std::max(right, x);
|
||||
bottom = std::max(bottom, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int y = 0; y < surf->h; ++y)
|
||||
{
|
||||
for (int x = 0; x < surf->w; ++x)
|
||||
{
|
||||
ColorRGBA color;
|
||||
SDL_GetRGBA(CSDL_Ext::getPixel(surf, x, y), surf->format, &color.r, &color.g, &color.b, &color.a);
|
||||
|
||||
if (color.a != SDL_ALPHA_TRANSPARENT)
|
||||
{
|
||||
// opaque
|
||||
top = std::min(top, y);
|
||||
left = std::min(left, x);
|
||||
right = std::max(right, x);
|
||||
bottom = std::max(bottom, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (left == surf->w)
|
||||
{
|
||||
// empty image - simply delete it
|
||||
optimizer.optimizeSurface(surf);
|
||||
SDL_FreeSurface(surf);
|
||||
surf = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
if (left != 0 || top != 0 || right != surf->w - 1 || bottom != surf->h - 1)
|
||||
{
|
||||
// non-zero border found
|
||||
Rect newDimensions(left, top, right - left + 1, bottom - top + 1);
|
||||
SDL_Rect rectSDL = CSDL_Ext::toSDL(newDimensions);
|
||||
auto newSurface = CSDL_Ext::newSurface(newDimensions.dimensions(), surf);
|
||||
SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_NONE);
|
||||
SDL_BlitSurface(surf, &rectSDL, newSurface, nullptr);
|
||||
|
||||
if (SDL_HasColorKey(surf))
|
||||
{
|
||||
uint32_t colorKey;
|
||||
SDL_GetColorKey(surf, &colorKey);
|
||||
SDL_SetColorKey(newSurface, SDL_TRUE, colorKey);
|
||||
}
|
||||
|
||||
SDL_FreeSurface(surf);
|
||||
surf = newSurface;
|
||||
|
||||
margins.x += left;
|
||||
margins.y += top;
|
||||
}
|
||||
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
|
||||
@@ -234,22 +164,20 @@ std::shared_ptr<const ISharedImage> SDLImageShared::scaleInteger(int factor, SDL
|
||||
if (palette && surf->format->palette)
|
||||
SDL_SetSurfacePalette(surf, palette);
|
||||
|
||||
SDL_Surface * scaled = nullptr;
|
||||
SDLImageScaler scaler(surf, Rect(margins, fullSize));
|
||||
|
||||
// dump heuristics to differentiate tileable UI elements from map object / combat assets
|
||||
if (mode == EImageBlitMode::OPAQUE || mode == EImageBlitMode::COLORKEY || mode == EImageBlitMode::SIMPLE)
|
||||
scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, factor, EScalingAlgorithm::XBRZ_OPAQUE);
|
||||
scaler.scaleSurfaceIntegerFactor(GH.screenHandler().getScalingFactor(), EScalingAlgorithm::XBRZ_OPAQUE);
|
||||
else
|
||||
scaled = CSDL_Ext::scaleSurfaceIntegerFactor(surf, factor, EScalingAlgorithm::XBRZ_ALPHA);
|
||||
scaler.scaleSurfaceIntegerFactor(GH.screenHandler().getScalingFactor(), EScalingAlgorithm::XBRZ_ALPHA);
|
||||
|
||||
SDL_Surface * scaled = scaler.acquireResultSurface();
|
||||
|
||||
auto ret = std::make_shared<SDLImageShared>(scaled);
|
||||
|
||||
ret->fullSize.x = fullSize.x * factor;
|
||||
ret->fullSize.y = fullSize.y * factor;
|
||||
|
||||
ret->margins.x = (int) round((float)margins.x * factor);
|
||||
ret->margins.y = (int) round((float)margins.y * factor);
|
||||
ret->optimizeSurface();
|
||||
ret->fullSize = scaler.getResultDimensions().dimensions();
|
||||
ret->margins = scaler.getResultDimensions().topLeft();
|
||||
|
||||
// erase our own reference
|
||||
SDL_FreeSurface(scaled);
|
||||
@@ -262,13 +190,14 @@ std::shared_ptr<const ISharedImage> SDLImageShared::scaleInteger(int factor, SDL
|
||||
|
||||
std::shared_ptr<const ISharedImage> SDLImageShared::scaleTo(const Point & size, SDL_Palette * palette) const
|
||||
{
|
||||
float scaleX = static_cast<float>(size.x) / fullSize.x;
|
||||
float scaleY = static_cast<float>(size.y) / fullSize.y;
|
||||
|
||||
if (palette && surf->format->palette)
|
||||
SDL_SetSurfacePalette(surf, palette);
|
||||
|
||||
auto scaled = CSDL_Ext::scaleSurface(surf, (int)(surf->w * scaleX), (int)(surf->h * scaleY), EScalingAlgorithm::BILINEAR);
|
||||
SDLImageScaler scaler(surf, Rect(margins, fullSize));
|
||||
|
||||
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]);
|
||||
@@ -278,12 +207,8 @@ std::shared_ptr<const ISharedImage> SDLImageShared::scaleTo(const Point & size,
|
||||
CSDL_Ext::setDefaultColorKey(scaled);//just in case
|
||||
|
||||
auto ret = std::make_shared<SDLImageShared>(scaled);
|
||||
|
||||
ret->fullSize.x = (int) round((float)fullSize.x * scaleX);
|
||||
ret->fullSize.y = (int) round((float)fullSize.y * scaleY);
|
||||
|
||||
ret->margins.x = (int) round((float)margins.x * scaleX);
|
||||
ret->margins.y = (int) round((float)margins.y * scaleY);
|
||||
ret->fullSize = scaler.getResultDimensions().dimensions();
|
||||
ret->margins = scaler.getResultDimensions().topLeft();
|
||||
|
||||
// erase our own reference
|
||||
SDL_FreeSurface(scaled);
|
||||
|
233
client/renderSDL/SDLImageScaler.cpp
Normal file
233
client/renderSDL/SDLImageScaler.cpp
Normal file
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* SDLImageScaler.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 "SDLImageScaler.h"
|
||||
|
||||
#include "SDL_Extensions.h"
|
||||
|
||||
#include "../CMT.h"
|
||||
#include "../xBRZ/xbrz.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <SDL_surface.h>
|
||||
|
||||
SDLImageOptimizer::SDLImageOptimizer(SDL_Surface * surf, const Rect & virtualDimensions)
|
||||
: surf(surf)
|
||||
, virtualDimensions(virtualDimensions)
|
||||
{
|
||||
}
|
||||
|
||||
void SDLImageOptimizer::optimizeSurface(SDL_Surface * formatSourceSurface)
|
||||
{
|
||||
if (!surf)
|
||||
return;
|
||||
|
||||
int left = surf->w;
|
||||
int top = surf->h;
|
||||
int right = 0;
|
||||
int bottom = 0;
|
||||
|
||||
// locate fully-transparent area around image
|
||||
// H3 hadles this on format level, but mods or images scaled in runtime do not
|
||||
if (surf->format->palette)
|
||||
{
|
||||
for (int y = 0; y < surf->h; ++y)
|
||||
{
|
||||
const uint8_t * row = static_cast<uint8_t *>(surf->pixels) + y * surf->pitch;
|
||||
for (int x = 0; x < surf->w; ++x)
|
||||
{
|
||||
if (row[x] != 0)
|
||||
{
|
||||
// opaque or can be opaque (e.g. disabled shadow)
|
||||
top = std::min(top, y);
|
||||
left = std::min(left, x);
|
||||
right = std::max(right, x);
|
||||
bottom = std::max(bottom, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int y = 0; y < surf->h; ++y)
|
||||
{
|
||||
for (int x = 0; x < surf->w; ++x)
|
||||
{
|
||||
ColorRGBA color;
|
||||
SDL_GetRGBA(CSDL_Ext::getPixel(surf, x, y), surf->format, &color.r, &color.g, &color.b, &color.a);
|
||||
|
||||
if (color.a != SDL_ALPHA_TRANSPARENT)
|
||||
{
|
||||
// opaque
|
||||
top = std::min(top, y);
|
||||
left = std::min(left, x);
|
||||
right = std::max(right, x);
|
||||
bottom = std::max(bottom, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// empty image
|
||||
if (left == surf->w)
|
||||
return;
|
||||
|
||||
if (left != 0 || top != 0 || right != surf->w - 1 || bottom != surf->h - 1)
|
||||
{
|
||||
// non-zero border found
|
||||
Rect newDimensions(left, top, right - left + 1, bottom - top + 1);
|
||||
SDL_Rect rectSDL = CSDL_Ext::toSDL(newDimensions);
|
||||
auto newSurface = CSDL_Ext::newSurface(newDimensions.dimensions(), formatSourceSurface);
|
||||
SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_NONE);
|
||||
SDL_BlitSurface(surf, &rectSDL, newSurface, nullptr);
|
||||
|
||||
if (SDL_HasColorKey(surf))
|
||||
{
|
||||
uint32_t colorKey;
|
||||
SDL_GetColorKey(surf, &colorKey);
|
||||
SDL_SetColorKey(newSurface, SDL_TRUE, colorKey);
|
||||
}
|
||||
output = newSurface;
|
||||
|
||||
virtualDimensions.x += left;
|
||||
virtualDimensions.y += top;
|
||||
}
|
||||
else
|
||||
{
|
||||
output = surf;
|
||||
output->refcount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_Surface * SDLImageOptimizer::acquireResultSurface()
|
||||
{
|
||||
SDL_Surface * result = output;
|
||||
output = nullptr;
|
||||
return result;
|
||||
}
|
||||
|
||||
const Rect & SDLImageOptimizer::getResultDimensions() const
|
||||
{
|
||||
return virtualDimensions;
|
||||
}
|
||||
|
||||
void SDLImageScaler::scaleSurface(Point targetDimensions, EScalingAlgorithm algorithm)
|
||||
{
|
||||
if(!targetDimensions.x || !targetDimensions.y)
|
||||
throw std::runtime_error("invalid scaling dimensions!");
|
||||
|
||||
Point inputSurfaceSize(intermediate->w, intermediate->h);
|
||||
Point outputSurfaceSize = targetDimensions * inputSurfaceSize / virtualDimensionsInput.dimensions();
|
||||
Point outputMargins = targetDimensions * virtualDimensionsInput.topLeft() / virtualDimensionsInput.dimensions();
|
||||
|
||||
// TODO: use xBRZ if possible? E.g. when scaling to 150% do 100% -> 200% via xBRZ and then linear downscale 200% -> 150%?
|
||||
// Need to investigate which is optimal for performance and for visuals
|
||||
ret = CSDL_Ext::newSurface(Point(outputSurfaceSize.x, outputSurfaceSize.y), intermediate);
|
||||
|
||||
virtualDimensionsOutput = Rect(outputMargins, targetDimensions); // TODO: account for input virtual size
|
||||
|
||||
const uint32_t * srcPixels = static_cast<const uint32_t*>(intermediate->pixels);
|
||||
uint32_t * dstPixels = static_cast<uint32_t*>(ret->pixels);
|
||||
|
||||
if (algorithm == EScalingAlgorithm::NEAREST)
|
||||
xbrz::nearestNeighborScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h);
|
||||
else
|
||||
xbrz::bilinearScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h);
|
||||
}
|
||||
|
||||
void SDLImageScaler::scaleSurfaceIntegerFactor(int factor, EScalingAlgorithm algorithm)
|
||||
{
|
||||
if(factor == 0)
|
||||
throw std::runtime_error("invalid scaling factor!");
|
||||
|
||||
int newWidth = intermediate->w * factor;
|
||||
int newHight = intermediate->h * factor;
|
||||
|
||||
virtualDimensionsOutput = virtualDimensionsInput * factor;
|
||||
|
||||
ret = CSDL_Ext::newSurface(Point(newWidth, newHight), intermediate);
|
||||
|
||||
assert(intermediate->pitch == intermediate->w * 4);
|
||||
assert(ret->pitch == ret->w * 4);
|
||||
|
||||
const uint32_t * srcPixels = static_cast<const uint32_t*>(intermediate->pixels);
|
||||
uint32_t * dstPixels = static_cast<uint32_t*>(ret->pixels);
|
||||
|
||||
switch (algorithm)
|
||||
{
|
||||
case EScalingAlgorithm::NEAREST:
|
||||
xbrz::nearestNeighborScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h);
|
||||
break;
|
||||
case EScalingAlgorithm::BILINEAR:
|
||||
xbrz::bilinearScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h);
|
||||
break;
|
||||
case EScalingAlgorithm::XBRZ_ALPHA:
|
||||
case EScalingAlgorithm::XBRZ_OPAQUE:
|
||||
{
|
||||
auto format = algorithm == EScalingAlgorithm::XBRZ_OPAQUE ? xbrz::ColorFormat::ARGB_CLAMPED : xbrz::ColorFormat::ARGB;
|
||||
|
||||
if(intermediate->h < 32)
|
||||
{
|
||||
// for tiny images tbb incurs too high overhead
|
||||
xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, format, {});
|
||||
}
|
||||
else
|
||||
{
|
||||
// xbrz recommends granulation of 16, but according to tests, for smaller images granulation of 4 is actually the best option
|
||||
const int granulation = intermediate->h > 400 ? 16 : 4;
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, intermediate->h, granulation), [this, factor, srcPixels, dstPixels, format](const tbb::blocked_range<size_t> & r)
|
||||
{
|
||||
xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, format, {}, r.begin(), r.end());
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error("invalid scaling algorithm!");
|
||||
}
|
||||
}
|
||||
|
||||
SDLImageScaler::SDLImageScaler(SDL_Surface * surf)
|
||||
:SDLImageScaler(surf, Rect(0,0,surf->w, surf->h))
|
||||
{
|
||||
}
|
||||
|
||||
SDLImageScaler::SDLImageScaler(SDL_Surface * surf, const Rect & virtualDimensions)
|
||||
{
|
||||
SDLImageOptimizer optimizer(surf, virtualDimensions);
|
||||
optimizer.optimizeSurface(screen);
|
||||
intermediate = optimizer.acquireResultSurface();
|
||||
virtualDimensionsInput = optimizer.getResultDimensions();
|
||||
|
||||
if (intermediate == surf)
|
||||
{
|
||||
SDL_FreeSurface(intermediate);
|
||||
intermediate = SDL_ConvertSurfaceFormat(surf, SDL_PIXELFORMAT_ARGB8888, 0);
|
||||
}
|
||||
}
|
||||
|
||||
SDLImageScaler::~SDLImageScaler()
|
||||
{
|
||||
SDL_FreeSurface(intermediate);
|
||||
SDL_FreeSurface(ret);
|
||||
}
|
||||
|
||||
SDL_Surface * SDLImageScaler::acquireResultSurface()
|
||||
{
|
||||
SDL_Surface * result = ret;
|
||||
ret = nullptr;
|
||||
return result;
|
||||
}
|
||||
|
||||
const Rect & SDLImageScaler::getResultDimensions() const
|
||||
{
|
||||
return virtualDimensionsOutput;
|
||||
}
|
63
client/renderSDL/SDLImageScaler.h
Normal file
63
client/renderSDL/SDLImageScaler.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* SDLImageScaler.h, 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
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../render/IImage.h"
|
||||
#include "../../lib/Rect.h"
|
||||
|
||||
class SDLImageOptimizer : boost::noncopyable
|
||||
{
|
||||
SDL_Surface * surf = nullptr;
|
||||
SDL_Surface * output = nullptr;
|
||||
Rect virtualDimensions = Rect(0,0,0,0);
|
||||
public:
|
||||
SDLImageOptimizer(SDL_Surface * surf, const Rect & virtualDimensions);
|
||||
|
||||
void optimizeSurface(SDL_Surface * formatSourceSurface);
|
||||
|
||||
/// Aquires resulting surface and transfers surface ownership to the caller
|
||||
/// May return nullptr if input image was empty
|
||||
SDL_Surface * acquireResultSurface();
|
||||
|
||||
/// Returns adjusted virtual dimensions of resulting surface
|
||||
const Rect & getResultDimensions() const;
|
||||
};
|
||||
|
||||
/// Class that performs scaling of SDL surfaces
|
||||
/// Object construction MUST be performed while holding UI lock
|
||||
/// but task execution can be performed asynchronously if needed
|
||||
class SDLImageScaler : boost::noncopyable
|
||||
{
|
||||
SDL_Surface * intermediate = nullptr;
|
||||
SDL_Surface * ret = nullptr;
|
||||
Rect virtualDimensionsInput = Rect(0,0,0,0);
|
||||
Rect virtualDimensionsOutput = Rect(0,0,0,0);
|
||||
|
||||
public:
|
||||
SDLImageScaler(SDL_Surface * surf);
|
||||
SDLImageScaler(SDL_Surface * surf, const Rect & virtualDimensions);
|
||||
~SDLImageScaler();
|
||||
|
||||
/// Performs upscaling or downscaling to a requested dimensions
|
||||
/// Aspect ratio is NOT maintained.
|
||||
/// xbrz algorithm is not supported in this mode
|
||||
void scaleSurface(Point dimensions, EScalingAlgorithm algorithm);
|
||||
|
||||
/// Performs upscaling by specified integral factor, potentially using xbrz algorithm if requested
|
||||
void scaleSurfaceIntegerFactor(int factor, EScalingAlgorithm algorithm);
|
||||
|
||||
/// Aquires resulting surface and transfers surface ownership to the caller
|
||||
/// May return nullptr if input image was empty
|
||||
SDL_Surface * acquireResultSurface();
|
||||
|
||||
/// Returns adjusted virtual dimensions of resulting surface
|
||||
const Rect & getResultDimensions() const;
|
||||
};
|
@@ -631,89 +631,6 @@ void CSDL_Ext::convertToGrayscale( SDL_Surface * surf, const Rect & rect )
|
||||
}
|
||||
}
|
||||
|
||||
// scaling via bilinear interpolation algorithm.
|
||||
// NOTE: best results are for scaling in range 50%...200%.
|
||||
// And upscaling looks awful right now - should be fixed somehow
|
||||
SDL_Surface * CSDL_Ext::scaleSurface(SDL_Surface * surf, int width, int height, EScalingAlgorithm algorithm)
|
||||
{
|
||||
if(!surf || !width || !height)
|
||||
return nullptr;
|
||||
|
||||
// TODO: use xBRZ if possible? E.g. when scaling to 150% do 100% -> 200% via xBRZ and then linear downscale 200% -> 150%?
|
||||
// Need to investigate which is optimal for performance and for visuals
|
||||
|
||||
SDL_Surface * intermediate = SDL_ConvertSurface(surf, screen->format, 0);
|
||||
SDL_Surface * ret = newSurface(Point(width, height), intermediate);
|
||||
|
||||
const uint32_t * srcPixels = static_cast<const uint32_t*>(intermediate->pixels);
|
||||
uint32_t * dstPixels = static_cast<uint32_t*>(ret->pixels);
|
||||
|
||||
if (algorithm == EScalingAlgorithm::NEAREST)
|
||||
xbrz::nearestNeighborScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h);
|
||||
else
|
||||
xbrz::bilinearScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h);
|
||||
|
||||
SDL_FreeSurface(intermediate);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SDL_Surface * CSDL_Ext::scaleSurfaceIntegerFactor(SDL_Surface * surf, int factor, EScalingAlgorithm algorithm)
|
||||
{
|
||||
if(surf == nullptr || factor == 0)
|
||||
return nullptr;
|
||||
|
||||
int newWidth = surf->w * factor;
|
||||
int newHight = surf->h * factor;
|
||||
|
||||
SDL_Surface * intermediate = SDL_ConvertSurfaceFormat(surf, SDL_PIXELFORMAT_ARGB8888, 0);
|
||||
SDL_Surface * ret = newSurface(Point(newWidth, newHight), intermediate);
|
||||
|
||||
assert(intermediate->pitch == intermediate->w * 4);
|
||||
assert(ret->pitch == ret->w * 4);
|
||||
|
||||
const uint32_t * srcPixels = static_cast<const uint32_t*>(intermediate->pixels);
|
||||
uint32_t * dstPixels = static_cast<uint32_t*>(ret->pixels);
|
||||
|
||||
switch (algorithm)
|
||||
{
|
||||
case EScalingAlgorithm::NEAREST:
|
||||
xbrz::nearestNeighborScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h);
|
||||
break;
|
||||
case EScalingAlgorithm::BILINEAR:
|
||||
xbrz::bilinearScale(srcPixels, intermediate->w, intermediate->h, dstPixels, ret->w, ret->h);
|
||||
break;
|
||||
case EScalingAlgorithm::XBRZ_ALPHA:
|
||||
case EScalingAlgorithm::XBRZ_OPAQUE:
|
||||
{
|
||||
auto format = algorithm == EScalingAlgorithm::XBRZ_OPAQUE ? xbrz::ColorFormat::ARGB_CLAMPED : xbrz::ColorFormat::ARGB;
|
||||
|
||||
if(intermediate->h < 32)
|
||||
{
|
||||
// for tiny images tbb incurs too high overhead
|
||||
xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, format, {});
|
||||
}
|
||||
else
|
||||
{
|
||||
// xbrz recommends granulation of 16, but according to tests, for smaller images granulation of 4 is actually the best option
|
||||
const int granulation = intermediate->h > 400 ? 16 : 4;
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, intermediate->h, granulation), [factor, srcPixels, dstPixels, intermediate, format](const tbb::blocked_range<size_t> & r)
|
||||
{
|
||||
xbrz::scale(factor, srcPixels, dstPixels, intermediate->w, intermediate->h, format, {}, r.begin(), r.end());
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error("invalid scaling algorithm!");
|
||||
}
|
||||
|
||||
SDL_FreeSurface(intermediate);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void CSDL_Ext::blitSurface(SDL_Surface * src, const Rect & srcRectInput, SDL_Surface * dst, const Point & dstPoint)
|
||||
{
|
||||
SDL_Rect srcRect = CSDL_Ext::toSDL(srcRectInput);
|
||||
|
@@ -18,7 +18,6 @@ struct SDL_Renderer;
|
||||
struct SDL_Texture;
|
||||
struct SDL_Surface;
|
||||
struct SDL_Color;
|
||||
enum class EScalingAlgorithm : int8_t;
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
@@ -91,10 +90,6 @@ using TColorPutterAlpha = void (*)(uint8_t *&, const uint8_t &, const uint8_t &,
|
||||
template<int bpp>
|
||||
SDL_Surface * createSurfaceWithBpp(int width, int height); //create surface with give bits per pixels value
|
||||
|
||||
// bilinear filtering. Always returns rgba surface
|
||||
SDL_Surface * scaleSurface(SDL_Surface * surf, int width, int height, EScalingAlgorithm scaler);
|
||||
SDL_Surface * scaleSurfaceIntegerFactor(SDL_Surface * surf, int factor, EScalingAlgorithm scaler);
|
||||
|
||||
template<int bpp>
|
||||
void convertToGrayscaleBpp(SDL_Surface * surf, const Rect & rect);
|
||||
void convertToGrayscale(SDL_Surface * surf, const Rect & rect);
|
||||
|
@@ -260,7 +260,7 @@ void ScalableImageShared::draw(SDL_Surface * where, const Point & dest, const Re
|
||||
if (scaled.at(scalingFactor).shadow.at(0))
|
||||
flipAndDraw(scaled.at(scalingFactor).shadow, Colors::WHITE_TRUE, parameters.alphaValue);
|
||||
|
||||
if (parameters.player != PlayerColor::CANNOT_DETERMINE)
|
||||
if (parameters.player.isValidPlayer())
|
||||
{
|
||||
scaled.at(scalingFactor).playerColored[parameters.player.getNum()]->draw(where, parameters.palette, dest, src, Colors::WHITE_TRUE, parameters.alphaValue, locator.layer);
|
||||
}
|
||||
@@ -351,7 +351,7 @@ void ScalableImageInstance::playerColored(const PlayerColor & player)
|
||||
{
|
||||
parameters.player = player;
|
||||
|
||||
if (!parameters.palette)
|
||||
if (parameters.palette)
|
||||
parameters.playerColored(player);
|
||||
|
||||
image->preparePlayerColoredImage(player);
|
||||
|
@@ -15,9 +15,6 @@
|
||||
|
||||
#include "../../lib/Color.h"
|
||||
|
||||
#include <tbb/concurrent_queue.h>
|
||||
#include <tbb/task_arena.h>
|
||||
|
||||
struct SDL_Palette;
|
||||
|
||||
class ScalableImageInstance;
|
||||
@@ -47,24 +44,6 @@ struct ScalableImageParameters : boost::noncopyable
|
||||
void adjustPalette(const SDL_Palette * originalPalette, EImageBlitMode blitMode, const ColorFilter & shifter, uint32_t colorsToSkipMask);
|
||||
};
|
||||
|
||||
class ImageScaler
|
||||
{
|
||||
ImageScaler();
|
||||
|
||||
tbb::task_arena arena;
|
||||
public:
|
||||
static ImageScaler & getInstance()
|
||||
{
|
||||
static ImageScaler scaler;
|
||||
return scaler;
|
||||
}
|
||||
|
||||
void enqueueTask(const std::function<void()> & task)
|
||||
{
|
||||
arena.enqueue(task);
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
@@ -97,12 +76,6 @@ class ScalableImageShared final : public std::enable_shared_from_this<ScalableIm
|
||||
/// Locator of this image, for loading additional (e.g. upscaled) images
|
||||
const SharedImageLocator locator;
|
||||
|
||||
/// Contains all upscaling tasks related to this image that finished processing and can be applied
|
||||
tbb::concurrent_queue<std::function<void()>> upscalingQueue;
|
||||
|
||||
/// Number of images that are currently being upscaled
|
||||
int scheduledUpscalingEvents = 0;
|
||||
|
||||
std::shared_ptr<const ISharedImage> loadOrGenerateImage(EImageBlitMode mode, int8_t scalingFactor, PlayerColor color) const;
|
||||
|
||||
void loadScaledImages(int8_t scalingFactor, PlayerColor color);
|
||||
|
@@ -54,6 +54,11 @@ public:
|
||||
return Point(x*mul, y*mul);
|
||||
}
|
||||
|
||||
constexpr Point operator/(const Point &b) const
|
||||
{
|
||||
return Point(x/b.x,y/b.y);
|
||||
}
|
||||
|
||||
constexpr Point operator*(const Point &b) const
|
||||
{
|
||||
return Point(x*b.x,y*b.y);
|
||||
|
@@ -120,7 +120,7 @@ TurnInfo::TurnInfo(TurnInfoCache * sharedCache, const CGHeroInstance * target, i
|
||||
{
|
||||
static const CSelector selector = Selector::type()(BonusType::ROUGH_TERRAIN_DISCOUNT);
|
||||
const auto & bonuses = sharedCache->roughTerrainDiscount.getBonusList(target, selector);
|
||||
roughTerrainDiscountValue = bonuses->getFirst(daySelector) != nullptr;
|
||||
roughTerrainDiscountValue = bonuses->valOfBonuses(daySelector);
|
||||
}
|
||||
|
||||
{
|
||||
|
Reference in New Issue
Block a user