mirror of
https://github.com/vcmi/vcmi.git
synced 2025-11-25 22:42:04 +02:00
basic algorithm
This commit is contained in:
@@ -25,6 +25,12 @@ SharedImageLocator::SharedImageLocator(const JsonNode & config, EImageBlitMode m
|
||||
|
||||
if(!config["defFile"].isNull())
|
||||
defFile = AnimationPath::fromJson(config["defFile"]);
|
||||
|
||||
if(!config["generateShadow"].isNull())
|
||||
generateShadow = static_cast<SharedImageLocator::ShadowMode>(config["generateShadow"].Integer());
|
||||
|
||||
if(!config["generateOverlay"].isNull())
|
||||
generateOverlay = static_cast<SharedImageLocator::OverlayMode>(config["generateOverlay"].Integer());
|
||||
}
|
||||
|
||||
SharedImageLocator::SharedImageLocator(const ImagePath & path, EImageBlitMode mode)
|
||||
@@ -60,6 +66,10 @@ bool SharedImageLocator::operator < (const SharedImageLocator & other) const
|
||||
return defFrame < other.defFrame;
|
||||
if(layer != other.layer)
|
||||
return layer < other.layer;
|
||||
if(generateShadow != other.generateShadow)
|
||||
return generateShadow < other.generateShadow;
|
||||
if(generateOverlay != other.generateOverlay)
|
||||
return generateOverlay < other.generateOverlay;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -16,12 +16,28 @@
|
||||
|
||||
struct SharedImageLocator
|
||||
{
|
||||
enum ShadowMode
|
||||
{
|
||||
SHADOW_NONE,
|
||||
SHADOW_NORMAL,
|
||||
SHADOW_SHEAR
|
||||
};
|
||||
enum OverlayMode
|
||||
{
|
||||
OVERLAY_NONE,
|
||||
OVERLAY_OUTLINE,
|
||||
OVERLAY_FLAG
|
||||
};
|
||||
|
||||
std::optional<ImagePath> image;
|
||||
std::optional<AnimationPath> defFile;
|
||||
int defFrame = -1;
|
||||
int defGroup = -1;
|
||||
EImageBlitMode layer = EImageBlitMode::OPAQUE;
|
||||
|
||||
std::optional<ShadowMode> generateShadow;
|
||||
std::optional<OverlayMode> generateOverlay;
|
||||
|
||||
SharedImageLocator() = default;
|
||||
SharedImageLocator(const AnimationPath & path, int frame, int group, EImageBlitMode layer);
|
||||
SharedImageLocator(const JsonNode & config, EImageBlitMode layer);
|
||||
|
||||
@@ -291,9 +291,14 @@ std::shared_ptr<SDLImageShared> RenderHandler::loadScaledImage(const ImageLocato
|
||||
|
||||
std::string imagePathString = pathToLoad.getName();
|
||||
|
||||
if(locator.layer == EImageBlitMode::ONLY_FLAG_COLOR || locator.layer == EImageBlitMode::ONLY_SELECTION)
|
||||
bool generateShadow = locator.generateShadow && (*locator.generateShadow) != SharedImageLocator::ShadowMode::SHADOW_NONE;
|
||||
bool generateOverlay = locator.generateOverlay && (*locator.generateOverlay) != SharedImageLocator::OverlayMode::OVERLAY_NONE;
|
||||
bool isShadow = locator.layer == EImageBlitMode::ONLY_SHADOW_HIDE_SELECTION || locator.layer == EImageBlitMode::ONLY_SHADOW_HIDE_FLAG_COLOR;
|
||||
bool isOverlay = locator.layer == EImageBlitMode::ONLY_FLAG_COLOR || locator.layer == EImageBlitMode::ONLY_SELECTION;
|
||||
|
||||
if(isOverlay && !generateOverlay)
|
||||
imagePathString += "-OVERLAY";
|
||||
if(locator.layer == EImageBlitMode::ONLY_SHADOW_HIDE_SELECTION || locator.layer == EImageBlitMode::ONLY_SHADOW_HIDE_FLAG_COLOR)
|
||||
if(isShadow && !generateShadow)
|
||||
imagePathString += "-SHADOW";
|
||||
if(locator.playerColored.isValidPlayer())
|
||||
imagePathString += "-" + boost::to_upper_copy(GameConstants::PLAYER_COLOR_NAMES[locator.playerColored.getNum()]);
|
||||
@@ -304,16 +309,24 @@ std::shared_ptr<SDLImageShared> RenderHandler::loadScaledImage(const ImageLocato
|
||||
auto imagePathSprites = ImagePath::builtin(imagePathString).addPrefix(scaledSpritesPath.at(locator.scalingFactor));
|
||||
auto imagePathData = ImagePath::builtin(imagePathString).addPrefix(scaledDataPath.at(locator.scalingFactor));
|
||||
|
||||
std::shared_ptr<SDLImageShared> img = nullptr;
|
||||
|
||||
if(CResourceHandler::get()->existsResource(imagePathSprites))
|
||||
return std::make_shared<SDLImageShared>(imagePathSprites);
|
||||
img = std::make_shared<SDLImageShared>(imagePathSprites);
|
||||
else if(CResourceHandler::get()->existsResource(imagePathData))
|
||||
img = std::make_shared<SDLImageShared>(imagePathData);
|
||||
else if(CResourceHandler::get()->existsResource(imagePath))
|
||||
img = std::make_shared<SDLImageShared>(imagePath);
|
||||
|
||||
if(CResourceHandler::get()->existsResource(imagePathData))
|
||||
return std::make_shared<SDLImageShared>(imagePathData);
|
||||
if(img)
|
||||
{
|
||||
if(isShadow && generateShadow)
|
||||
img = img->drawShadow((*locator.generateShadow) == SharedImageLocator::ShadowMode::SHADOW_SHEER);
|
||||
if(isOverlay && generateOverlay && (*locator.generateOverlay) == SharedImageLocator::OverlayMode::OVERLAY_OUTLINE)
|
||||
img = img->drawOutline(Colors::WHITE, getScalingFactor());
|
||||
}
|
||||
|
||||
if(CResourceHandler::get()->existsResource(imagePath))
|
||||
return std::make_shared<SDLImageShared>(imagePath);
|
||||
|
||||
return nullptr;
|
||||
return img;
|
||||
}
|
||||
|
||||
std::shared_ptr<IImage> RenderHandler::loadImage(const ImageLocator & locator)
|
||||
|
||||
@@ -429,6 +429,47 @@ std::shared_ptr<const ISharedImage> SDLImageShared::verticalFlip() const
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::shared_ptr<const ISharedImage> SDLImageShared::drawShadow(bool doSheer) const
|
||||
{
|
||||
if(upscalingInProgress)
|
||||
throw std::runtime_error("Attempt to access images that is still being loaded!");
|
||||
|
||||
if (!surf)
|
||||
return shared_from_this();
|
||||
|
||||
SDL_Surface * shadow = CSDL_Ext::drawShadow(surf, doSheer);
|
||||
auto ret = std::make_shared<SDLImageShared>(shadow);
|
||||
ret->fullSize = fullSize;
|
||||
ret->margins.x = margins.x;
|
||||
ret->margins.y = margins.y;
|
||||
|
||||
// erase our own reference
|
||||
SDL_FreeSurface(shadow);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::shared_ptr<const ISharedImage> SDLImageShared::drawOutline(const ColorRGBA & color, int thickness) const
|
||||
{
|
||||
if(upscalingInProgress)
|
||||
throw std::runtime_error("Attempt to access images that is still being loaded!");
|
||||
|
||||
if (!surf)
|
||||
return shared_from_this();
|
||||
|
||||
SDL_Color sdlColor = { color.r, color.g, color.b, color.a };
|
||||
SDL_Surface * outline = CSDL_Ext::drawOutline(surf, sdlColor, thickness);
|
||||
auto ret = std::make_shared<SDLImageShared>(outline);
|
||||
ret->fullSize = fullSize;
|
||||
ret->margins.x = margins.x;
|
||||
ret->margins.y = margins.y;
|
||||
|
||||
// erase our own reference
|
||||
SDL_FreeSurface(outline);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Keep the original palette, in order to do color switching operation
|
||||
void SDLImageShared::savePalette()
|
||||
{
|
||||
|
||||
@@ -71,5 +71,8 @@ public:
|
||||
[[nodiscard]] std::shared_ptr<const ISharedImage> scaleInteger(int factor, SDL_Palette * palette, EImageBlitMode blitMode) const override;
|
||||
[[nodiscard]] std::shared_ptr<const ISharedImage> scaleTo(const Point & size, SDL_Palette * palette) const override;
|
||||
|
||||
std::shared_ptr<const ISharedImage> drawShadow(bool doSheer) const;
|
||||
std::shared_ptr<const ISharedImage> drawOutline(const ColorRGBA & color, int thickness) const;
|
||||
|
||||
friend class SDLImageLoader;
|
||||
};
|
||||
|
||||
@@ -685,3 +685,313 @@ void CSDL_Ext::getClipRect(SDL_Surface * src, Rect & other)
|
||||
|
||||
other = CSDL_Ext::fromSDL(rect);
|
||||
}
|
||||
|
||||
SDL_Surface * CSDL_Ext::drawOutline(SDL_Surface * source, const SDL_Color & color, int thickness)
|
||||
{
|
||||
// ensure format
|
||||
SDL_Surface *sourceSurface = SDL_ConvertSurfaceFormat(source, SDL_PIXELFORMAT_ARGB8888, 0);
|
||||
SDL_Surface *destSurface = newSurface(Point(source->w, source->h));
|
||||
|
||||
int width = sourceSurface->w;
|
||||
int height = sourceSurface->h;
|
||||
|
||||
// Iterate through the pixels of the image
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
Uint8 maxPixel = 0;
|
||||
Uint8 minPixel = 255;
|
||||
int halfThickness = (thickness + 1) / 2;
|
||||
|
||||
// Loop over the neighborhood around (x, y)
|
||||
for(int offsetY = -halfThickness; offsetY <= halfThickness; offsetY++)
|
||||
{
|
||||
for(int offsetX = -halfThickness; offsetX <= halfThickness; offsetX++)
|
||||
{
|
||||
// Circle instead of rectangle
|
||||
if(offsetX * offsetX + offsetY * offsetY > halfThickness * halfThickness)
|
||||
continue;
|
||||
|
||||
int neighborX = x + offsetX;
|
||||
int neighborY = y + offsetY;
|
||||
|
||||
// Check image bounds
|
||||
if(neighborX >= 0 && neighborX < destSurface->w && neighborY >= 0 && neighborY < destSurface->h)
|
||||
{
|
||||
// Get the pixel at the neighbor position
|
||||
Uint32 pixel = *((Uint32*)sourceSurface->pixels + neighborY * width + neighborX);
|
||||
Uint8 r, g, b, a;
|
||||
SDL_GetRGBA(pixel, sourceSurface->format, &r, &g, &b, &a);
|
||||
|
||||
// Compare the pixel alpha value to find the maximum and maximum
|
||||
if(a > maxPixel)
|
||||
maxPixel = a;
|
||||
if(a < minPixel)
|
||||
minPixel = a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Uint32 newPixel = SDL_MapRGBA(destSurface->format, color.r, color.g, color.b, maxPixel - minPixel);
|
||||
*((Uint32*)destSurface->pixels + y * width + x) = newPixel;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_FreeSurface(sourceSurface);
|
||||
|
||||
return destSurface;
|
||||
}
|
||||
|
||||
void applyAffineTransform(SDL_Surface* src, SDL_Surface* dst, double a, double b, double c, double d, double tx, double ty)
|
||||
{
|
||||
assert(src->format->format == SDL_PIXELFORMAT_ARGB8888);
|
||||
assert(dst->format->format == SDL_PIXELFORMAT_ARGB8888);
|
||||
|
||||
// Lock surfaces for direct pixel access
|
||||
if (SDL_MUSTLOCK(src)) SDL_LockSurface(src);
|
||||
if (SDL_MUSTLOCK(dst)) SDL_LockSurface(dst);
|
||||
|
||||
// Calculate inverse matrix M_inv for mapping dst -> src
|
||||
double det = a * d - b * c;
|
||||
if (det == 0)
|
||||
throw std::runtime_error("Singular transform matrix!");
|
||||
double invDet = 1.0 / det;
|
||||
double ia = d * invDet;
|
||||
double ib = -b * invDet;
|
||||
double ic = -c * invDet;
|
||||
double id = a * invDet;
|
||||
|
||||
// For each pixel in the destination image
|
||||
for(int y = 0; y < dst->h; y++)
|
||||
{
|
||||
for(int x = 0; x < dst->w; x++)
|
||||
{
|
||||
// Map destination pixel (x,y) back to source coordinates (srcX, srcY)
|
||||
double srcX = ia * (x - tx) + ib * (y - ty);
|
||||
double srcY = ic * (x - tx) + id * (y - ty);
|
||||
|
||||
// Nearest neighbor sampling (can be improved to bilinear)
|
||||
int srcXi = static_cast<int>(round(srcX));
|
||||
int srcYi = static_cast<int>(round(srcY));
|
||||
|
||||
// Check bounds
|
||||
if (srcXi >= 0 && srcXi < src->w && srcYi >= 0 && srcYi < src->h)
|
||||
{
|
||||
Uint32* srcPixels = (Uint32*)src->pixels;
|
||||
Uint32* dstPixels = (Uint32*)dst->pixels;
|
||||
|
||||
Uint32 pixel = srcPixels[srcYi * src->w + srcXi];
|
||||
dstPixels[y * dst->w + x] = pixel;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Outside source bounds: set transparent or black
|
||||
Uint32* dstPixels = (Uint32*)dst->pixels;
|
||||
dstPixels[y * dst->w + x] = 0x00000000; // transparent black
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (SDL_MUSTLOCK(src)) SDL_UnlockSurface(src);
|
||||
if (SDL_MUSTLOCK(dst)) SDL_UnlockSurface(dst);
|
||||
}
|
||||
|
||||
int getLowestNonTransparentY(SDL_Surface* surface)
|
||||
{
|
||||
assert(surface->format->format == SDL_PIXELFORMAT_ARGB8888);
|
||||
|
||||
if(SDL_MUSTLOCK(surface))
|
||||
SDL_LockSurface(surface);
|
||||
|
||||
int w = surface->w;
|
||||
int h = surface->h;
|
||||
int bpp = surface->format->BytesPerPixel;
|
||||
Uint8* pixels = (Uint8*)surface->pixels;
|
||||
|
||||
for(int y = h - 1; y >= 0; --y)
|
||||
{
|
||||
Uint8* row = pixels + y * surface->pitch;
|
||||
|
||||
for(int x = 0; x < w; ++x)
|
||||
{
|
||||
Uint32 pixel = *(Uint32*)(row + x * bpp);
|
||||
|
||||
Uint8 r, g, b, a;
|
||||
SDL_GetRGBA(pixel, surface->format, &r, &g, &b, &a);
|
||||
|
||||
if (a > 0)
|
||||
{
|
||||
if(SDL_MUSTLOCK(surface))
|
||||
SDL_UnlockSurface(surface);
|
||||
return y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (SDL_MUSTLOCK(surface)) SDL_UnlockSurface(surface);
|
||||
return -1; // fully transparent
|
||||
}
|
||||
|
||||
void fillAlphaPixelWithRGBA(SDL_Surface* surface, Uint8 r, Uint8 g, Uint8 b, Uint8 a)
|
||||
{
|
||||
assert(surface->format->format == SDL_PIXELFORMAT_ARGB8888);
|
||||
|
||||
if (SDL_MUSTLOCK(surface)) SDL_LockSurface(surface);
|
||||
|
||||
Uint32* pixels = (Uint32*)surface->pixels;
|
||||
int pixelCount = surface->w * surface->h;
|
||||
|
||||
for (int i = 0; i < pixelCount; i++)
|
||||
{
|
||||
Uint32 pixel = pixels[i];
|
||||
|
||||
Uint8 pr, pg, pb, pa;
|
||||
// Extract existing RGBA components using SDL_GetRGBA
|
||||
SDL_GetRGBA(pixel, surface->format, &pr, &pg, &pb, &pa);
|
||||
|
||||
Uint32 newPixel = SDL_MapRGBA(surface->format, r, g, b, a);
|
||||
if(pa == 0)
|
||||
newPixel = SDL_MapRGBA(surface->format, 0, 0, 0, 0);
|
||||
|
||||
pixels[i] = newPixel;
|
||||
}
|
||||
|
||||
if (SDL_MUSTLOCK(surface)) SDL_UnlockSurface(surface);
|
||||
}
|
||||
|
||||
void gaussianBlur(SDL_Surface* surface, int amount)
|
||||
{
|
||||
assert(surface->format->format == SDL_PIXELFORMAT_ARGB8888);
|
||||
|
||||
if (!surface || amount <= 0) return;
|
||||
|
||||
if (SDL_MUSTLOCK(surface))
|
||||
{
|
||||
if (SDL_LockSurface(surface) != 0)
|
||||
throw std::runtime_error("Failed to lock surface!");
|
||||
}
|
||||
|
||||
int width = surface->w;
|
||||
int height = surface->h;
|
||||
int pixelCount = width * height;
|
||||
|
||||
Uint32* pixels = static_cast<Uint32*>(surface->pixels);
|
||||
|
||||
std::vector<Uint8> srcR(pixelCount);
|
||||
std::vector<Uint8> srcG(pixelCount);
|
||||
std::vector<Uint8> srcB(pixelCount);
|
||||
std::vector<Uint8> srcA(pixelCount);
|
||||
|
||||
std::vector<Uint8> dstR(pixelCount);
|
||||
std::vector<Uint8> dstG(pixelCount);
|
||||
std::vector<Uint8> dstB(pixelCount);
|
||||
std::vector<Uint8> dstA(pixelCount);
|
||||
|
||||
// Initialize src channels from surface pixels
|
||||
for (int y = 0; y < height; ++y)
|
||||
{
|
||||
for (int x = 0; x < width; ++x)
|
||||
{
|
||||
Uint32 pixel = pixels[y * width + x];
|
||||
Uint8 r, g, b, a;
|
||||
SDL_GetRGBA(pixel, surface->format, &r, &g, &b, &a);
|
||||
|
||||
int idx = y * width + x;
|
||||
srcR[idx] = r;
|
||||
srcG[idx] = g;
|
||||
srcB[idx] = b;
|
||||
srcA[idx] = a;
|
||||
}
|
||||
}
|
||||
|
||||
// 3x3 Gaussian kernel
|
||||
float kernel[3][3] = {
|
||||
{1.f/16, 2.f/16, 1.f/16},
|
||||
{2.f/16, 4.f/16, 2.f/16},
|
||||
{1.f/16, 2.f/16, 1.f/16}
|
||||
};
|
||||
|
||||
// Apply the blur 'amount' times for stronger blur
|
||||
for (int iteration = 0; iteration < amount; ++iteration) {
|
||||
for (int y = 0; y < height; ++y) {
|
||||
for (int x = 0; x < width; ++x) {
|
||||
float sumR = 0.f, sumG = 0.f, sumB = 0.f, sumA = 0.f;
|
||||
|
||||
for (int ky = -1; ky <= 1; ++ky) {
|
||||
for (int kx = -1; kx <= 1; ++kx) {
|
||||
int nx = x + kx;
|
||||
int ny = y + ky;
|
||||
|
||||
// Clamp edges
|
||||
if (nx < 0) nx = 0;
|
||||
else if (nx >= width) nx = width - 1;
|
||||
if (ny < 0) ny = 0;
|
||||
else if (ny >= height) ny = height - 1;
|
||||
|
||||
int nIdx = ny * width + nx;
|
||||
float kval = kernel[ky + 1][kx + 1];
|
||||
|
||||
sumR += srcR[nIdx] * kval;
|
||||
sumG += srcG[nIdx] * kval;
|
||||
sumB += srcB[nIdx] * kval;
|
||||
sumA += srcA[nIdx] * kval;
|
||||
}
|
||||
}
|
||||
|
||||
int idx = y * width + x;
|
||||
dstR[idx] = static_cast<Uint8>(sumR);
|
||||
dstG[idx] = static_cast<Uint8>(sumG);
|
||||
dstB[idx] = static_cast<Uint8>(sumB);
|
||||
dstA[idx] = static_cast<Uint8>(sumA);
|
||||
}
|
||||
}
|
||||
// Swap src and dst for next iteration (blur chaining)
|
||||
srcR.swap(dstR);
|
||||
srcG.swap(dstG);
|
||||
srcB.swap(dstB);
|
||||
srcA.swap(dstA);
|
||||
}
|
||||
|
||||
// After final iteration, write back to surface pixels
|
||||
for (int y = 0; y < height; ++y) {
|
||||
for (int x = 0; x < width; ++x) {
|
||||
int idx = y * width + x;
|
||||
pixels[idx] = SDL_MapRGBA(surface->format, srcR[idx], srcG[idx], srcB[idx], srcA[idx]);
|
||||
}
|
||||
}
|
||||
|
||||
if (SDL_MUSTLOCK(surface)) {
|
||||
SDL_UnlockSurface(surface);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_Surface * CSDL_Ext::drawShadow(SDL_Surface * source, bool doSheer)
|
||||
{
|
||||
SDL_Surface *sourceSurface = SDL_ConvertSurfaceFormat(source, SDL_PIXELFORMAT_ARGB8888, 0);
|
||||
SDL_Surface *destSurface = newSurface(Point(source->w, source->h));
|
||||
|
||||
assert(destSurface->format->format == SDL_PIXELFORMAT_ARGB8888);
|
||||
|
||||
double shearX = doSheer ? 0.5 : 0.0;
|
||||
double scaleY = 0.25;
|
||||
|
||||
int lowestSource = getLowestNonTransparentY(sourceSurface);
|
||||
int lowestTransformed = lowestSource * scaleY;
|
||||
|
||||
// Parameters for applyAffineTransform
|
||||
double a = 1.0;
|
||||
double b = shearX;
|
||||
double c = 0.0;
|
||||
double d = scaleY;
|
||||
double tx = -shearX * lowestSource;
|
||||
double ty = lowestSource - lowestTransformed;
|
||||
|
||||
applyAffineTransform(sourceSurface, destSurface, a, b, c, d, tx, ty);
|
||||
fillAlphaPixelWithRGBA(destSurface, 0, 0, 0, 128);
|
||||
gaussianBlur(destSurface, 1);
|
||||
|
||||
SDL_FreeSurface(sourceSurface);
|
||||
|
||||
return destSurface;
|
||||
}
|
||||
|
||||
@@ -76,4 +76,7 @@ SDL_Color toSDL(const ColorRGBA & color);
|
||||
void setDefaultColorKey(SDL_Surface * surface);
|
||||
///set key-color to 0,255,255 only if it exactly mapped
|
||||
void setDefaultColorKeyPresize(SDL_Surface * surface);
|
||||
|
||||
SDL_Surface * drawOutline(SDL_Surface * source, const SDL_Color & color, int thickness);
|
||||
SDL_Surface * drawShadow(SDL_Surface * source, bool doSheer);
|
||||
}
|
||||
|
||||
@@ -440,6 +440,8 @@ std::shared_ptr<const ISharedImage> ScalableImageShared::loadOrGenerateImage(EIm
|
||||
|
||||
loadingLocator.image = locator.image;
|
||||
loadingLocator.defFile = locator.defFile;
|
||||
loadingLocator.generateShadow = locator.generateShadow;
|
||||
loadingLocator.generateOverlay = locator.generateOverlay;
|
||||
loadingLocator.defFrame = locator.defFrame;
|
||||
loadingLocator.defGroup = locator.defGroup;
|
||||
loadingLocator.layer = mode;
|
||||
|
||||
Reference in New Issue
Block a user