From 5f96795080ec4bf8821d3961db201cf06b48e94e Mon Sep 17 00:00:00 2001 From: Chris Beck Date: Thu, 13 Nov 2014 23:56:04 -0500 Subject: [PATCH] refactor scale_surface, and add old version as "legacy linear" option --- .../window/advanced_graphics_options.cfg | 3 +- src/gui/dialogs/advanced_graphics_options.cpp | 4 +- src/gui/dialogs/advanced_graphics_options.hpp | 1 + src/image.cpp | 4 + src/sdl/utils.cpp | 145 ++++++++++++++++++ src/sdl/utils.hpp | 13 ++ 6 files changed, 167 insertions(+), 3 deletions(-) diff --git a/data/gui/default/window/advanced_graphics_options.cfg b/data/gui/default/window/advanced_graphics_options.cfg index e08a60a5f023..3837197c6d47 100644 --- a/data/gui/default/window/advanced_graphics_options.cfg +++ b/data/gui/default/window/advanced_graphics_options.cfg @@ -39,10 +39,11 @@ #define _GUI_SCALE_ALGO_OPTIONS CASE - {_GUI_SCALE_OPTION ({CASE}+"_linear") _"Linear" _"Bilinear intepolation scaling (legacy wesnoth option)"} + {_GUI_SCALE_OPTION ({CASE}+"_legacy_lin") _"Linear (legacy)" _"Bilinear intepolation scaling (legacy wesnoth option)"} {_GUI_SCALE_OPTION ({CASE}+"_nn") _"Nearest Neighbor" _"Nearest Neighbor scaling (fastest)"} {_GUI_SCALE_OPTION ({CASE}+"_xbrzlin") _"xBRZ + linear" _"xBRZ followed by Bilinear interpolation"} {_GUI_SCALE_OPTION ({CASE}+"_xbrznn") _"xBRZ + NN" _"xBRZ followed by Nearest Neighbor (recommended)"} + {_GUI_SCALE_OPTION ({CASE}+"_linear") _"Linear" _"Bilinear intepolation scaling (new implementation)"} #enddef #define _GUI_SCALE_CHOICE CASE LABEL TOOLTIP diff --git a/src/gui/dialogs/advanced_graphics_options.cpp b/src/gui/dialogs/advanced_graphics_options.cpp index 4eb4be787bde..1007d077c568 100644 --- a/src/gui/dialogs/advanced_graphics_options.cpp +++ b/src/gui/dialogs/advanced_graphics_options.cpp @@ -72,7 +72,7 @@ void tadvanced_graphics_options::setup_scale_button(const std::string & case_id, { std::string pref_id = "scale_" + case_id; - tadvanced_graphics_options::SCALING_ALGORITHM algo = tadvanced_graphics_options::LINEAR; + tadvanced_graphics_options::SCALING_ALGORITHM algo = tadvanced_graphics_options::LEGACY_LINEAR; try { algo = string_to_SCALING_ALGORITHM(preferences::get(pref_id)); } catch (bad_enum_cast &) { @@ -89,7 +89,7 @@ void tadvanced_graphics_options::setup_scale_button(const std::string & case_id, void tadvanced_graphics_options::scale_button_callback(std::string pref_id, SCALING_ALGORITHM me, twindow & window) { - tadvanced_graphics_options::SCALING_ALGORITHM algo = tadvanced_graphics_options::LINEAR; + tadvanced_graphics_options::SCALING_ALGORITHM algo = tadvanced_graphics_options::LEGACY_LINEAR; try { algo = string_to_SCALING_ALGORITHM(preferences::get(pref_id)); } catch (bad_enum_cast &) { diff --git a/src/gui/dialogs/advanced_graphics_options.hpp b/src/gui/dialogs/advanced_graphics_options.hpp index a0a590fa9901..5139f29237a2 100644 --- a/src/gui/dialogs/advanced_graphics_options.hpp +++ b/src/gui/dialogs/advanced_graphics_options.hpp @@ -48,6 +48,7 @@ class tadvanced_graphics_options : public tdialog (NEAREST_NEIGHBOR, "nn") (XBRZ_LIN, "xbrzlin") (XBRZ_NN, "xbrznn") + (LEGACY_LINEAR, "legacy_lin") ) private: diff --git a/src/image.cpp b/src/image.cpp index a6775cd75ce8..be0b01b819cb 100644 --- a/src/image.cpp +++ b/src/image.cpp @@ -775,6 +775,10 @@ static surface scale_surface_algorithm(const surface & res, int w, int h, gui2:: surface xbrz_temp(scale_surface_xbrz(res, std::max(std::min(z_factor,5),1))); return scale_surface_nn(xbrz_temp, w, h); } + case gui2::tadvanced_graphics_options::LEGACY_LINEAR: + { + return scale_surface_legacy(res, w, h); + } default: assert(false && "I don't know how to implement this scaling algorithm"); throw 42; diff --git a/src/sdl/utils.cpp b/src/sdl/utils.cpp index 777143fe2219..b6b4535eba3f 100644 --- a/src/sdl/utils.cpp +++ b/src/sdl/utils.cpp @@ -480,6 +480,137 @@ surface scale_surface(const surface &surf, int w, int h, bool optimize) return NULL; } + { + const_surface_lock src_lock(src); + surface_lock dst_lock(dst); + + const Uint32* const src_pixels = src_lock.pixels(); + Uint32* const dst_pixels = dst_lock.pixels(); + + fixed_t xratio = fxpdiv(surf->w,w); + fixed_t yratio = fxpdiv(surf->h,h); + + fixed_t ysrc = ftofxp(0.0); + for(int ydst = 0; ydst != h; ++ydst, ysrc += yratio) { + fixed_t xsrc = ftofxp(0.0); + for(int xdst = 0; xdst != w; ++xdst, xsrc += xratio) { + const int xsrcint = fxptoi(xsrc); + const int ysrcint = fxptoi(ysrc); + + const Uint32* const src_word = src_pixels + ysrcint*src->w + xsrcint; + Uint32* const dst_word = dst_pixels + ydst*dst->w + xdst; + const int dx = (xsrcint + 1 < src->w) ? 1 : 0; + const int dy = (ysrcint + 1 < src->h) ? src->w : 0; + + Uint8 r,g,b,a; + Uint32 rr,gg,bb,aa, temp; + + Uint32 pix[4], bilin[4]; + + // This next part is the fixed point + // equivalent of "take everything to + // the right of the decimal point." + // These fundamental weights decide + // the contributions from various + // input pixels. The labels assume + // that the upper left corner of the + // screen ("northeast") is 0,0 but the + // code should still be consistent if + // the graphics origin is actually + // somewhere else. + // + // That is, the bilin array holds the + // "geometric" weights. I.E. If I'm scaling + // a 2 x 2 block a 10 x 10 block, then for + // pixel (2,2) of ouptut, the upper left + // pixel should be 10:1 more influential than + // the upper right, and also 10:1 more influential + // than lower left, and 100:1 more influential + // than lower right. + + const fixed_t e = 0x000000FF & xsrc; + const fixed_t s = 0x000000FF & ysrc; + const fixed_t n = 0xFF - s; + const fixed_t w = 0xFF - e; + + pix[0] = *src_word; // northwest + pix[1] = *(src_word + dx); // northeast + pix[2] = *(src_word + dy); // southwest + pix[3] = *(src_word + dx + dy); // southeast + + bilin[0] = n*w; + bilin[1] = n*e; + bilin[2] = s*w; + bilin[3] = s*e; + + int loc; + rr = bb = gg = aa = 0; + for (loc=0; loc<4; loc++) { + a = pix[loc] >> 24; + r = pix[loc] >> 16; + g = pix[loc] >> 8; + b = pix[loc] >> 0; + + //We also have to implement weighting by alpha for the RGB components + //If a unit has some parts solid and some parts translucent, + //i.e. a red cloak but a dark shadow, then when we scale in + //the shadow shouldn't appear to become red at the edges. + //This part also smoothly interpolates between alpha=0 being + //transparent and having no contribution, vs being opaque. + temp = (a * bilin[loc]); + rr += r * temp; + gg += g * temp; + bb += b * temp; + aa += temp; + } + + a = aa >> (16); // we average the alphas, they don't get weighted by any other factor besides bilin + if (a != 0) { + rr /= a; // finish alpha weighting: divide by sum of alphas + gg /= a; + bb /= a; + } + r = rr >> (16); // now shift over by 16 for the bilin part, 8 + g = gg >> (16); + b = bb >> (16); + *dst_word = (a << 24) + (r << 16) + (g << 8) + b; + } + } + } + + return optimize ? create_optimized_surface(dst) : dst; +} + +// NOTE: Don't pass this function 0 scaling arguments. +surface scale_surface_legacy(const surface &surf, int w, int h, bool optimize) +{ + // Since SDL version 1.1.5 0 is transparent, before 255 was transparent. + assert(SDL_ALPHA_TRANSPARENT==0); + + if(surf == NULL) + return NULL; + + if(w == surf->w && h == surf->h) { + return surf; + } + assert(w >= 0); + assert(h >= 0); + + surface dst(create_neutral_surface(w,h)); + + if (w == 0 || h ==0) { + std::cerr << "Create an empty image\n"; + return create_optimized_surface(dst); + } + + surface src(make_neutral_surface(surf)); + // Now both surfaces are always in the "neutral" pixel format + + if(src == NULL || dst == NULL) { + std::cerr << "Could not create surface to scale onto\n"; + return NULL; + } + { const_surface_lock src_lock(src); surface_lock dst_lock(dst); @@ -568,6 +699,19 @@ surface scale_surface(const surface &surf, int w, int h, bool optimize) // Some of the input images are hex tiles, // created using a hexagon shaped alpha channel // that is either set to full-on or full-off. + // + // If intermediate alpha values are introduced + // along a hex edge, it produces a gametime artifact. + // Moving the mouse around will leave behind + // "hexagon halos" from the temporary highlighting. + // In other words, the Wesnoth rendering engine + // freaks out. + // + // The alpha thresholding step attempts + // to accommodates this limitation. + // There is a small loss of quality. + // For example, skeleton bowstrings + // are not as good as they could be. rr = gg = bb = aa = 0; for (loc=0; loc<4; loc++) { @@ -589,6 +733,7 @@ surface scale_surface(const surface &surf, int w, int h, bool optimize) g = gg >> 16; b = bb >> 16; a = aa >> 16; + a = (a < avg_a/2) ? 0 : avg_a; *dst_word = (a << 24) + (r << 16) + (g << 8) + b; } } diff --git a/src/sdl/utils.hpp b/src/sdl/utils.hpp index 410a842419b4..31cb0f7aa258 100644 --- a/src/sdl/utils.hpp +++ b/src/sdl/utils.hpp @@ -193,6 +193,19 @@ surface scale_surface_nn(const surface & surf, int w, int h); */ surface scale_surface(const surface &surf, int w, int h, bool optimize=true); +/** Scale a surface (legacy (1.10, 1.12) version) + * @param surf The source surface. + * @param w The width of the resulting surface. + * @param h The height of the resulting surface. + * @param optimize Should the return surface be RLE optimized. + * @return A surface containing the scaled version of the source. + * @retval 0 Returned upon error. + * @retval surf Returned if w == surf->w and h == surf->h + * note this ignores the optimize flag. + */ +surface scale_surface_legacy(const surface &surf, int w, int h, bool optimize=true); + + /** Scale a surface using modified nearest neighbour algorithm. Use only if * preserving sharp edges is a priority (e.g. minimap). * @param surf The source surface.