From 9f99a4ef7dd95db243423fcadb9f6e49fa73e819 Mon Sep 17 00:00:00 2001 From: Eugene Date: Sun, 21 Jan 2024 02:50:14 +0400 Subject: [PATCH] Simplify nearest neighbor rescale (#8240) --- boost_test_schedule | 4 + projectfiles/CodeBlocks/tests.cbp | 1 + .../project.pbxproj | 76 ++++++------ source_lists/boost_unit_tests | 1 + src/sdl/utils.cpp | 81 ++++--------- src/tests/test_sdl.cpp | 108 ++++++++++++++++++ 6 files changed, 175 insertions(+), 96 deletions(-) create mode 100644 src/tests/test_sdl.cpp diff --git a/boost_test_schedule b/boost_test_schedule index 5bdef60f345..adbe3d9e11c 100644 --- a/boost_test_schedule +++ b/boost_test_schedule @@ -235,6 +235,10 @@ rng/test_mt_rng_reproducibility5 rng/test_mt_rng_reproducibility_coverage rng/validate_get_random_int rng/validate_get_random_int2 +sdl/test_scale_sharp_nullptr +sdl/test_scale_sharp_zero +sdl/test_scale_sharp_round +sdl/test_scale_sharp_fractional test_serialization_utils_and_unicode/utils_join_test test_serialization_utils_and_unicode/utils_split_test test_serialization_utils_and_unicode/utils_quoted_split_test diff --git a/projectfiles/CodeBlocks/tests.cbp b/projectfiles/CodeBlocks/tests.cbp index 42f983ab5dc..a65359a5624 100644 --- a/projectfiles/CodeBlocks/tests.cbp +++ b/projectfiles/CodeBlocks/tests.cbp @@ -1211,6 +1211,7 @@ + diff --git a/projectfiles/Xcode/The Battle for Wesnoth.xcodeproj/project.pbxproj b/projectfiles/Xcode/The Battle for Wesnoth.xcodeproj/project.pbxproj index 9a9cb1aabd4..106e931580c 100644 --- a/projectfiles/Xcode/The Battle for Wesnoth.xcodeproj/project.pbxproj +++ b/projectfiles/Xcode/The Battle for Wesnoth.xcodeproj/project.pbxproj @@ -628,6 +628,7 @@ 62114CC61750B99800CA42C7 /* mouse_action_item.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 62114CC41750B99800CA42C7 /* mouse_action_item.cpp */; }; 62114CC91750B9AF00CA42C7 /* item_palette.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 62114CC71750B9AF00CA42C7 /* item_palette.cpp */; }; 621DEB8415A7BA1F00FEE18A /* highlighter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 621DEB8215A7BA1F00FEE18A /* highlighter.cpp */; }; + 62714C2FBE84B66CF14E3722 /* test_sdl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D911474D925FA88D5B856A0E /* test_sdl.cpp */; }; 627F1EDB175AF35C000042E0 /* aspect_advancements.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 627F1ED9175AF35C000042E0 /* aspect_advancements.cpp */; }; 6295C3B5150FC95C0077D8C5 /* action.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6295C3A7150FC95C0077D8C5 /* action.cpp */; }; 6295C3B6150FC95C0077D8C5 /* action_label.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6295C3AA150FC95C0077D8C5 /* action_label.cpp */; }; @@ -2706,6 +2707,7 @@ C61F473D9AC43768A445E218 /* tod_new_schedule.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = tod_new_schedule.cpp; sourceTree = ""; }; C679447D91FD3623CC852FF8 /* edit_pbl_translation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = edit_pbl_translation.hpp; sourceTree = ""; }; D4594633BF3F8A06D6AE752F /* prompt.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = prompt.hpp; sourceTree = ""; }; + D911474D925FA88D5B856A0E /* test_sdl.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = test_sdl.cpp; path = test_sdl.cpp; sourceTree = ""; }; D9A141EAAE90E98B6F6171D6 /* choose_addon.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = choose_addon.hpp; sourceTree = ""; }; EC0341DF1ECF46FE000F2E2B /* config_attribute_value.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = config_attribute_value.cpp; sourceTree = ""; }; EC0341E01ECF46FE000F2E2B /* config_attribute_value.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = config_attribute_value.hpp; sourceTree = ""; }; @@ -4796,6 +4798,7 @@ 91E356131CACA6CB00774252 /* test_recall_list.cpp */, 91E356141CACA6CB00774252 /* test_rng.cpp */, B597C4AA0FACD42E00CE81F5 /* test_serialization.cpp */, + D911474D925FA88D5B856A0E /* test_sdl.cpp */, B597C4A60FACD42E00CE81F5 /* test_team.cpp */, 91E356171CACA6CB00774252 /* test_unit_map.cpp */, B597C4A50FACD42E00CE81F5 /* test_util.cpp */, @@ -6540,6 +6543,7 @@ EBB44A70837B84A928CB6424 /* edit_pbl_translation.cpp in Sources */, 7BFC4DF5BFF8CF75855BA662 /* prompt.cpp in Sources */, C93048A9AE576B6AD016DFA4 /* tod_new_schedule.cpp in Sources */, + 62714C2FBE84B66CF14E3722 /* test_sdl.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -6643,15 +6647,15 @@ "$(PROJECT_DIR)/Headers/SDL2", "$(PROJECT_DIR)/Headers/harfbuzz", ); + LD_CLASSIC_1000 = ""; + LD_CLASSIC_1100 = ""; + LD_CLASSIC_1200 = ""; + LD_CLASSIC_1300 = ""; + LD_CLASSIC_1400 = ""; + LD_CLASSIC_1500 = "-ld_classic"; LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/lib"; MACOSX_DEPLOYMENT_TARGET = 10.12; - "LD_CLASSIC_1000" = ""; - "LD_CLASSIC_1100" = ""; - "LD_CLASSIC_1200" = ""; - "LD_CLASSIC_1300" = ""; - "LD_CLASSIC_1400" = ""; - "LD_CLASSIC_1500" = "-ld_classic"; OTHER_LDFLAGS = ( "-lz", "-lbz2", @@ -6717,16 +6721,16 @@ GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = Info.plist; INSTALL_PATH = /Applications; + LD_CLASSIC_1000 = ""; + LD_CLASSIC_1100 = ""; + LD_CLASSIC_1200 = ""; + LD_CLASSIC_1300 = ""; + LD_CLASSIC_1400 = ""; + LD_CLASSIC_1500 = "-ld_classic"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; LLVM_LTO = YES_THIN; OTHER_CFLAGS = "-Wall"; OTHER_CPLUSPLUSFLAGS = "-Wall"; - "LD_CLASSIC_1000" = ""; - "LD_CLASSIC_1100" = ""; - "LD_CLASSIC_1200" = ""; - "LD_CLASSIC_1300" = ""; - "LD_CLASSIC_1400" = ""; - "LD_CLASSIC_1500" = "-ld_classic"; OTHER_LDFLAGS = ( "-lz", "-lbz2", @@ -7112,15 +7116,15 @@ GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = Info.plist; INSTALL_PATH = /Applications; + LD_CLASSIC_1000 = ""; + LD_CLASSIC_1100 = ""; + LD_CLASSIC_1200 = ""; + LD_CLASSIC_1300 = ""; + LD_CLASSIC_1400 = ""; + LD_CLASSIC_1500 = "-ld_classic"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; OTHER_CFLAGS = "-Wall"; OTHER_CPLUSPLUSFLAGS = "-Wall"; - "LD_CLASSIC_1000" = ""; - "LD_CLASSIC_1100" = ""; - "LD_CLASSIC_1200" = ""; - "LD_CLASSIC_1300" = ""; - "LD_CLASSIC_1400" = ""; - "LD_CLASSIC_1500" = "-ld_classic"; OTHER_LDFLAGS = ( "-lz", "-lbz2", @@ -7178,16 +7182,16 @@ GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = Info.plist; INSTALL_PATH = /Applications; + LD_CLASSIC_1000 = ""; + LD_CLASSIC_1100 = ""; + LD_CLASSIC_1200 = ""; + LD_CLASSIC_1300 = ""; + LD_CLASSIC_1400 = ""; + LD_CLASSIC_1500 = "-ld_classic"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; LLVM_LTO = YES_THIN; OTHER_CFLAGS = "-Wall"; OTHER_CPLUSPLUSFLAGS = "-Wall"; - "LD_CLASSIC_1000" = ""; - "LD_CLASSIC_1100" = ""; - "LD_CLASSIC_1200" = ""; - "LD_CLASSIC_1300" = ""; - "LD_CLASSIC_1400" = ""; - "LD_CLASSIC_1500" = "-ld_classic"; OTHER_LDFLAGS = ( "-lz", "-lbz2", @@ -7263,17 +7267,17 @@ "$(PROJECT_DIR)/Headers/SDL2", "$(PROJECT_DIR)/Headers/harfbuzz", ); + LD_CLASSIC_1000 = ""; + LD_CLASSIC_1100 = ""; + LD_CLASSIC_1200 = ""; + LD_CLASSIC_1300 = ""; + LD_CLASSIC_1400 = ""; + LD_CLASSIC_1500 = "-ld_classic"; LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/lib"; MACOSX_DEPLOYMENT_TARGET = 10.12; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "-Wall"; - "LD_CLASSIC_1000" = ""; - "LD_CLASSIC_1100" = ""; - "LD_CLASSIC_1200" = ""; - "LD_CLASSIC_1300" = ""; - "LD_CLASSIC_1400" = ""; - "LD_CLASSIC_1500" = "-ld_classic"; OTHER_LDFLAGS = ( "-lz", "-lbz2", @@ -7318,15 +7322,15 @@ "$(PROJECT_DIR)/Headers/SDL2", "$(PROJECT_DIR)/Headers/harfbuzz", ); + LD_CLASSIC_1000 = ""; + LD_CLASSIC_1100 = ""; + LD_CLASSIC_1200 = ""; + LD_CLASSIC_1300 = ""; + LD_CLASSIC_1400 = ""; + LD_CLASSIC_1500 = "-ld_classic"; LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; LIBRARY_SEARCH_PATHS = "$(PROJECT_DIR)/lib"; MACOSX_DEPLOYMENT_TARGET = 10.12; - "LD_CLASSIC_1000" = ""; - "LD_CLASSIC_1100" = ""; - "LD_CLASSIC_1200" = ""; - "LD_CLASSIC_1300" = ""; - "LD_CLASSIC_1400" = ""; - "LD_CLASSIC_1500" = "-ld_classic"; OTHER_LDFLAGS = ( "-lz", "-lbz2", diff --git a/source_lists/boost_unit_tests b/source_lists/boost_unit_tests index c8a7b45ffc1..0e9b878f654 100644 --- a/source_lists/boost_unit_tests +++ b/source_lists/boost_unit_tests @@ -19,6 +19,7 @@ tests/test_map_location.cpp tests/test_mp_connect.cpp tests/test_recall_list.cpp tests/test_rng.cpp +tests/test_sdl.cpp tests/test_serialization.cpp tests/test_simple_wml.cpp tests/test_team.cpp diff --git a/src/sdl/utils.cpp b/src/sdl/utils.cpp index ccd2e07b57b..0e6e3c8fce2 100644 --- a/src/sdl/utils.cpp +++ b/src/sdl/utils.cpp @@ -395,29 +395,28 @@ surface scale_surface_legacy(const surface &surf, int w, int h) surface scale_surface_sharp(const surface& surf, int w, int h) { // Since SDL version 1.1.5 0 is transparent, before 255 was transparent. - assert(SDL_ALPHA_TRANSPARENT==0); + assert(SDL_ALPHA_TRANSPARENT == 0); - if(surf == nullptr) + if(surf == nullptr) { return nullptr; - + } if(w == surf->w && h == surf->h) { return surf; } + assert(w >= 0); assert(h >= 0); - - surface dst(w,h); - - if (w == 0 || h ==0) { - PLAIN_LOG << "Create an empty image"; - return dst; - } - - if(surf == nullptr || dst == nullptr) { + surface dst(w, h); + if(dst == nullptr) { PLAIN_LOG << "Could not create surface to scale onto"; return nullptr; } + if(w == 0 || h == 0) { + PLAIN_LOG << "Creating an empty image"; + return dst; + } + { const_surface_lock src_lock(surf); surface_lock dst_lock(dst); @@ -425,56 +424,18 @@ surface scale_surface_sharp(const surface& surf, int w, int h) const uint32_t* const src_pixels = src_lock.pixels(); uint32_t* const dst_pixels = dst_lock.pixels(); - float xratio = static_cast(surf->w) / w; - float yratio = static_cast(surf->h) / h; + const int src_w = surf->w; + const int src_h = surf->h; - float ysrc = 0.0f; - for(int ydst = 0; ydst != h; ++ydst, ysrc += yratio) { - float xsrc = 0.0f; - for(int xdst = 0; xdst != w; ++xdst, xsrc += xratio) { - float red = 0.0f, green = 0.0f, blue = 0.0f, alpha = 0.0f; - - float summation = 0.0f; - - // We now have a rectangle, (xsrc,ysrc,xratio,yratio) - // which we want to derive the pixel from - for(float xloc = xsrc; xloc < xsrc+xratio; xloc += 1) { - const float xsize = std::min(std::floor(xloc+1)-xloc,xsrc+xratio-xloc); - - for(float yloc = ysrc; yloc < ysrc+yratio; yloc += 1) { - const int xsrcint = std::max(0,std::min(surf->w-1,static_cast(xsrc))); - const int ysrcint = std::max(0,std::min(surf->h-1,static_cast(ysrc))); - const float ysize = std::min(std::floor(yloc+1)-yloc,ysrc+yratio-yloc); - - uint8_t r,g,b,a; - - SDL_GetRGBA(src_pixels[ysrcint*surf->w + xsrcint],surf->format,&r,&g,&b,&a); - float value = xsize * ysize; - summation += value; - if (!a) continue; - value *= a; - alpha += value; - red += r * value; - green += g * value; - blue += b * value; - } - } - - if (alpha != 0) { - red = red / alpha + 0.5f; - green = green / alpha + 0.5f; - blue = blue / alpha + 0.5f; - alpha = alpha / summation + 0.5f; - } - - dst_pixels[ydst*dst->w + xdst] = SDL_MapRGBA( - dst->format - , static_cast(red) - , static_cast(green) - , static_cast(blue) - , static_cast(alpha)); + const float xratio = static_cast(src_w) / static_cast(w); + const float yratio = static_cast(src_h) / static_cast(h); + for(int ydst = 0; ydst != h; ++ydst) { + for(int xdst = 0; xdst != w; ++xdst) { + // Project dst pixel to a single corresponding src pixel by scale and simply take it + const int xsrc = std::floor(static_cast(xdst) * xratio); + const int ysrc = std::floor(static_cast(ydst) * yratio); + dst_pixels[ydst * dst->w + xdst] = src_pixels[ysrc * src_w + xsrc]; } - } } diff --git a/src/tests/test_sdl.cpp b/src/tests/test_sdl.cpp new file mode 100644 index 00000000000..a2363c49bf2 --- /dev/null +++ b/src/tests/test_sdl.cpp @@ -0,0 +1,108 @@ +/* + Copyright (C) 2023 + Part of the Battle for Wesnoth Project https://www.wesnoth.org/ + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY. + + See the COPYING file for more details. +*/ + +#define GETTEXT_DOMAIN "wesnoth-test" + +#include "sdl/surface.hpp" +#include "sdl/utils.hpp" + +#include +#include +#include + +constexpr uint32_t red = 0xFF'FF'00'00; +constexpr uint32_t green = 0xFF'00'FF'00; +constexpr uint32_t blue = 0xFF'00'00'FF; +constexpr uint32_t yellow = 0xFF'FF'FF'00; +constexpr uint32_t white = 0xFF'FF'FF'FF; +constexpr uint32_t black = 0xFF'00'00'00; + +constexpr std::array img_4x4 { + red, white, green, black, + black, black, black, black, + blue, white, yellow, black, + black, black, black, black, +}; + +constexpr std::array img_4x4_to_2x2_result { + red, green, + blue, yellow, +}; + +constexpr std::array img_4x4_to_3x2_result { + red, white, green, + blue, white, yellow +}; + +template +surface array_to_surface(const std::array& arr) +{ + surface surf{w, h}; + + { + surface_lock surf_lock{surf}; + uint32_t* const pixels = surf_lock.pixels(); + for(size_t i = 0; i < w * h; ++i) { + pixels[i] = arr[i]; + } + } + + return surf; +} + +std::vector surface_to_vec(const surface& surf) +{ + const_surface_lock lock{surf}; + const uint32_t* const pixels = lock.pixels(); + std::vector pixel_vec; + const int surf_size = surf->w * surf->h; + std::copy(pixels, pixels + surf_size, std::back_inserter(pixel_vec)); + return pixel_vec; +} + +BOOST_AUTO_TEST_SUITE(sdl) + +BOOST_AUTO_TEST_CASE(test_scale_sharp_nullptr) +{ + surface result = scale_surface_sharp(nullptr, 2, 2); + BOOST_CHECK_EQUAL(result, nullptr); +} + +BOOST_AUTO_TEST_CASE(test_scale_sharp_zero) +{ + surface src = array_to_surface<4, 4>(img_4x4); + surface result = scale_surface_sharp(src, 0, 0); + BOOST_CHECK_EQUAL(result->w, 0); + BOOST_CHECK_EQUAL(result->h, 0); +} + +BOOST_AUTO_TEST_CASE(test_scale_sharp_round) +{ + surface src = array_to_surface<4, 4>(img_4x4); + surface result = scale_surface_sharp(src, 2, 2); + std::vector result_pixels = surface_to_vec(result); + BOOST_CHECK_EQUAL_COLLECTIONS( + result_pixels.begin(), result_pixels.end(), img_4x4_to_2x2_result.begin(), img_4x4_to_2x2_result.end()); +} + +BOOST_AUTO_TEST_CASE(test_scale_sharp_fractional) +{ + surface src = array_to_surface<4, 4>(img_4x4); + surface result = scale_surface_sharp(src, 3, 2); + std::vector result_pixels = surface_to_vec(result); + BOOST_CHECK_EQUAL_COLLECTIONS( + result_pixels.begin(), result_pixels.end(), img_4x4_to_3x2_result.begin(), img_4x4_to_3x2_result.end()); +} + +BOOST_AUTO_TEST_SUITE_END()