/////////////////////////////////////////////////////////////////////////////// // Name: src/common/bmpbndl.cpp // Purpose: Common methods of wxBitmapBundle class. // Author: Vadim Zeitlin // Created: 2021-09-22 // Copyright: (c) 2021 Vadim Zeitlin // Licence: wxWindows licence /////////////////////////////////////////////////////////////////////////////// // ============================================================================ // declarations // ============================================================================ // ---------------------------------------------------------------------------- // headers // ---------------------------------------------------------------------------- // for compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" #ifndef WX_PRECOMP #endif // WX_PRECOMP #include "wx/bmpbndl.h" #include "wx/icon.h" #include "wx/window.h" #include "wx/private/bmpbndl.h" #include #ifdef __WXOSX__ #include "wx/osx/private.h" #endif // ---------------------------------------------------------------------------- // private helpers // ---------------------------------------------------------------------------- namespace { // Simplest possible bundle implementation storing a collection of bitmaps class wxBitmapBundleImplSet : public wxBitmapBundleImpl { public: // The vector must not be empty, caller is supposed to have checked for it. explicit wxBitmapBundleImplSet(const wxVector& bitmaps) { Init(&bitmaps[0], bitmaps.size()); } // Convenience ctor from a single bitmap. explicit wxBitmapBundleImplSet(const wxBitmap& bitmap) { Init(&bitmap, 1); } ~wxBitmapBundleImplSet() { #ifdef __WXOSX__ if (m_nsImage) wxMacCocoaRelease(m_nsImage); #endif } virtual wxSize GetDefaultSize() const wxOVERRIDE; virtual wxSize GetPreferredSizeAtScale(double scale) const wxOVERRIDE; virtual wxBitmap GetBitmap(const wxSize& size) wxOVERRIDE; #ifdef __WXOSX__ virtual WXImage OSXGetImage() const wxOVERRIDE; private: mutable WXImage m_nsImage = NULL; #endif private: // Struct containing bitmap itself as well as a flag indicating whether we // generated it by rescaling the existing bitmap or not. struct Entry { // Create a new entry from the original bitmap. explicit Entry(const wxBitmap& bitmap_) : bitmap(bitmap_) { generated = false; } // Create a new entry of the given size by resizing the bitmap of an // existing one. Entry(const Entry& entry, const wxSize& size) : bitmap(entry.bitmap) { wxASSERT_MSG( !entry.generated, wxS("should use original bitmap") ); generated = true; wxBitmap::Rescale(bitmap, size); } wxBitmap bitmap; bool generated; }; // Comparator comparing entries by the bitmap size. struct BitmapSizeComparator { bool operator()(const Entry& entry1, const Entry& entry2) const { // We could compare the bitmaps areas too, but they're supposed to // all use different sizes anyhow, so keep things simple. return entry1.bitmap.GetHeight() < entry2.bitmap.GetHeight(); } }; typedef wxVector Entries; // All bitmaps sorted by size. // // Note that this vector is never empty. Entries m_entries; // Common implementation of all ctors. void Init(const wxBitmap* bitmaps, size_t n); wxDECLARE_NO_COPY_CLASS(wxBitmapBundleImplSet); }; } // anonymous namespace // ============================================================================ // wxBitmapBundleImplSet implementation // ============================================================================ void wxBitmapBundleImplSet::Init(const wxBitmap* bitmaps, size_t n) { m_entries.reserve(n); for ( size_t i = 0; i < n; ++i ) { const wxBitmap& bitmap = bitmaps[i]; wxASSERT_MSG( bitmap.IsOk(), wxS("all bundle bitmaps must be valid") ); m_entries.push_back(Entry(bitmap)); } std::sort(m_entries.begin(), m_entries.end(), BitmapSizeComparator()); // Should we check that all bitmaps really have unique sizes here? For now, // don't bother with this, but we might want to do it later if it really // turns out to be a problem in practice. } wxSize wxBitmapBundleImplSet::GetDefaultSize() const { // Default size is the size of the smallest bitmap in the bundle. 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 // vector size is small enough (< 10) for it not to matter in practice. const size_t n = m_entries.size(); size_t lastSmaller = 0; for ( size_t i = 0; i < n; ++i ) { const Entry& entry = m_entries[i]; const wxSize sizeThis = entry.bitmap.GetSize(); if ( sizeThis.y == size.y ) { // Exact match, just use it. return entry.bitmap; } if ( sizeThis.y < size.y ) { // Don't rescale this one, we prefer to downscale rather than // upscale as it results in better-looking bitmaps. lastSmaller = i; continue; } if ( sizeThis.y > size.y && !entry.generated ) { // We know that we don't have any exact match and we've found the // next bigger bitmap, so rescale it to the desired size. const Entry entryNew(entry, size); m_entries.insert(m_entries.begin() + lastSmaller + 1, entryNew); return entryNew.bitmap; } } // We only get here if the requested size is larger than the size of all // the bitmaps we have, in which case we have no choice but to upscale one // of the bitmaps, so find the largest available non-generated bitmap. for ( size_t i = n; n > 0; --i ) { const Entry& entry = m_entries[i - 1]; if ( !entry.generated ) { const Entry entryNew(entry, size); m_entries.push_back(entryNew); return entryNew.bitmap; } } // We should have at least one non-generated bitmap. wxFAIL_MSG( wxS("unreachable") ); return wxBitmap(); } #ifdef __WXOSX__ WXImage wxBitmapBundleImplSet::OSXGetImage() const { if ( m_nsImage == NULL ) { #if wxOSX_USE_COCOA m_nsImage = wxOSXImageFromBitmap(m_entries[0].bitmap); const size_t n = m_entries.size(); for ( size_t i = 1; i < n; ++i ) { const Entry& entry = m_entries[i]; wxOSXAddBitmapToImage(m_nsImage, entry.bitmap); } #else // TODO determine best bitmap for device scale factor, and use that // with wxOSXImageFromBitmap as on iOS there is only one bitmap in a UIImage #endif if ( m_nsImage ) wxMacCocoaRetain(m_nsImage); } return m_nsImage; } #endif // ============================================================================ // wxBitmapBundle implementation // ============================================================================ wxBitmapBundle::wxBitmapBundle() { } wxBitmapBundle::wxBitmapBundle(wxBitmapBundleImpl* impl) : m_impl(impl) { } wxBitmapBundle::wxBitmapBundle(const wxBitmap& bitmap) : m_impl(bitmap.IsOk() ? new wxBitmapBundleImplSet(bitmap) : NULL) { } wxBitmapBundle::wxBitmapBundle(const wxIcon& icon) : m_impl(icon.IsOk() ? new wxBitmapBundleImplSet(wxBitmap(icon)) : NULL) { } wxBitmapBundle::wxBitmapBundle(const wxImage& image) : m_impl(image.IsOk() ? new wxBitmapBundleImplSet(wxBitmap(image)) : NULL) { } wxBitmapBundle::wxBitmapBundle(const wxBitmapBundle& other) : m_impl(other.m_impl) { } wxBitmapBundle& wxBitmapBundle::operator=(const wxBitmapBundle& other) { // No need to check for self-assignment because m_impl already does. m_impl = other.m_impl; return *this; } wxBitmapBundle::~wxBitmapBundle() { } /* static */ wxBitmapBundle wxBitmapBundle::FromBitmaps(const wxVector& bitmaps) { if ( bitmaps.empty() ) return wxBitmapBundle(); return wxBitmapBundle(new wxBitmapBundleImplSet(bitmaps)); } /* static */ wxBitmapBundle wxBitmapBundle::FromImpl(wxBitmapBundleImpl* impl) { return wxBitmapBundle(impl); } // MSW has its own, actually working, version, in MSW-specific code. #if !defined( __WXMSW__ ) && !defined( __WXOSX__ ) /* static */ wxBitmapBundle wxBitmapBundle::FromResources(const wxString& WXUNUSED(name)) { wxFAIL_MSG ( "Loading bitmaps from resources not available on this platform, " "don't use this function and call wxBitmapBundle::FromBitmaps() " "instead." ); return wxBitmapBundle(); } #endif // !__WXMSW__ && !__WXOSX__ wxSize wxBitmapBundle::GetDefaultSize() const { if ( !m_impl ) return wxDefaultSize; 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 ) return wxBitmap(); return m_impl->GetBitmap(size == wxDefaultSize ? GetDefaultSize() : size); }