diff --git a/include/wx/bmpbndl.h b/include/wx/bmpbndl.h index f5db5ab140..4024e3154c 100644 --- a/include/wx/bmpbndl.h +++ b/include/wx/bmpbndl.h @@ -15,6 +15,7 @@ #include "wx/vector.h" class wxBitmapBundleImpl; +class WXDLLIMPEXP_FWD_CORE wxWindow; // It should be possible to implement SVG rasterizing without raw bitmap // support using wxDC::DrawSpline(), but currently we don't do it and so @@ -96,6 +97,12 @@ public: // default DPI, i.e. 100% scaling. Returns invalid size for empty bundle. wxSize GetDefaultSize() const; + // Get preferred size, i.e. usually the closest size in which a bitmap is + // available to the ideal size determined from the default size and the DPI + // scaling, for the given window. + wxSize GetPreferredSizeFor(wxWindow* window) const; + wxSize GetPreferredSizeAtScale(double scale) const; + // Get bitmap of the specified size, creating a new bitmap from the closest // available size by rescaling it if necessary. // @@ -177,6 +184,11 @@ public: // Must always return a valid size. virtual wxSize GetDefaultSize() const = 0; + // Return the preferred size that should be used at the given scale. + // + // Must always return a valid size. + virtual wxSize GetPreferredSizeAtScale(double scale) const = 0; + // Retrieve the bitmap of exactly the given size. // // Note that this function is non-const because it may generate the bitmap diff --git a/interface/wx/bmpbndl.h b/interface/wx/bmpbndl.h index c17273ef04..c9392657fa 100644 --- a/interface/wx/bmpbndl.h +++ b/interface/wx/bmpbndl.h @@ -252,6 +252,31 @@ public: */ wxSize GetDefaultSize() const; + /** + Get the size that would be best to use for this bundle at the given DPI + scaling factor. + + For bundles containing some number of the fixed-size bitmaps, this + function returns the size of an existing bitmap closest to the ideal + size at the given scale, i.e. GetDefaultSize() multiplied by @a scale. + + Passing a size returned by this function to GetBitmap() ensures that + bitmap doesn't need to be rescaled, which typically significantly + lowers its quality. + */ + wxSize GetPreferredSizeAtScale(double scale) const; + + /** + Get the size that would be best to use for this bundle at the DPI + scaling factor used by the given window. + + This is just a convenient wrapper for GetPreferredSizeAtScale() calling + that function with the result of wxWindow::GetDPIScaleFactor(). + + @param window Non-null and fully created window. + */ + wxSize GetPreferredSizeFor(wxWindow* window) const; + /** Get bitmap of the specified size, creating a new bitmap from the closest available size by rescaling it if necessary. @@ -285,6 +310,16 @@ public: ... determine the minimum/default size for bitmap to use ... } + wxSize GetPreferredSizeAtScale(double scale) const wxOVERRIDE + { + // If it's ok to scale the bitmap, just use the standard size + // at the given scale: + return GetDefaultSize()*scale; + + ... otherwise, an existing bitmap of the size closest to the + one above would need to be found and its size returned ... + } + wxBitmap GetBitmap(const wxSize& size) wxOVERRIDE { ... get the bitmap of the requested size from somewhere and @@ -314,6 +349,13 @@ public: */ virtual wxSize GetDefaultSize() const = 0; + /** + Return the preferred size that should be used at the given scale. + + Must always return a valid size. + */ + virtual wxSize GetPreferredSizeAtScale(double scale) const = 0; + /** Retrieve the bitmap of exactly the given size. diff --git a/samples/toolbar/toolbar.cpp b/samples/toolbar/toolbar.cpp index 9222ace1bb..41c5fb73a3 100644 --- a/samples/toolbar/toolbar.cpp +++ b/samples/toolbar/toolbar.cpp @@ -520,6 +520,13 @@ void MyFrame::PopulateToolbar(wxToolBarBase* toolBar) return m_sizeDef; } + wxSize GetPreferredSizeAtScale(double scale) const wxOVERRIDE + { + // We just scale the bitmap to fit the requested size, so + // we don't really have any preferences. + return m_sizeDef*scale; + } + wxBitmap GetBitmap(const wxSize& size) wxOVERRIDE { // In this simple implementation we don't bother caching diff --git a/src/common/bmpbndl.cpp b/src/common/bmpbndl.cpp index 0628b5ecd0..401a74b523 100644 --- a/src/common/bmpbndl.cpp +++ b/src/common/bmpbndl.cpp @@ -23,6 +23,7 @@ #include "wx/bmpbndl.h" #include "wx/icon.h" +#include "wx/window.h" #include "wx/private/bmpbndl.h" @@ -64,6 +65,7 @@ public: } virtual wxSize GetDefaultSize() const wxOVERRIDE; + virtual wxSize GetPreferredSizeAtScale(double scale) const wxOVERRIDE; virtual wxBitmap GetBitmap(const wxSize& size) wxOVERRIDE; #ifdef __WXOSX__ @@ -157,6 +159,47 @@ wxSize wxBitmapBundleImplSet::GetDefaultSize() const return m_entries[0].bitmap.GetSize(); } +wxSize wxBitmapBundleImplSet::GetPreferredSizeAtScale(double scale) const +{ + // Target size is the ideal size we'd like the bitmap to have at this scale. + const wxSize sizeTarget = GetDefaultSize()*scale; + + const size_t n = m_entries.size(); + for ( size_t i = 0; i < n; ++i ) + { + const wxSize sizeThis = m_entries[i].bitmap.GetSize(); + + // Keep looking for the exact match which we still can hope to find + // while the current bitmap is smaller. + if ( sizeThis.y < sizeTarget.y ) + continue; + + // If we've found the exact match, just return it. + if ( sizeThis.y == sizeTarget.y ) + return sizeThis; + + // We've found the closest bigger bitmap. + + // If there is no smaller bitmap, we have no choice but to use this one. + if ( i == 0 ) + return sizeThis; + + // Decide whether we should use this one or the previous smaller one + // depending on which of them is closer to the target size, breaking + // the tie in favour of the bigger size. + const wxSize sizeLast = m_entries[i - 1].bitmap.GetSize(); + + return sizeThis.y - sizeTarget.y <= sizeTarget.y - sizeLast.y + ? sizeThis + : sizeLast; + + } + + // We only get here if the target size is bigger than all the available + // sizes, in which case we have no choice but to use the biggest bitmap. + return m_entries.back().bitmap.GetSize(); +} + wxBitmap wxBitmapBundleImplSet::GetBitmap(const wxSize& size) { // We use linear search instead if binary one because it's simpler and the @@ -326,6 +369,21 @@ wxSize wxBitmapBundle::GetDefaultSize() const return m_impl->GetDefaultSize(); } +wxSize wxBitmapBundle::GetPreferredSizeFor(wxWindow* window) const +{ + wxCHECK_MSG( window, wxDefaultSize, "window must be valid" ); + + return GetPreferredSizeAtScale(window->GetDPIScaleFactor()); +} + +wxSize wxBitmapBundle::GetPreferredSizeAtScale(double scale) const +{ + if ( !m_impl ) + return wxDefaultSize; + + return m_impl->GetPreferredSizeAtScale(scale); +} + wxBitmap wxBitmapBundle::GetBitmap(const wxSize& size) const { if ( !m_impl ) diff --git a/src/common/tbarbase.cpp b/src/common/tbarbase.cpp index 9d2cbbbcbe..cda354fe36 100644 --- a/src/common/tbarbase.cpp +++ b/src/common/tbarbase.cpp @@ -32,6 +32,7 @@ #include "wx/image.h" #endif // WXWIN_COMPATIBILITY_2_8 #include "wx/menu.h" + #include "wx/vector.h" #endif extern WXDLLEXPORT_DATA(const char) wxToolBarNameStr[] = "toolbar"; @@ -432,6 +433,39 @@ void wxToolBarBase::ClearTools() } } +namespace +{ + +// Struct containing the number of tools preferring to use the given size. +struct SizePrefWithCount +{ + SizePrefWithCount() : count(0) { } + + wxSize size; + int count; +}; + +typedef wxVector SizePrefs; + +void RecordSizePref(SizePrefs& prefs, const wxSize& size) +{ + for ( size_t n = 0; n < prefs.size(); ++n ) + { + if ( prefs[n].size == size ) + { + prefs[n].count++; + return; + } + } + + SizePrefWithCount pref; + pref.size = size; + pref.count++; + prefs.push_back(pref); +} + +} // anonymous namespace + void wxToolBarBase::AdjustToolBitmapSize() { if ( HasFlag(wxTB_NOICONS) ) @@ -442,8 +476,10 @@ void wxToolBarBase::AdjustToolBitmapSize() const wxSize sizeOrig(m_defaultWidth, m_defaultHeight); - wxSize sizeActual(sizeOrig); - + // We want to use preferred bitmap size, but the preferred sizes can be + // different for different bitmap bundles, so record all their preferences + // first. + SizePrefs prefs; const double scale = GetDPIScaleFactor(); for ( wxToolBarToolsList::const_iterator i = m_tools.begin(); i != m_tools.end(); @@ -451,11 +487,48 @@ void wxToolBarBase::AdjustToolBitmapSize() { const wxBitmapBundle& bmp = (*i)->GetNormalBitmapBundle(); if ( bmp.IsOk() ) - sizeActual.IncTo(bmp.GetDefaultSize()*scale); + RecordSizePref(prefs, bmp.GetPreferredSizeAtScale(scale)); } - if ( sizeActual != sizeOrig ) - SetToolBitmapSize(sizeActual); + // Now find the size preferred by most tools. + int countMax = 0; + wxSize sizePreferred; + for ( size_t n = 0; n < prefs.size(); ++n ) + { + const int countThis = prefs[n].count; + const wxSize sizeThis = prefs[n].size; + + if ( countThis > countMax ) + { + countMax = countThis; + sizePreferred = sizeThis; + } + else if ( countThis == countMax ) + { + // We have a tie between different sizes, choose the one + // corresponding to the current scale factor, if possible, as this + // is the ideal bitmap size that should be consistent with all the + // other bitmaps. + if ( sizePreferred != sizeOrig*scale ) + { + if ( sizeThis == sizeOrig*scale ) + { + sizePreferred = sizeThis; + } + else // Neither of the sizes is the ideal one. + { + // Choose the larger one as like this some bitmaps will be + // downscaled, which should look better than upscaling some + // (other) ones. + if ( sizeThis.y > sizePreferred.y ) + sizePreferred = sizeThis; + } + } + } + } + + if ( sizePreferred != sizeOrig ) + SetToolBitmapSize(sizePreferred); } bool wxToolBarBase::Realize() diff --git a/src/generic/bmpsvg.cpp b/src/generic/bmpsvg.cpp index 8d1b4fc1bc..1c34e5215b 100644 --- a/src/generic/bmpsvg.cpp +++ b/src/generic/bmpsvg.cpp @@ -93,6 +93,7 @@ public: } virtual wxSize GetDefaultSize() const wxOVERRIDE; + virtual wxSize GetPreferredSizeAtScale(double scale) const wxOVERRIDE; virtual wxBitmap GetBitmap(const wxSize& size) wxOVERRIDE; private: @@ -126,6 +127,12 @@ wxSize wxBitmapBundleImplSVG::GetDefaultSize() const return m_sizeDef; } +wxSize wxBitmapBundleImplSVG::GetPreferredSizeAtScale(double scale) const +{ + // We consider that we can render at any scale. + return m_sizeDef*scale; +} + wxBitmap wxBitmapBundleImplSVG::GetBitmap(const wxSize& size) { if ( !m_cachedBitmap.IsOk() || m_cachedBitmap.GetSize() != size ) diff --git a/src/msw/anybutton.cpp b/src/msw/anybutton.cpp index 0364d24ee5..b62d516aa0 100644 --- a/src/msw/anybutton.cpp +++ b/src/msw/anybutton.cpp @@ -107,7 +107,7 @@ class wxButtonImageData: public wxObject public: wxButtonImageData(wxWindow* btn, const wxBitmapBundle& normalBundle) { - m_bitmapSize = normalBundle.GetDefaultSize() * btn->GetDPIScaleFactor(); + m_bitmapSize = normalBundle.GetPreferredSizeFor(btn); m_bitmapBundles[wxAnyButton::State_Normal] = normalBundle; } @@ -254,7 +254,7 @@ public: // the image list wxXPButtonImageData(wxAnyButton *btn, const wxBitmapBundle& bitmapBundle) : wxButtonImageData(btn, bitmapBundle), - m_hwndBtn(GetHwndOf(btn)) + m_btn(btn) { InitImageList(); @@ -403,7 +403,7 @@ private: void UpdateImageInfo() { - if ( !::SendMessage(m_hwndBtn, BCM_SETIMAGELIST, 0, (LPARAM)&m_data) ) + if ( !::SendMessage(GetHwndOf(m_btn), BCM_SETIMAGELIST, 0, (LPARAM)&m_data) ) { wxLogDebug("SendMessage(BCM_SETIMAGELIST) failed"); } @@ -415,7 +415,7 @@ private: // We need to recreate the image list using the new size and re-add all // bitmaps to it. - m_bitmapSize = event.Scale(m_bitmapSize); + m_bitmapSize = m_bitmapBundles[wxAnyButton::State_Normal].GetPreferredSizeFor(m_btn); m_iml.Destroy(); InitImageList(); @@ -431,7 +431,7 @@ private: BUTTON_IMAGELIST m_data; // the button we're associated with - const HWND m_hwndBtn; + wxWindow* const m_btn; wxDECLARE_NO_COPY_CLASS(wxXPButtonImageData); diff --git a/src/msw/bmpbndl.cpp b/src/msw/bmpbndl.cpp index f4c82894da..4380df8f14 100644 --- a/src/msw/bmpbndl.cpp +++ b/src/msw/bmpbndl.cpp @@ -138,6 +138,7 @@ public: const wxBitmap& bitmap); virtual wxSize GetDefaultSize() const wxOVERRIDE; + virtual wxSize GetPreferredSizeAtScale(double scale) const wxOVERRIDE; virtual wxBitmap GetBitmap(const wxSize& size) wxOVERRIDE; private: @@ -185,6 +186,52 @@ wxSize wxBitmapBundleImplRC::GetDefaultSize() const return m_bitmaps[0].GetSize(); } +wxSize wxBitmapBundleImplRC::GetPreferredSizeAtScale(double scale) const +{ + // Optimistically assume we're going to use this exact scale by default. + double scalePreferred = scale; + + for ( size_t i = 0; ; ++i ) + { + if ( i == m_resourceInfos.size() ) + { + // The requested scale is bigger than anything we have, so use the + // biggest available one. + scalePreferred = m_resourceInfos[i - 1].scale; + break; + } + + const double scaleThis = m_resourceInfos[i].scale; + + // Keep looking for the exact match which we still can hope to find + // while the current scale is smaller. + if ( scaleThis < scale ) + continue; + + // If we've found the exact match, just use it. + if ( scaleThis == scale ) + break; + + // We've found the closest bigger scale. + + // If there is no smaller one, we have no choice but to use this one. + if ( i == 0 ) + break; + + // Decide whether we should use this one or the previous smaller one + // depending on which of them is closer to the target scale, breaking + // the tie in favour of the bigger one. + const double scaleLast = m_resourceInfos[i - 1].scale; + + scalePreferred = scaleThis - scale <= scale - scaleLast + ? scaleThis + : scaleLast; + break; + } + + return GetDefaultSize()*scalePreferred; +} + wxBitmap wxBitmapBundleImplRC::AddBitmap(const ResourceInfo& info, const wxSize& sizeExpected, diff --git a/src/msw/toolbar.cpp b/src/msw/toolbar.cpp index b9d38736c8..081a864d67 100644 --- a/src/msw/toolbar.cpp +++ b/src/msw/toolbar.cpp @@ -1949,10 +1949,6 @@ void wxToolBar::RealizeHelper() void wxToolBar::OnDPIChanged(wxDPIChangedEvent& event) { - // Ensure that when Realize() is called, the bitmaps size corresponding to - // the new resolution will be used. - SetToolBitmapSize(event.Scale(wxSize(m_defaultWidth, m_defaultHeight))); - // Manually scale the size of the controls. Even though the font has been // updated, the internal size of the controls does not. wxToolBarToolsList::compatibility_iterator node; diff --git a/src/osx/core/bmpbndl.mm b/src/osx/core/bmpbndl.mm index 7fdb89a78a..10663232de 100644 --- a/src/osx/core/bmpbndl.mm +++ b/src/osx/core/bmpbndl.mm @@ -46,6 +46,7 @@ public: ~wxOSXImageBundleImpl(); virtual wxSize GetDefaultSize() const wxOVERRIDE; + virtual wxSize GetPreferredSizeAtScale(double scale) const wxOVERRIDE; virtual wxBitmap GetBitmap(const wxSize& size) wxOVERRIDE; virtual WXImage OSXGetImage() const wxOVERRIDE; @@ -76,6 +77,14 @@ wxSize wxOSXImageBundleImpl::GetDefaultSize() const return wxSize(sz.width, sz.height); } +wxSize wxOSXImageBundleImpl::GetPreferredSizeAtScale(double scale) const +{ + // The system always performs scaling, as the scaling factor is integer and + // so it doesn't make sense to round it up or down, hence we should use the + // theoretical best size for given scale. + return GetDefaultSize()*scale; +} + wxBitmap wxOSXImageBundleImpl::GetBitmap(const wxSize& size) { return wxBitmap(); diff --git a/tests/graphics/bmpbundle.cpp b/tests/graphics/bmpbundle.cpp index aaddfc546a..b14a25148c 100644 --- a/tests/graphics/bmpbundle.cpp +++ b/tests/graphics/bmpbundle.cpp @@ -47,6 +47,27 @@ TEST_CASE("BitmapBundle::FromBitmaps", "[bmpbundle]") CHECK( b.GetBitmap(wxSize(24, 24)).GetSize() == wxSize(24, 24) ); } +TEST_CASE("BitmapBundle::GetPreferredSize", "[bmpbundle]") +{ + CHECK( wxBitmapBundle().GetPreferredSizeAtScale(1) == wxDefaultSize ); + + const wxSize normal(32, 32); + const wxSize bigger(64, 64); + + const wxBitmapBundle + b = wxBitmapBundle::FromBitmaps(wxBitmap(normal), wxBitmap(bigger)); + + CHECK( b.GetPreferredSizeAtScale(0 ) == normal ); + CHECK( b.GetPreferredSizeAtScale(1 ) == normal ); + CHECK( b.GetPreferredSizeAtScale(1.25) == normal ); + CHECK( b.GetPreferredSizeAtScale(1.4 ) == normal ); + CHECK( b.GetPreferredSizeAtScale(1.5 ) == bigger ); + CHECK( b.GetPreferredSizeAtScale(1.75) == bigger ); + CHECK( b.GetPreferredSizeAtScale(2 ) == bigger ); + CHECK( b.GetPreferredSizeAtScale(2.5 ) == bigger ); + CHECK( b.GetPreferredSizeAtScale(3 ) == bigger ); +} + #ifdef wxHAS_SVG TEST_CASE("BitmapBundle::FromSVG", "[bmpbundle][svg]")