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/samples/image/image.cpp b/samples/image/image.cpp index a8541e9166..eb65cd2ffc 100644 --- a/samples/image/image.cpp +++ b/samples/image/image.cpp @@ -127,6 +127,12 @@ enum ID_ROTATE_LEFT = wxID_HIGHEST+1, 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 }; @@ -189,6 +195,7 @@ private: m_bitmap = bitmap; m_zoom = 1.; + m_useImageForZoom = false; wxMenu *menu = new wxMenu; menu->Append(wxID_SAVEAS); @@ -197,9 +204,17 @@ 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->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"); @@ -234,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 */ ); } @@ -442,16 +470,68 @@ 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(); } + 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; @@ -518,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(); }; @@ -946,6 +1031,13 @@ 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) + + 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() //----------------------------------------------------------------------------- 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 diff --git a/tests/benchmarks/bench.cpp b/tests/benchmarks/bench.cpp index 2473db1f38..f40351584e 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; }; @@ -83,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; } // ============================================================================ @@ -99,8 +105,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 +138,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 +152,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 +195,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 +243,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 +264,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 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 diff --git a/tests/benchmarks/image.cpp b/tests/benchmarks/image.cpp index 327a4e4cde..663e1a362e 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,48 @@ 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(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) { - 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(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) { - 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(); }