From c227d107281798fc92d90ee45f26520fd4f5cb6f Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Tue, 22 Feb 2022 01:54:26 +0000 Subject: [PATCH 1/7] Add "Double zoom" command to the image sample image view window Just a convenient way to go to 200% zoom instead of having to use "Zoom in" command several times to get close to it. --- samples/image/image.cpp | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/samples/image/image.cpp b/samples/image/image.cpp index a8541e9166..7a0b8ddcab 100644 --- a/samples/image/image.cpp +++ b/samples/image/image.cpp @@ -127,6 +127,7 @@ enum ID_ROTATE_LEFT = wxID_HIGHEST+1, ID_ROTATE_RIGHT, ID_RESIZE, + ID_ZOOM_x2, ID_PAINT_BG }; @@ -197,9 +198,11 @@ private: "Uncheck this for transparent images"); menu->AppendSeparator(); menu->Append(ID_RESIZE, "&Fit to window\tCtrl-F"); + menu->AppendSeparator(); menu->Append(wxID_ZOOM_IN, "Zoom &in\tCtrl-+"); menu->Append(wxID_ZOOM_OUT, "Zoom &out\tCtrl--"); menu->Append(wxID_ZOOM_100, "Reset zoom to &100%\tCtrl-1"); + menu->Append(ID_ZOOM_x2, "Double zoom level\tCtrl-2"); menu->AppendSeparator(); menu->Append(ID_ROTATE_LEFT, "Rotate &left\tCtrl-L"); menu->Append(ID_ROTATE_RIGHT, "Rotate &right\tCtrl-R"); @@ -442,12 +445,28 @@ private: void OnZoom(wxCommandEvent& event) { - if ( event.GetId() == wxID_ZOOM_IN ) - m_zoom *= 1.2; - else if ( event.GetId() == wxID_ZOOM_OUT ) - m_zoom /= 1.2; - else // wxID_ZOOM_100 - m_zoom = 1.; + switch ( event.GetId() ) + { + case wxID_ZOOM_IN: + m_zoom *= 1.2; + break; + + case wxID_ZOOM_OUT: + m_zoom /= 1.2; + break; + + case wxID_ZOOM_100: + m_zoom = 1.; + break; + + case ID_ZOOM_x2: + m_zoom *= 2.; + break; + + default: + wxFAIL_MSG("unknown zoom command"); + return; + } UpdateStatusBar(); } @@ -946,6 +965,7 @@ wxBEGIN_EVENT_TABLE(MyImageFrame, wxFrame) EVT_MENU(wxID_ZOOM_IN, MyImageFrame::OnZoom) EVT_MENU(wxID_ZOOM_OUT, MyImageFrame::OnZoom) EVT_MENU(wxID_ZOOM_100, MyImageFrame::OnZoom) + EVT_MENU(ID_ZOOM_x2, MyImageFrame::OnZoom) wxEND_EVENT_TABLE() //----------------------------------------------------------------------------- From ce8bf736fa24684195bbb7e5743c36ad4e04e783 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Tue, 22 Feb 2022 02:26:06 +0000 Subject: [PATCH 2/7] Allow using wxImage::Scale() for zooming in image sample By default, continue to use wxDC::SetUserScale(), but allow also using wxImage::Scale() with various parameters to compare how the results look like. --- samples/image/image.cpp | 80 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 4 deletions(-) diff --git a/samples/image/image.cpp b/samples/image/image.cpp index 7a0b8ddcab..eb65cd2ffc 100644 --- a/samples/image/image.cpp +++ b/samples/image/image.cpp @@ -128,6 +128,11 @@ enum ID_ROTATE_RIGHT, ID_RESIZE, ID_ZOOM_x2, + ID_ZOOM_DC, + ID_ZOOM_NEAREST, + ID_ZOOM_BILINEAR, + ID_ZOOM_BICUBIC, + ID_ZOOM_BOX_AVERAGE, ID_PAINT_BG }; @@ -190,6 +195,7 @@ private: m_bitmap = bitmap; m_zoom = 1.; + m_useImageForZoom = false; wxMenu *menu = new wxMenu; menu->Append(wxID_SAVEAS); @@ -204,6 +210,12 @@ private: menu->Append(wxID_ZOOM_100, "Reset zoom to &100%\tCtrl-1"); menu->Append(ID_ZOOM_x2, "Double zoom level\tCtrl-2"); menu->AppendSeparator(); + menu->AppendRadioItem(ID_ZOOM_DC, "Use wx&DC for zoomin\tShift-Ctrl-D"); + menu->AppendRadioItem(ID_ZOOM_NEAREST, "Use rescale nearest\tShift-Ctrl-N"); + menu->AppendRadioItem(ID_ZOOM_BILINEAR, "Use rescale bilinear\tShift-Ctrl-L"); + menu->AppendRadioItem(ID_ZOOM_BICUBIC, "Use rescale bicubic\tShift-Ctrl-C"); + menu->AppendRadioItem(ID_ZOOM_BOX_AVERAGE, "Use rescale box average\tShift-Ctrl-B"); + menu->AppendSeparator(); menu->Append(ID_ROTATE_LEFT, "Rotate &left\tCtrl-L"); menu->Append(ID_ROTATE_RIGHT, "Rotate &right\tCtrl-R"); @@ -237,14 +249,27 @@ private: if ( GetMenuBar()->IsChecked(ID_PAINT_BG) ) dc.Clear(); - dc.SetUserScale(m_zoom, m_zoom); + const int width = int(m_zoom * m_bitmap.GetWidth()); + const int height = int(m_zoom * m_bitmap.GetHeight()); + + wxBitmap bitmap; + if ( m_useImageForZoom ) + { + bitmap = m_bitmap.ConvertToImage().Scale(width, height, + m_resizeQuality); + } + else + { + dc.SetUserScale(m_zoom, m_zoom); + bitmap = m_bitmap; + } const wxSize size = GetClientSize(); dc.DrawBitmap ( - m_bitmap, - dc.DeviceToLogicalX((size.x - int(m_zoom * m_bitmap.GetWidth())) / 2), - dc.DeviceToLogicalY((size.y - int(m_zoom * m_bitmap.GetHeight())) / 2), + bitmap, + dc.DeviceToLogicalX((size.x - width) / 2), + dc.DeviceToLogicalY((size.y - height) / 2), true /* use mask */ ); } @@ -471,6 +496,42 @@ private: UpdateStatusBar(); } + void OnUseZoom(wxCommandEvent& event) + { + bool useImageForZoom = true; + + switch ( event.GetId() ) + { + case ID_ZOOM_DC: + useImageForZoom = false; + break; + + case ID_ZOOM_NEAREST: + m_resizeQuality = wxIMAGE_QUALITY_NEAREST; + break; + + case ID_ZOOM_BILINEAR: + m_resizeQuality = wxIMAGE_QUALITY_BILINEAR; + break; + + case ID_ZOOM_BICUBIC: + m_resizeQuality = wxIMAGE_QUALITY_BICUBIC; + break; + + case ID_ZOOM_BOX_AVERAGE: + m_resizeQuality = wxIMAGE_QUALITY_BOX_AVERAGE; + break; + + default: + wxFAIL_MSG("unknown use for zoom command"); + return; + } + + m_useImageForZoom = useImageForZoom; + + Refresh(); + } + void OnRotate(wxCommandEvent& event) { double angle = 5; @@ -537,6 +598,11 @@ private: wxBitmap m_bitmap; double m_zoom; + // If false, then wxDC is used for zooming. If true, then m_resizeQuality + // is used with wxImage::Scale() for zooming. + bool m_useImageForZoom; + wxImageResizeQuality m_resizeQuality; + wxDECLARE_EVENT_TABLE(); }; @@ -966,6 +1032,12 @@ wxBEGIN_EVENT_TABLE(MyImageFrame, wxFrame) EVT_MENU(wxID_ZOOM_OUT, MyImageFrame::OnZoom) EVT_MENU(wxID_ZOOM_100, MyImageFrame::OnZoom) EVT_MENU(ID_ZOOM_x2, MyImageFrame::OnZoom) + + EVT_MENU(ID_ZOOM_DC, MyImageFrame::OnUseZoom) + EVT_MENU(ID_ZOOM_NEAREST, MyImageFrame::OnUseZoom) + EVT_MENU(ID_ZOOM_BILINEAR, MyImageFrame::OnUseZoom) + EVT_MENU(ID_ZOOM_BICUBIC, MyImageFrame::OnUseZoom) + EVT_MENU(ID_ZOOM_BOX_AVERAGE, MyImageFrame::OnUseZoom) wxEND_EVENT_TABLE() //----------------------------------------------------------------------------- From 5847b302be47b1e4cb1e5786caa482aa29778971 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Tue, 22 Feb 2022 23:39:38 +0000 Subject: [PATCH 3/7] Make it possible to run a benchmark for the given amount of time This is more practical than running it a given number of times, which may result in very long running times for slow functions, such as wxImage resizing tests using wxIMAGE_QUALITY_HIGH. Also show the standard deviation in addition to the average and min/max values. --- tests/benchmarks/bench.cpp | 197 ++++++++++++++++++++++++------------- 1 file changed, 126 insertions(+), 71 deletions(-) diff --git a/tests/benchmarks/bench.cpp b/tests/benchmarks/bench.cpp index 2473db1f38..7cf00fe981 100644 --- a/tests/benchmarks/bench.cpp +++ b/tests/benchmarks/bench.cpp @@ -32,7 +32,7 @@ static const char OPTION_LIST = 'l'; static const char OPTION_SINGLE = '1'; -static const char OPTION_AVG_COUNT = 'a'; +static const char OPTION_RUN_TIME = 't'; static const char OPTION_NUM_RUNS = 'n'; static const char OPTION_NUMERIC_PARAM = 'p'; static const char OPTION_STRING_PARAM = 's'; @@ -64,13 +64,17 @@ public: const wxString& GetStringParameter() const { return m_strParam; } private: + // output the results of a single benchmark if successful or just return + // false if anything went wrong + bool RunSingleBenchmark(Bench::Function* func); + // list all registered benchmarks void ListBenchmarks(); // command lines options/parameters wxSortedArrayString m_toRun; - long m_numRuns, - m_avgCount, + long m_numRuns, // number of times to run a single benchmark or 0 + m_runTime, // minimum time to run a single benchmark if m_numRuns == 0 m_numParam; wxString m_strParam; }; @@ -99,8 +103,8 @@ wxString Bench::GetStringParameter() BenchApp::BenchApp() { - m_avgCount = 10; - m_numRuns = 10000; // just some default (TODO: switch to time-based one) + m_numRuns = 0; // this means to use m_runTime + m_runTime = 500; // default minimum m_numParam = 0; } @@ -132,12 +136,13 @@ void BenchApp::OnInitCmdLine(wxCmdLineParser& parser) "single", "run the benchmark once only"); - parser.AddOption(OPTION_AVG_COUNT, - "avg-count", + parser.AddOption(OPTION_RUN_TIME, + "run-time", wxString::Format ( - "number of times to run benchmarking loop (default: %ld)", - m_avgCount + "maximum time to run each benchmark in ms " + "(default: %ld, set to 0 to disable)", + m_runTime ), wxCMD_LINE_VAL_NUMBER); parser.AddOption(OPTION_NUM_RUNS, @@ -145,7 +150,7 @@ void BenchApp::OnInitCmdLine(wxCmdLineParser& parser) wxString::Format ( "number of times to run each benchmark in a loop " - "(default: %ld)", + "(default: %ld, 0 means to run until max time passes)", m_numRuns ), wxCMD_LINE_VAL_NUMBER); @@ -188,25 +193,26 @@ bool BenchApp::OnCmdLineParsed(wxCmdLineParser& parser) return false; } - bool numRunsSpecified = false; - if ( parser.Found(OPTION_AVG_COUNT, &m_avgCount) ) - numRunsSpecified = true; - if ( parser.Found(OPTION_NUM_RUNS, &m_numRuns) ) - numRunsSpecified = true; + const bool runTimeSpecified = parser.Found(OPTION_RUN_TIME, &m_runTime); + const bool numRunsSpecified = parser.Found(OPTION_NUM_RUNS, &m_numRuns); parser.Found(OPTION_NUMERIC_PARAM, &m_numParam); parser.Found(OPTION_STRING_PARAM, &m_strParam); if ( parser.Found(OPTION_SINGLE) ) { - if ( numRunsSpecified ) + if ( runTimeSpecified || numRunsSpecified ) { wxFprintf(stderr, "Incompatible options specified.\n"); return false; } - m_avgCount = m_numRuns = 1; } + else if ( numRunsSpecified && !runTimeSpecified ) + { + // If only the number of runs is specified, use it only. + m_runTime = 0; + } // construct sorted array for quick verification of benchmark names wxSortedArrayString benchmarks; @@ -235,6 +241,20 @@ bool BenchApp::OnCmdLineParsed(wxCmdLineParser& parser) int BenchApp::OnRun() { int rc = EXIT_SUCCESS; + + wxString params; + if ( m_numParam ) + params += wxString::Format("N=%ld", m_numParam); + if ( !m_strParam.empty() ) + { + if ( !params.empty() ) + params += " and "; + params += wxString::Format("s=\"%s\"", m_strParam); + } + + if ( !params.empty() ) + wxPrintf("Benchmarks are running with non-default %s\n", params); + for ( Bench::Function *func = Bench::Function::GetFirst(); func; func = func->GetNext() ) @@ -242,68 +262,103 @@ int BenchApp::OnRun() if ( m_toRun.Index(func->GetName()) == wxNOT_FOUND ) continue; - wxString params; - if ( m_numParam ) - params += wxString::Format(" with N=%ld", m_numParam); - if ( !m_strParam.empty() ) + if ( !RunSingleBenchmark(func) ) { - if ( !params.empty() ) - params += " and"; - params += wxString::Format(" with s=\"%s\"", m_strParam); - } - - wxPrintf("Benchmarking %s%s: ", func->GetName(), params); - - long timeMin = LONG_MAX, - timeMax = 0, - timeTotal = 0; - bool ok = func->Init(); - for ( long a = 0; ok && a < m_avgCount; a++ ) - { - wxStopWatch sw; - for ( long n = 0; n < m_numRuns && ok; n++ ) - { - ok = func->Run(); - } - - sw.Pause(); - - const long t = sw.Time(); - if ( t < timeMin ) - timeMin = t; - if ( t > timeMax ) - timeMax = t; - timeTotal += t; - } - - func->Done(); - - if ( !ok ) - { - wxPrintf("ERROR\n"); + wxFprintf(stderr, "ERROR running %s\n", func->GetName()); rc = EXIT_FAILURE; } - else - { - wxPrintf("%ldms total, ", timeTotal); - - long times = m_avgCount; - if ( m_avgCount > 2 ) - { - timeTotal -= timeMin + timeMax; - times -= 2; - } - - wxPrintf("%.2f avg (min=%ld, max=%ld)\n", - (float)timeTotal / times, timeMin, timeMax); - } - - fflush(stdout); } return rc; } +bool BenchApp::RunSingleBenchmark(Bench::Function* func) +{ + if ( !func->Init() ) + return false; + + wxPrintf("Benchmarking %s: ", func->GetName()); + fflush(stdout); + + // We use the algorithm for iteratively computing the mean and the + // standard deviation of the sequence of values described in Knuth's + // "The Art of Computer Programming, Volume 2: Seminumerical + // Algorithms", section 4.2.2. + // + // The algorithm defines the sequences M(k) and S(k) as follows: + // + // M(1) = x(1), M(k) = M(k-1) + (x(k) - M(k-1)) / k + // S(1) = 0, S(k) = S(k-1) + (x(k) - M(k-1))*(x(k) - M(k)) + // + // where x(k) is the k-th value. Then the mean is simply the last value + // of the first sequence M(N) and the standard deviation is + // sqrt(S(N)/(N-1)). + + wxStopWatch swTotal; + if ( !func->Run() ) + return false; + + double timeMin = DBL_MAX, + timeMax = 0; + + double m = swTotal.TimeInMicro().ToDouble(); + double s = 0; + + long n = 0; + for ( ;; ) + { + // One termination condition is reaching the maximum number of runs. + if ( ++n == m_numRuns ) + break; + + double t; + { + wxStopWatch swThis; + if ( !func->Run() ) + return false; + + t = swThis.TimeInMicro().ToDouble(); + } + + if ( t < timeMin ) + timeMin = t; + if ( t > timeMax ) + timeMax = t; + + const double lastM = m; + m += (t - lastM) / n; + s += (t - lastM)*(t - m); + + // The other termination condition is that we are running for at least + // m_runTime milliseconds. + if ( m_runTime && swTotal.Time() >= m_runTime ) + break; + } + + func->Done(); + + // For a single run there is no standard deviation and min/max don't make + // much sense. + if ( n == 1 ) + { + wxPrintf("single run took %.0fus\n", m); + } + else + { + s = sqrt(s / (n - 1)); + + wxPrintf + ( + "%ld runs, %.0fus avg, %.0f std dev (%.0f/%.0f min/max)\n", + n, m, s, timeMin, timeMax + ); + } + + fflush(stdout); + + return true; +} + int BenchApp::OnExit() { #if wxUSE_GUI From a8ec7eec0b8ff9c63ecbf2f5bf501b59136a227f Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Tue, 22 Feb 2022 23:58:38 +0000 Subject: [PATCH 4/7] Allow passing default value to Bench::GetXXXParameter() Just an extra convenience for the benchmark functions. --- tests/benchmarks/bench.cpp | 10 ++++++---- tests/benchmarks/bench.h | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/benchmarks/bench.cpp b/tests/benchmarks/bench.cpp index 7cf00fe981..f40351584e 100644 --- a/tests/benchmarks/bench.cpp +++ b/tests/benchmarks/bench.cpp @@ -87,14 +87,16 @@ wxIMPLEMENT_APP_CONSOLE(BenchApp); Bench::Function *Bench::Function::ms_head = NULL; -long Bench::GetNumericParameter() +long Bench::GetNumericParameter(long defVal) { - return wxGetApp().GetNumericParameter(); + const long val = wxGetApp().GetNumericParameter(); + return val ? val : defVal; } -wxString Bench::GetStringParameter() +wxString Bench::GetStringParameter(const wxString& defVal) { - return wxGetApp().GetStringParameter(); + const wxString& val = wxGetApp().GetStringParameter(); + return !val.empty() ? val : defVal; } // ============================================================================ diff --git a/tests/benchmarks/bench.h b/tests/benchmarks/bench.h index 11c38fdb84..9eef60bcec 100644 --- a/tests/benchmarks/bench.h +++ b/tests/benchmarks/bench.h @@ -83,7 +83,7 @@ private: Tests may use this parameter in whatever way they see fit, by default it is 1 but can be set to a different value by user from the command line. */ -long GetNumericParameter(); +long GetNumericParameter(long defValue = 1); /** Get the string parameter. @@ -91,7 +91,7 @@ long GetNumericParameter(); Tests may use this parameter in whatever way they see fit, by default it is empty but can be set to a different value by user from the command line. */ -wxString GetStringParameter(); +wxString GetStringParameter(const wxString& defValue = wxString()); } // namespace Bench From e25b47ee32ab23c6105a6e689956c2ead379d324 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Tue, 22 Feb 2022 23:59:36 +0000 Subject: [PATCH 5/7] Allow tweaking parameters of wxImage::Scale() benchmarks Use the string parameter as the name of the file containing the image to resize and the numeric parameter as the scale factor in percents (so that fractional factors could also be specified using this integer parameter). --- tests/benchmarks/image.cpp | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/tests/benchmarks/image.cpp b/tests/benchmarks/image.cpp index 327a4e4cde..15fc4ebaf5 100644 --- a/tests/benchmarks/image.cpp +++ b/tests/benchmarks/image.cpp @@ -65,7 +65,7 @@ static const wxImage& GetTestImage() if ( !s_triedToLoad ) { s_triedToLoad = true; - s_image.LoadFile("horse.bmp"); + s_image.LoadFile(Bench::GetStringParameter("horse.bmp")); } return s_image; @@ -73,20 +73,32 @@ static const wxImage& GetTestImage() BENCHMARK_FUNC(EnlargeNormal) { - return GetTestImage().Scale(300, 300, wxIMAGE_QUALITY_NORMAL).IsOk(); + const wxImage& image = GetTestImage(); + const double factor = Bench::GetNumericParameter(150) / 100.; + return image.Scale(factor*image.GetWidth(), factor*image.GetHeight(), + wxIMAGE_QUALITY_NORMAL).IsOk(); } BENCHMARK_FUNC(EnlargeHighQuality) { - return GetTestImage().Scale(300, 300, wxIMAGE_QUALITY_HIGH).IsOk(); + const wxImage& image = GetTestImage(); + const double factor = Bench::GetNumericParameter(150) / 100.; + return image.Scale(factor*image.GetWidth(), factor*image.GetHeight(), + wxIMAGE_QUALITY_HIGH).IsOk(); } BENCHMARK_FUNC(ShrinkNormal) { - return GetTestImage().Scale(50, 50, wxIMAGE_QUALITY_NORMAL).IsOk(); + const wxImage& image = GetTestImage(); + const double factor = Bench::GetNumericParameter(50) / 100.; + return image.Scale(factor*image.GetWidth(), factor*image.GetHeight(), + wxIMAGE_QUALITY_NORMAL).IsOk(); } BENCHMARK_FUNC(ShrinkHighQuality) { - return GetTestImage().Scale(50, 50, wxIMAGE_QUALITY_HIGH).IsOk(); + const wxImage& image = GetTestImage(); + const double factor = Bench::GetNumericParameter(50) / 100.; + return image.Scale(factor*image.GetWidth(), factor*image.GetHeight(), + wxIMAGE_QUALITY_HIGH).IsOk(); } From 4e05ee9c5ab7a816e64ea3444859c00d5715e7a5 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 23 Feb 2022 00:01:52 +0000 Subject: [PATCH 6/7] Benchmark wxIMAGE_QUALITY_BOX_AVERAGE too for completeness Check the speed of wxImage::Scale() using this algorithm too. As expected, it's between normal and high quality when upscaling and exactly the same as high quality when shrinking (because it is actually the algorithm used for shrinking for wxIMAGE_QUALITY_HIGH). --- tests/benchmarks/image.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/benchmarks/image.cpp b/tests/benchmarks/image.cpp index 15fc4ebaf5..663e1a362e 100644 --- a/tests/benchmarks/image.cpp +++ b/tests/benchmarks/image.cpp @@ -79,6 +79,14 @@ BENCHMARK_FUNC(EnlargeNormal) wxIMAGE_QUALITY_NORMAL).IsOk(); } +BENCHMARK_FUNC(EnlargeBoxAverage) +{ + const wxImage& image = GetTestImage(); + const double factor = Bench::GetNumericParameter(150) / 100.; + return image.Scale(factor*image.GetWidth(), factor*image.GetHeight(), + wxIMAGE_QUALITY_BOX_AVERAGE).IsOk(); +} + BENCHMARK_FUNC(EnlargeHighQuality) { const wxImage& image = GetTestImage(); @@ -95,6 +103,14 @@ BENCHMARK_FUNC(ShrinkNormal) wxIMAGE_QUALITY_NORMAL).IsOk(); } +BENCHMARK_FUNC(ShrinkBoxAverage) +{ + const wxImage& image = GetTestImage(); + const double factor = Bench::GetNumericParameter(50) / 100.; + return image.Scale(factor*image.GetWidth(), factor*image.GetHeight(), + wxIMAGE_QUALITY_BOX_AVERAGE).IsOk(); +} + BENCHMARK_FUNC(ShrinkHighQuality) { const wxImage& image = GetTestImage(); From 84cb293e712ba2db7a946a166a83e1d0fdec3297 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 23 Feb 2022 00:04:02 +0000 Subject: [PATCH 7/7] Make wxBitmap::Rescale() less horrible for commonly used icons Make the results look somewhat better by using wxIMAGE_QUALITY_NEAREST which preserves sharp horizontal and vertical edges in the images which are common in the icons, with which this function is often used, instead of blurring them as wxIMAGE_QUALITY_HIGH does. This is also much (factor of ~40) faster, which shouldn't hurt neither. --- interface/wx/bitmap.h | 4 ++-- src/common/bmpbase.cpp | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/interface/wx/bitmap.h b/interface/wx/bitmap.h index 7f06e9cec6..b39d1a6faa 100644 --- a/interface/wx/bitmap.h +++ b/interface/wx/bitmap.h @@ -837,8 +837,8 @@ public: This function is just a convenient wrapper for wxImage::Rescale() used to resize the given @a bmp to the requested size. If you need more control over resizing, e.g. to specify the quality option different - from ::wxIMAGE_QUALITY_HIGH used by default, please use wxImage - function directly instead. + from ::wxIMAGE_QUALITY_NEAREST used by this function, please use the + wxImage function directly instead. Both the bitmap itself and size must be valid. diff --git a/src/common/bmpbase.cpp b/src/common/bmpbase.cpp index d511ee5a04..320a80ebb3 100644 --- a/src/common/bmpbase.cpp +++ b/src/common/bmpbase.cpp @@ -70,8 +70,11 @@ void wxBitmapHelpers::Rescale(wxBitmap& bmp, const wxSize& sizeNeeded) wxCHECK_RET( sizeNeeded.IsFullySpecified(), wxS("New size must be given") ); #if wxUSE_IMAGE + // Note that we use "nearest" rescale mode here to preserve sharp edges in + // the icons for which this function is often used. It's also consistent + // with what wxDC::DrawBitmap() does, i.e. the fallback method below. wxImage img = bmp.ConvertToImage(); - img.Rescale(sizeNeeded.x, sizeNeeded.y, wxIMAGE_QUALITY_HIGH); + img.Rescale(sizeNeeded.x, sizeNeeded.y, wxIMAGE_QUALITY_NEAREST); bmp = wxBitmap(img); #else // !wxUSE_IMAGE // Fallback method of scaling the bitmap