Files
wxWidgets/src/common/bmpbndl.cpp
Vadim Zeitlin a81e0d83c1 Use wxBitmap::GetLogicalXXX() instead of GetScaledXXX()
Replace old functions with the new ones in the library code itself.

Note that wxSTC and wxRichText still use GetScaledXXX(), but they're
different functions that might need to be renamed/dealt with separately.
2022-01-22 18:57:31 +00:00

599 lines
17 KiB
C++

///////////////////////////////////////////////////////////////////////////////
// Name: src/common/bmpbndl.cpp
// Purpose: Common methods of wxBitmapBundle class.
// Author: Vadim Zeitlin
// Created: 2021-09-22
// Copyright: (c) 2021 Vadim Zeitlin <vadim@wxwidgets.org>
// 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/filename.h"
#include "wx/icon.h"
#include "wx/imaglist.h"
#include "wx/window.h"
#include "wx/private/bmpbndl.h"
#include <algorithm>
#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<wxBitmap>& bitmaps)
{
Init(&bitmaps[0], bitmaps.size());
}
// Convenience ctor from a single bitmap.
explicit wxBitmapBundleImplSet(const wxBitmap& bitmap)
{
Init(&bitmap, 1);
}
~wxBitmapBundleImplSet()
{
}
virtual wxSize GetDefaultSize() const wxOVERRIDE;
virtual wxSize GetPreferredSizeAtScale(double scale) const wxOVERRIDE;
virtual wxBitmap GetBitmap(const wxSize& size) wxOVERRIDE;
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<Entry> Entries;
// All bitmaps sorted by size.
//
// Note that this vector is never empty.
Entries m_entries;
// The size of the bitmap at the default size.
//
// Note that it may be different from the size of the first entry if we
// only have high resolution bitmap and no bitmap for 100% DPI.
wxSize m_sizeDefault;
// Common implementation of all ctors.
void Init(const wxBitmap* bitmaps, size_t n);
#ifdef __WXOSX__
void OSXCreateNSImage();
#endif
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());
// This is not normally the case, but it could happen that even the
// smallest bitmap has scale factor > 1, so use its scaled size (this can
// notably be the case when there is only a single high resolution bitmap
// provided, e.g. in the code predating wxBitmapBundle introduction but now
// using it due to implicit conversion to it from wxBitmap).
m_sizeDefault = m_entries[0].bitmap.GetLogicalSize();
// 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.
#ifdef __WXOSX__
OSXCreateNSImage();
#endif
}
wxSize wxBitmapBundleImplSet::GetDefaultSize() const
{
return m_sizeDefault;
}
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.
const wxSize sizeMax = m_entries.back().bitmap.GetSize();
// But check how far is it from the requested scale: if it's more than 1.5
// times smaller, we should still scale it, notably to ensure that bitmaps
// of standard size are scaled when 2x DPI scaling is used.
return static_cast<double>(sizeTarget.y) / sizeMax.y >= 1.5
? sizeTarget
: sizeMax;
}
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__
void wxBitmapBundleImplSet::OSXCreateNSImage()
{
WXImage image = NULL;
#if wxOSX_USE_COCOA
image = 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(image, entry.bitmap);
}
#else
image = wxOSXImageFromBitmap(m_entries[0].bitmap);
// 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 ( image )
wxOSXSetImageForBundleImpl(this, image);
}
#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<wxBitmap>& 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__
wxBitmapBundle wxBitmapBundle::FromFiles(const wxString& filename)
{
wxFileName fn(filename);
return FromFiles(fn.GetPath(wxPATH_GET_VOLUME), fn.GetName(), fn.GetExt());
}
#if !defined( __WXOSX__ )
/* static */
wxBitmapBundle wxBitmapBundle::FromFiles(const wxString& path, const wxString& filename, const wxString& extension)
{
wxVector<wxBitmap> bitmaps;
wxFileName fn(path, filename, extension);
wxString ext = extension.Lower();
for ( int dpiFactor = 1 ; dpiFactor <= 2 ; ++dpiFactor)
{
if ( dpiFactor == 1 )
fn.SetName(filename);
else
fn.SetName(wxString::Format("%s@%dx", filename, dpiFactor));
if ( !fn.FileExists() && dpiFactor != 1 )
{
// try alternate naming scheme
fn.SetName(wxString::Format("%s_%dx", filename, dpiFactor));
}
if ( fn.FileExists() )
{
wxBitmap bmp(fn.GetFullPath(), wxBITMAP_TYPE_ANY);
if ( bmp.IsOk() )
{
bitmaps.push_back(bmp);
}
}
}
return wxBitmapBundle::FromBitmaps(bitmaps);
}
#endif
wxSize wxBitmapBundle::GetDefaultSize() const
{
if ( !m_impl )
return wxDefaultSize;
return m_impl->GetDefaultSize();
}
wxSize wxBitmapBundle::GetPreferredSizeFor(const 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();
const wxSize sizeDef = GetDefaultSize();
wxBitmap bmp = m_impl->GetBitmap(size == wxDefaultSize ? sizeDef : size);
// Ensure that the returned bitmap uses the scale factor such that it takes
// the same space, in logical pixels, as the bitmap in the default size.
if ( size != wxDefaultSize )
bmp.SetScaleFactor(static_cast<double>(size.y)/sizeDef.y);
return bmp;
}
wxBitmap wxBitmapBundle::GetBitmapFor(const wxWindow* window) const
{
return GetBitmap(GetPreferredSizeFor(window));
}
namespace
{
// Struct containing the number of tools preferring to use the given size.
struct SizePrefWithCount
{
SizePrefWithCount() : count(0) { }
wxSize size;
int count;
};
typedef wxVector<SizePrefWithCount> 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
/* static */
wxSize
wxBitmapBundle::GetConsensusSizeFor(wxWindow* win,
const wxVector<wxBitmapBundle>& bundles,
const wxSize& sizeDefault)
{
const double scale = win->GetDPIScaleFactor();
const wxSize sizeIdeal = sizeDefault*scale;
// 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;
for ( size_t n = 0; n < bundles.size(); ++n )
{
RecordSizePref(prefs, bundles[n].GetPreferredSizeAtScale(scale));
}
// 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 != sizeIdeal )
{
if ( sizeThis == sizeIdeal )
{
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;
}
}
}
}
return sizePreferred;
}
/* static */
wxImageList*
wxBitmapBundle::CreateImageList(wxWindow* win,
const wxVector<wxBitmapBundle>& bundles)
{
wxCHECK_MSG( win, NULL, "must have a valid window" );
wxCHECK_MSG( !bundles.empty(), NULL, "should have some images" );
// We arbitrarily choose the default size of the first bundle as the
// default size for the image list too, as it's not clear what else could
// we do here. Note that this size is only used to break the tie in case
// the same number of bundles prefer two different sizes, so it's not going
// to matter at all in most cases.
wxSize size = GetConsensusSizeFor(win, bundles, bundles[0].GetDefaultSize());
// wxImageList wants the logical size for the platforms where logical and
// physical pixels are different.
size /= win->GetContentScaleFactor();
wxImageList* const iml = new wxImageList(size.x, size.y);
for ( size_t n = 0; n < bundles.size(); ++n )
{
iml->Add(bundles[n].GetBitmap(size));
}
return iml;
}
// ============================================================================
// wxBitmapBundleImpl implementation
// ============================================================================
wxBitmapBundleImpl::~wxBitmapBundleImpl()
{
#ifdef __WXOSX__
wxOSXBundleImplDestroyed(this);
#endif
}