From 1d48dd095d026ed1ae19a8f486795afd65dd3190 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Wed, 25 Aug 2021 17:42:16 +0100 Subject: [PATCH] Scale hardware cursor bilinearly Make the hardware cursor blurry like the rest of the screen. --- CMakeLists.txt | 1 + Source/hwcursor.cpp | 12 +++- Source/utils/sdl_bilinear_scale.cpp | 101 ++++++++++++++++++++++++++++ Source/utils/sdl_bilinear_scale.hpp | 19 ++++++ 4 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 Source/utils/sdl_bilinear_scale.cpp create mode 100644 Source/utils/sdl_bilinear_scale.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 484400bf8..e73e09a42 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -417,6 +417,7 @@ set(libdevilutionx_SRCS Source/utils/file_util.cpp Source/utils/language.cpp Source/utils/paths.cpp + Source/utils/sdl_bilinear_scale.cpp Source/utils/sdl_thread.cpp Source/DiabloUI/art.cpp Source/DiabloUI/art_draw.cpp diff --git a/Source/hwcursor.cpp b/Source/hwcursor.cpp index 0fbd54735..d27df25a7 100644 --- a/Source/hwcursor.cpp +++ b/Source/hwcursor.cpp @@ -15,6 +15,7 @@ #include "cursor.h" #include "engine.h" #include "utils/display.h" +#include "utils/sdl_bilinear_scale.hpp" #include "utils/sdl_wrap.h" namespace devilution { @@ -61,6 +62,11 @@ Point GetHotpointPosition(const SDL_Surface &surface, HotpointPosition position) app_fatal("Unhandled enum value"); } +bool ShouldUseBilinearScaling() +{ + return sgOptions.Graphics.szScaleQuality[0] != '0'; +} + bool SetHardwareCursor(SDL_Surface *surface, HotpointPosition hotpointPosition) { SDLCursorUniquePtr newCursor; @@ -75,7 +81,11 @@ bool SetHardwareCursor(SDL_Surface *surface, HotpointPosition hotpointPosition) SDLSurfaceUniquePtr converted { SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_ARGB8888, 0) }; SDLSurfaceUniquePtr scaledSurface = SDLWrap::CreateRGBSurfaceWithFormat(0, scaledSize.width, scaledSize.height, 32, SDL_PIXELFORMAT_ARGB8888); - SDL_BlitScaled(converted.get(), nullptr, scaledSurface.get(), nullptr); + if (ShouldUseBilinearScaling()) { + BilinearScale32(converted.get(), scaledSurface.get()); + } else { + SDL_BlitScaled(converted.get(), nullptr, scaledSurface.get(), nullptr); + } const Point hotpoint = GetHotpointPosition(*scaledSurface, hotpointPosition); newCursor = SDLCursorUniquePtr { SDL_CreateColorCursor(scaledSurface.get(), hotpoint.x, hotpoint.y) }; } diff --git a/Source/utils/sdl_bilinear_scale.cpp b/Source/utils/sdl_bilinear_scale.cpp new file mode 100644 index 000000000..67d970d08 --- /dev/null +++ b/Source/utils/sdl_bilinear_scale.cpp @@ -0,0 +1,101 @@ +#include "utils/sdl_bilinear_scale.hpp" + +#include +#include + +// Performs bilinear scaling using fixed-width integer math. + +namespace devilution { + +namespace { + +unsigned Frac(unsigned fixedPoint) +{ + return fixedPoint & 0xffff; +} + +unsigned ToInt(unsigned fixedPoint) +{ + return fixedPoint >> 16; +} + +std::unique_ptr CreateMixFactors(unsigned srcSize, unsigned dstSize) +{ + std::unique_ptr result { new unsigned[dstSize + 1] }; + const auto scale = static_cast(65536.0 * static_cast(srcSize - 1) / dstSize); + unsigned mix = 0; + for (unsigned i = 0; i <= dstSize; ++i) { + result[i] = mix; + mix = Frac(mix) + scale; + } + return result; +}; + +std::uint8_t MixColors(std::uint8_t first, std::uint8_t second, unsigned ratio) +{ + return ToInt((second - first) * ratio) + first; +} + +} // namespace + +void BilinearScale32(SDL_Surface *src, SDL_Surface *dst) +{ + const std::unique_ptr mixXs = CreateMixFactors(src->w, dst->w); + const std::unique_ptr mixYs = CreateMixFactors(src->h, dst->h); + + const unsigned dgap = dst->pitch - dst->w * 4; + + auto *srcPixels = static_cast(src->pixels); + auto *dstPixels = static_cast(dst->pixels); + + unsigned *curMixY = &mixYs[0]; + unsigned srcY = 0; + for (unsigned y = 0; y < static_cast(dst->h); ++y) { + std::uint8_t *s[4] = { + srcPixels, // Self + srcPixels + 4, // Right + srcPixels + src->pitch, // Bottom + srcPixels + src->pitch + 4 // Bottom right + }; + + unsigned *curMixX = &mixXs[0]; + unsigned srcX = 0; + for (unsigned x = 0; x < static_cast(dst->w); ++x) { + const unsigned mixX = Frac(*curMixX); + const unsigned mixY = Frac(*curMixY); + for (unsigned channel = 0; channel < 4; ++channel) { + dstPixels[channel] = MixColors( + MixColors(s[0][channel], s[1][channel], mixX), + MixColors(s[2][channel], s[3][channel], mixX), + mixY); + } + + ++curMixX; + if (*curMixX > 0) { + unsigned step = ToInt(*curMixX); + srcX += step; + if (srcX <= static_cast(src->w)) { + step *= 4; + for (auto &v : s) { + v += step; + } + } + } + + dstPixels += 4; + } + + ++curMixY; + if (*curMixY > 0) { + const unsigned step = ToInt(*curMixY); + srcY += step; + if (srcY < static_cast(src->h)) { + srcPixels += step * src->pitch; + } + } + + dstPixels += dgap; + } +} + +} // namespace devilution diff --git a/Source/utils/sdl_bilinear_scale.hpp b/Source/utils/sdl_bilinear_scale.hpp new file mode 100644 index 000000000..00ddd0e90 --- /dev/null +++ b/Source/utils/sdl_bilinear_scale.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include + +#if SDL_VERSION_ATLEAST(2, 0, 0) +#include +#else +#include +#endif + +namespace devilution { + +/** + * @brief Bilinear 32-bit scaling. + * Requires `src` and `dst` to have the same pixel format (ARGB8888 or RGBA8888). + */ +void BilinearScale32(SDL_Surface *src, SDL_Surface *dst); + +} // namespace devilution