Merge branch 'bmpbndl-scaling'

Improve wxBitmapBundle scaling logic by limiting it to integer scaling
factors and ensure it's consistent for files and MSW resources.

See #22481.
This commit is contained in:
Vadim Zeitlin
2022-06-04 18:36:50 +01:00
10 changed files with 628 additions and 194 deletions

View File

@@ -285,6 +285,24 @@ XRC format has been updated to allow specifying wxBitmapBundle with
multiple bitmaps or a single SVG image can be used.
Adapting Existing Code To High DPI {#high_dpi_existing_code}
==================================
Generally speaking, adding support for high DPI to the existing wxWidgets
programs involves doing at least the following:
1. Not using any hard-coded pixel values outside of `FromDIP()` (note that
this does _not_ apply to XRC).
2. Using wxBitmapBundle containing at least 2 (normal and high DPI) bitmaps
instead of wxBitmap and wxImageList when setting bitmaps.
3. Updating any custom art providers to override
wxArtProvider::CreateBitmapBundle() (and, of course, return multiple bitmaps
from it) instead of wxArtProvider::CreateBitmap().
4. Removing any calls to wxToolBar::SetToolBitmapSize() or, equivalently,
`<bitmapsize>` attributes from the XRC, as this forces unwanted scaling.
Platform-Specific Build Issues {#high_dpi_platform_specific}
==============================

View File

@@ -224,6 +224,29 @@ wxBitmapBundle wxBitmapBundle::FromImage(const wxImage& image)
class WXDLLIMPEXP_CORE wxBitmapBundleImpl : public wxRefCounter
{
protected:
// Standard implementation of GetPreferredBitmapSizeAtScale(): choose the
// scale closest to the given one from the available bitmap scales.
//
// If this function is used, GetNextAvailableScale() must be overridden!
wxSize DoGetPreferredSize(double scale) const;
// Helper for implementing GetBitmap(): if we need to upscale a bitmap,
// uses GetNextAvailableScale() to find the index of the best bitmap to
// use, where "best" is defined as "using scale which is a divisor of the
// given one", as upscaling by an integer factor is strongly preferable.
size_t GetIndexToUpscale(const wxSize& size) const;
// Override this function if DoGetPreferredSize() or GetIndexToUpscale() is
// used: it can use the provided parameter as an internal index, it's
// guaranteed to be 0 when calling this function for the first time. When
// there are no more scales, return 0.
//
// This function is not pure virtual because it doesn't need to be
// implemented if DoGetPreferredSize() is never used, but it will assert if
// it's called.
virtual double GetNextAvailableScale(size_t& i) const;
virtual ~wxBitmapBundleImpl();
public:

View File

@@ -442,7 +442,8 @@ public:
return GetDefaultSize()*scale;
... otherwise, an existing bitmap of the size closest to the
one above would need to be found and its size returned ...
one above would need to be found and its size returned,
possibly by letting DoGetPreferredSize() choose it ...
}
wxBitmap GetBitmap(const wxSize& size) wxOVERRIDE
@@ -488,6 +489,110 @@ public:
on demand and cache it.
*/
virtual wxBitmap GetBitmap(const wxSize& size) = 0;
protected:
/**
Helper for implementing GetPreferredBitmapSizeAtScale() in the derived
classes.
This function implements the standard algorithm used inside wxWidgets
itself and tries to find the scale closest to the given one, while also
trying to choose one of the available scales, to avoid actually
rescaling the bitmaps.
It relies on GetNextAvailableScale() to get information about the
available bitmaps, so that function must be overridden if this one is
used.
Typically this function is used in the derived classes implementation
to forward GetPreferredBitmapSizeAtScale() to it and when this is done,
GetBitmap() may also use GetIndexToUpscale() to choose the bitmap to
upscale if necessary:
@code
class MyCustomBitmapBundleImpl : public wxBitmapBundleImpl
{
public:
wxSize GetDefaultSize() const
{
return wxSize(32, 32);
}
wxSize GetPreferredBitmapSizeAtScale(double scale) const wxOVERRIDE
{
return DoGetPreferredSize(scale);
}
wxBitmap GetBitmap(const wxSize& size) wxOVERRIDE
{
// For consistency with GetNextAvailableScale(), we must have
// bitmap variants for 32, 48 and 64px sizes.
const wxSize availableSizes[] = { 32, 48, 64 };
if ( size.y <= 64 )
{
... get the bitmap from somewhere ...
}
else
{
size_t n = GetIndexToUpscale(size);
bitmap = ... get bitmap for availableSizes[n] ...;
wxBitmap::Rescale(bitmap, size);
}
return bitmap;
}
protected:
double GetNextAvailableScale(size_t& i) const wxOVERRIDE
{
const double availableScales[] = { 1, 1.5, 2, 0 };
// We can rely on not being called again once we return 0.
return availableScales[i++];
}
...
};
@endcode
@param scale The required scale, typically the same one as passed to
GetPreferredBitmapSizeAtScale().
@since 3.1.7
*/
wxSize DoGetPreferredSize(double scale) const;
/**
Return the index of the available scale most suitable to be upscaled to
the given size.
See DoGetPreferredSize() for an example of using this function.
@param size The required size, typically the same one as passed to
GetBitmap()
@since 3.1.7
*/
size_t GetIndexToUpscale(const wxSize& size) const;
/**
Return information about the available bitmaps.
Overriding this function is optional and only needs to be done if
either DoGetPreferredSize() or GetIndexToUpscale() are called. If you
do override it, this function must return the next available scale or
0.0 if there are no more.
The returned scales must be in ascending order and the first returned
scale, for the initial @a i value of 0, should be 1. The function must
change @a i, but the values of this index don't have to be consecutive
and it's only used by this function itself, the caller only initializes
it to 0 before the first call.
See DoGetPreferredSize() for an example of implementing this function.
@since 3.1.7
*/
virtual double GetNextAvailableScale(size_t& i) const;
};
/**

View File

@@ -882,6 +882,10 @@ public:
It is usually unnecessary to call this function, as the tools will
always be made big enough to fit the size of the bitmaps used in them.
Moreover, calling it may force wxToolBar to scale its images, even
using non-integer scaling factor, which will usually look bad, instead
of adapting the image size to the current DPI scaling in order to avoid
doing this.
If you do call it, it must be done before toolbar is Realize()'d.
@@ -894,6 +898,9 @@ public:
toolbar->Realize();
@endcode
Note that this example would scale bitmaps to 48 pixels when using 150%
DPI scaling, which wouldn't happen without calling SetToolBitmapSize().
@param size
The size of the bitmaps in the toolbar in logical pixels.

View File

@@ -149,12 +149,19 @@ namespace
class wxBitmapBundleImplArt : public wxBitmapBundleImpl
{
public:
wxBitmapBundleImplArt(const wxArtID& id,
wxBitmapBundleImplArt(const wxBitmap& bitmap,
const wxArtID& id,
const wxArtClient& client,
const wxSize& size)
const wxSize& sizeRequested)
: m_artId(id),
m_artClient(client),
m_sizeDefault(GetValidSize(id, client, size))
// The bitmap bundle must have the requested size if it was
// specified, but if it wasn't just use the (scale-independent)
// bitmap size.
m_sizeDefault(sizeRequested.IsFullySpecified()
? sizeRequested
: bitmap.GetDIPSize()),
m_bitmapScale(bitmap.GetScaleFactor())
{
}
@@ -165,8 +172,8 @@ public:
virtual wxSize GetPreferredBitmapSizeAtScale(double scale) const wxOVERRIDE
{
// We have no preferred sizes.
return m_sizeDefault*scale;
// Use the standard logic for integer-factor upscaling.
return DoGetPreferredSize(scale);
}
virtual wxBitmap GetBitmap(const wxSize& size) wxOVERRIDE
@@ -174,36 +181,24 @@ public:
return wxArtProvider::GetBitmap(m_artId, m_artClient, size);
}
private:
static wxSize GetValidSize(const wxArtID& id,
const wxArtClient& client,
const wxSize& size)
protected:
virtual double GetNextAvailableScale(size_t& i) const wxOVERRIDE
{
// If valid size is provided, just use it.
if ( size != wxDefaultSize )
return size;
// Otherwise, try to get the size we'd use without creating a bitmap
// immediately.
const wxSize sizeHint = wxArtProvider::GetSizeHint(client);
if ( sizeHint != wxDefaultSize )
return sizeHint;
// If we really have to, do create a bitmap just to get its size. Note
// we need the size in logical pixels here, it will be scaled later if
// necessary, so use GetDIPSize() and not GetSize().
const wxBitmap bitmap = wxArtProvider::GetBitmap(id, client);
if ( bitmap.IsOk() )
return bitmap.GetDIPSize();
// We really need some default size, so just return this hardcoded
// value if all else fails -- what else can we do.
return wxSize(16, 16);
// Unfortunately we don't know what bitmap sizes are available here as
// there is simply nothing in wxArtProvider API that returns this (and
// adding something to the API doesn't make sense as all this is only
// used for compatibility with the existing custom art providers -- new
// ones should just override CreateBitmapBundle() directly), so we only
// return the original bitmap scale, but hope that perhaps the provider
// will have other (e.g. x2) scales too, when our GetBitmap() is called.
return i++ ? 0.0 : m_bitmapScale;
}
private:
const wxArtID m_artId;
const wxArtClient m_artClient;
const wxSize m_sizeDefault;
const double m_bitmapScale;
wxDECLARE_NO_COPY_CLASS(wxBitmapBundleImplArt);
};
@@ -322,7 +317,11 @@ wxArtProvider::RescaleOrResizeIfNeeded(wxBitmap& bmp, const wxSize& sizeNeeded)
return;
#if wxUSE_IMAGE
if ((bmp_h <= sizeNeeded.x) && (bmp_w <= sizeNeeded.y))
// Allow upscaling by an integer factor: this looks not too horribly and is
// needed to use reasonably-sized bitmaps in the code not yet updated to
// use wxBitmapBundle but using custom art providers.
if ((bmp_w <= sizeNeeded.x) && (bmp_h <= sizeNeeded.y) &&
(sizeNeeded.x % bmp_w || sizeNeeded.y % bmp_h))
{
// the caller wants default size, which is larger than
// the image we have; to avoid degrading it visually by
@@ -332,7 +331,7 @@ wxArtProvider::RescaleOrResizeIfNeeded(wxBitmap& bmp, const wxSize& sizeNeeded)
img.Resize(sizeNeeded, offset);
bmp = wxBitmap(img);
}
else // scale (down or mixed, but not up)
else // scale (down or mixed, but not up, or at least not by an int factor)
#endif // wxUSE_IMAGE
{
wxBitmap::Rescale(bmp, sizeNeeded);
@@ -438,9 +437,12 @@ wxBitmapBundle wxArtProvider::GetBitmapBundle(const wxArtID& id,
// lower priority one: even if this means that the bitmap will be
// scaled, at least we'll be using the expected bitmap rather than
// potentially using a bitmap of a different style.
if ( provider->CreateBitmap(id, client, size).IsOk() )
const wxBitmap& bitmap = provider->CreateBitmap(id, client, size);
if ( bitmap.IsOk() )
{
bitmapbundle = wxBitmapBundle::FromImpl(new wxBitmapBundleImplArt(id, client, size));
bitmapbundle = wxBitmapBundle::FromImpl(
new wxBitmapBundleImplArt(bitmap, id, client, size)
);
break;
}
}

View File

@@ -26,6 +26,7 @@
#include "wx/icon.h"
#include "wx/iconbndl.h"
#include "wx/imaglist.h"
#include "wx/scopeguard.h"
#include "wx/window.h"
#include "wx/private/bmpbndl.h"
@@ -132,6 +133,9 @@ public:
virtual wxSize GetPreferredBitmapSizeAtScale(double scale) const wxOVERRIDE;
virtual wxBitmap GetBitmap(const wxSize& size) wxOVERRIDE;
protected:
virtual double GetNextAvailableScale(size_t& i) const wxOVERRIDE;
private:
// Struct containing bitmap itself as well as a flag indicating whether we
// generated it by rescaling the existing bitmap or not.
@@ -235,52 +239,24 @@ wxSize wxBitmapBundleImplSet::GetDefaultSize() const
return m_sizeDefault;
}
wxSize wxBitmapBundleImplSet::GetPreferredBitmapSizeAtScale(double scale) const
double wxBitmapBundleImplSet::GetNextAvailableScale(size_t& i) 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 )
while ( i < m_entries.size() )
{
const wxSize sizeThis = m_entries[i].bitmap.GetSize();
const Entry& entry = m_entries[i++];
// Keep looking for the exact match which we still can hope to find
// while the current bitmap is smaller.
if ( sizeThis.y < sizeTarget.y )
if ( entry.generated )
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;
return static_cast<double>(entry.bitmap.GetSize().y) / GetDefaultSize().y;
}
// 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();
return 0.0;
}
// 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;
wxSize wxBitmapBundleImplSet::GetPreferredBitmapSizeAtScale(double scale) const
{
return DoGetPreferredSize(scale);
}
wxBitmap wxBitmapBundleImplSet::GetBitmap(const wxSize& size)
@@ -322,24 +298,14 @@ wxBitmap wxBitmapBundleImplSet::GetBitmap(const wxSize& size)
// 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);
// of the bitmaps, so find the most appropriate one for doing it.
const size_t i = GetIndexToUpscale(size);
m_entries.push_back(entryNew);
const Entry entryNew(m_entries[i], size);
return entryNew.bitmap;
}
}
m_entries.push_back(entryNew);
// We should have at least one non-generated bitmap.
wxFAIL_MSG( wxS("unreachable") );
return wxBitmap();
return entryNew.bitmap;
}
#ifdef __WXOSX__
@@ -715,6 +681,148 @@ wxBitmapBundle::CreateImageList(wxWindow* win,
// wxBitmapBundleImpl implementation
// ============================================================================
double
wxBitmapBundleImpl::GetNextAvailableScale(size_t& WXUNUSED(i)) const
{
wxFAIL_MSG( wxS("must be overridden if called") );
return 0.0;
}
wxSize
wxBitmapBundleImpl::DoGetPreferredSize(double scaleTarget) const
{
double scaleBest = 0.0;
double scaleLast = 0.0;
for ( size_t i = 0;; )
{
const double scaleThis = GetNextAvailableScale(i);
if ( scaleThis == 0.0 )
{
// We only get here if the target scale is bigger than all the
// available scales, in which case we have no choice but to use the
// biggest bitmap, which corresponds to the last used scale that we
// should have by now.
wxASSERT_MSG( scaleLast != 0.0, "must have some available scales" );
// But check how far is it from the requested scale: if it's more than
// 1.5 times larger, we should still scale it, notably to ensure that
// bitmaps of standard size are scaled when 2x DPI scaling is used.
if ( scaleTarget > 1.5*scaleLast )
{
// However scaling by non-integer scales doesn't work well at
// all, so try to find a bitmap that we may rescale by an
// integer factor.
//
// Note that this is similar to GetIndexToUpscale(), but we
// don't want to fall back on the largest bitmap here, so we
// can't reuse it.
//
// Also, while we reenter GetNextAvailableScale() here, it
// doesn't matter because we're not going to continue using it
// in the outer loop any more.
for ( i = 0;; )
{
const double scale = GetNextAvailableScale(i);
if ( scale == 0.0 )
break;
const double factor = scaleTarget / scale;
if ( wxRound(factor) == factor )
{
scaleBest = scaleTarget;
// We don't need to keep going: if there is a bigger
// bitmap which can be scaled using an integer factor
// to the target scale, our GetIndexToUpscale() will
// find it, we don't need to do it here.
break;
}
}
// If none of the bitmaps can be upscaled by an integer factor,
// round the target scale itself, as we can be sure to be able
// to scale at least the base bitmap to it using an integer
// factor then.
if ( scaleBest == 0.0 )
scaleBest = wxRound(scaleTarget);
}
else // Target scale is not much greater than the biggest one we have.
{
scaleBest = scaleLast;
}
break;
}
// Ensure we remember the last used scale value.
wxON_BLOCK_EXIT_SET(scaleLast, scaleThis);
// Keep looking for the exact match which we still can hope to find
// while the current bitmap is smaller.
if ( scaleThis < scaleTarget )
continue;
// If we've found the exact match, just return it.
if ( scaleThis == scaleTarget )
{
scaleBest = scaleThis;
break;
}
// We've found the closest bigger bitmap.
// If there is no smaller bitmap, we have no choice but to use this one.
if ( scaleLast == 0.0 )
{
scaleBest = scaleThis;
break;
}
// 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 smaller size as it's arguably better to use
// slightly smaller bitmaps than too big ones.
scaleBest = scaleThis - scaleTarget < scaleTarget - scaleLast
? scaleThis
: scaleLast;
break;
}
return GetDefaultSize()*scaleBest;
}
size_t wxBitmapBundleImpl::GetIndexToUpscale(const wxSize& size) const
{
// Our best hope is to find a scale dividing the given one evenly.
size_t indexBest = (size_t)-1;
// In the worst case, we will use the largest index, as it should hopefully
// result in the least bad results.
size_t indexLast = 0;
const wxSize sizeDef = GetDefaultSize();
for ( size_t i = 0;; )
{
// Save it before it's updated by GetNextAvailableScale().
size_t indexPrev = i;
const double scaleThis = GetNextAvailableScale(i);
if ( scaleThis == 0.0 )
break;
// Only update it now, knowing that this index could have been used.
indexLast = indexPrev;
const double scale = size.y / (sizeDef.y*scaleThis);
if (wxRound(scale) == scale)
indexBest = indexLast;
}
return indexBest != (size_t)-1 ? indexBest : indexLast;
}
wxBitmapBundleImpl::~wxBitmapBundleImpl()
{
#ifdef __WXOSX__

View File

@@ -306,22 +306,7 @@ wxBitmap wxGTK2ArtProvider::CreateBitmap(const wxArtID& id,
if (stocksize == GTK_ICON_SIZE_INVALID)
stocksize = GTK_ICON_SIZE_BUTTON;
GdkPixbuf *pixbuf = CreateGtkIcon(stockid.utf8_str(), stocksize, size);
if (pixbuf && size != wxDefaultSize &&
(size.x != gdk_pixbuf_get_width(pixbuf) ||
size.y != gdk_pixbuf_get_height(pixbuf)))
{
GdkPixbuf *p2 = gdk_pixbuf_scale_simple(pixbuf, size.x, size.y,
GDK_INTERP_BILINEAR);
if (p2)
{
g_object_unref (pixbuf);
pixbuf = p2;
}
}
return wxBitmap(pixbuf);
return wxBitmap(CreateGtkIcon(stockid.utf8_str(), stocksize, size));
}
wxIconBundle

View File

@@ -203,28 +203,6 @@ protected:
const wxSize& size) wxOVERRIDE;
};
static wxBitmap CreateFromStdIcon(const char *iconName,
const wxArtClient& client)
{
wxIcon icon(iconName);
wxBitmap bmp;
bmp.CopyFromIcon(icon);
// The standard native message box icons are in message box size (32x32).
// If they are requested in any size other than the default or message
// box size, they must be rescaled first.
if ( client != wxART_MESSAGE_BOX && client != wxART_OTHER )
{
const wxSize size = wxArtProvider::GetNativeSizeHint(client);
if ( size != wxDefaultSize )
{
wxBitmap::Rescale(bmp, size);
}
}
return bmp;
}
wxBitmap wxWindowsArtProvider::CreateBitmap(const wxArtID& id,
const wxArtClient& client,
const wxSize& size)
@@ -251,14 +229,7 @@ wxBitmap wxWindowsArtProvider::CreateBitmap(const wxArtID& id,
bitmap = MSWGetBitmapFromIconLocation(sii.szPath, sii.iIcon,
sizeNeeded);
if ( bitmap.IsOk() )
{
if ( bitmap.GetSize() != sizeNeeded )
{
wxBitmap::Rescale(bitmap, sizeNeeded);
}
return bitmap;
}
}
}
#endif // wxHAS_SHGetStockIconInfo
@@ -303,7 +274,7 @@ wxBitmap wxWindowsArtProvider::CreateBitmap(const wxArtID& id,
name = "wxICON_QUESTION";
if ( name )
return CreateFromStdIcon(name, client);
return wxIcon(name);
}
// for anything else, fall back to generic provider:

View File

@@ -144,6 +144,9 @@ public:
virtual wxSize GetPreferredBitmapSizeAtScale(double scale) const wxOVERRIDE;
virtual wxBitmap GetBitmap(const wxSize& size) wxOVERRIDE;
protected:
virtual double GetNextAvailableScale(size_t& i) const wxOVERRIDE;
private:
// Load the bitmap from the given resource and add it m_bitmaps, after
// rescaling it to the given size if necessary, i.e. if the needed size is
@@ -189,50 +192,21 @@ wxSize wxBitmapBundleImplRC::GetDefaultSize() const
return m_bitmaps[0].GetSize();
}
double wxBitmapBundleImplRC::GetNextAvailableScale(size_t& i) const
{
return i < m_resourceInfos.size() ? m_resourceInfos[i++].scale : 0.0;
}
wxSize wxBitmapBundleImplRC::GetPreferredBitmapSizeAtScale(double scale) const
{
// Optimistically assume we're going to use this exact scale by default.
double scalePreferred = scale;
for ( size_t i = 0; ; ++i )
const size_t n = m_resourceInfos.size();
wxVector<double> scales(n);
for ( size_t i = 0; i < n; ++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;
scales[i] = m_resourceInfos[i].scale;
}
return GetDefaultSize()*scalePreferred;
return DoGetPreferredSize(scale);
}
wxBitmap
@@ -303,17 +277,19 @@ wxBitmap wxBitmapBundleImplRC::GetBitmap(const wxSize& size)
const wxSize sizeThis = sizeDef*info.scale;
// Use this bitmap if it's the first one bigger than the requested size
// or if it's the last item as in this case we're not going to find any
// bitmap bigger than the given one anyhow and we don't have any choice
// but to upscale the largest one we have.
if ( sizeThis.y >= size.y || i + 1 == m_resourceInfos.size() )
// Use this bitmap if it's the first one bigger than the requested size.
if ( sizeThis.y >= size.y )
return AddBitmap(info, sizeThis, size);
}
wxFAIL_MSG( wxS("unreachable") );
// We have to upscale some bitmap because we don't have any bitmaps larger
// than the requested size. Try to find one which can be upscaled using an
// integer factor.
const size_t i = GetIndexToUpscale(size);
return wxBitmap();
const ResourceInfo& info = m_resourceInfos[i];
return AddBitmap(info, sizeDef*info.scale, size);
}
// ============================================================================

View File

@@ -15,6 +15,7 @@
#include "wx/bmpbndl.h"
#include "wx/artprov.h"
#include "wx/dcmemory.h"
#include "asserthelper.h"
@@ -49,30 +50,268 @@ TEST_CASE("BitmapBundle::FromBitmaps", "[bmpbundle]")
CHECK( b.GetBitmap(wxSize(24, 24)).GetSize() == wxSize(24, 24) );
}
TEST_CASE("BitmapBundle::GetBitmap", "[bmpbundle]")
{
const wxBitmapBundle b = wxBitmapBundle::FromBitmap(wxBitmap(16, 16));
CHECK( b.GetBitmap(wxSize(16, 16)).GetSize() == wxSize(16, 16) );
CHECK( b.GetBitmap(wxSize(32, 32)).GetSize() == wxSize(32, 32) );
CHECK( b.GetBitmap(wxSize(24, 24)).GetSize() == wxSize(24, 24) );
}
// Helper functions for the test below.
namespace
{
// Default size here doesn't really matter.
const wxSize BITMAP_SIZE(16, 16);
// The choice of colours here is arbitrary too, but they need to be all
// different to allow identifying which bitmap got scaled.
struct ColourAtScale
{
double scale;
wxUint32 rgb;
};
const ColourAtScale colours[] =
{
{ 1.0, 0x000000ff },
{ 1.5, 0x0000ff00 },
{ 2.0, 0x00ff0000 },
};
// Return the colour used for the (original) bitmap at the given scale.
wxColour GetColourForScale(double scale)
{
wxColour col;
for ( size_t n = 0; n < WXSIZEOF(colours); ++n )
{
if ( colours[n].scale == scale )
{
col.SetRGB(colours[n].rgb);
return col;
}
}
wxFAIL_MSG("no colour for this scale");
return col;
}
double GetScaleFromColour(const wxColour& col)
{
const wxUint32 rgb = col.GetRGB();
for ( size_t n = 0; n < WXSIZEOF(colours); ++n )
{
if ( colours[n].rgb == rgb )
return colours[n].scale;
}
wxFAIL_MSG(wxString::Format("no scale for colour %s", col.GetAsString()));
return 0.0;
}
double SizeToScale(const wxSize& size)
{
return static_cast<double>(size.y) / BITMAP_SIZE.y;
}
wxBitmap MakeSolidBitmap(double scale)
{
wxBitmap bmp(BITMAP_SIZE*scale);
wxMemoryDC dc(bmp);
dc.SetBackground(GetColourForScale(scale));
dc.Clear();
return bmp;
}
wxColour GetBitmapColour(const wxBitmap& bmp)
{
const wxImage img = bmp.ConvertToImage();
// We just assume the bitmap is solid colour, we could check it, but it
// doesn't seem really useful to do it.
return wxColour(img.GetRed(0, 0), img.GetGreen(0, 0), img.GetBlue(0, 0));
}
// This struct exists just to allow using it conveniently in CHECK_THAT().
struct BitmapAtScale
{
BitmapAtScale(const wxBitmapBundle& b, double scale)
: size(b.GetPreferredBitmapSizeAtScale(scale)),
bitmap(b.GetBitmap(size))
{
}
const wxSize size;
const wxBitmap bitmap;
};
class BitmapAtScaleMatcher : public Catch::MatcherBase<BitmapAtScale>
{
public:
explicit BitmapAtScaleMatcher(double scale, double scaleOrig)
: m_scale(scale),
m_scaleOrig(scaleOrig)
{
}
bool match(const BitmapAtScale& bitmapAtScale) const wxOVERRIDE
{
const wxBitmap& bmp = bitmapAtScale.bitmap;
if ( SizeToScale(bitmapAtScale.size) != m_scale ||
SizeToScale(bmp.GetSize()) != m_scale )
{
m_diffDesc.Printf("should have scale %.1f", m_scale);
}
if ( GetBitmapColour(bmp) != GetColourForScale(m_scaleOrig) )
{
if ( m_diffDesc.empty() )
m_diffDesc = "should be ";
else
m_diffDesc += " and be ";
m_diffDesc += wxString::Format("created from x%.1f", m_scaleOrig);
}
return m_diffDesc.empty();
}
std::string describe() const wxOVERRIDE
{
return m_diffDesc.utf8_string();
}
private:
const double m_scale;
const double m_scaleOrig;
mutable wxString m_diffDesc;
};
// The first parameter here determines the size of the expected bitmap and the
// second one, which defaults to the first one if it's not specified, the size
// of the bitmap which must have been scaled to create the bitmap of the right
// size.
BitmapAtScaleMatcher SameAs(double scale, double scaleOrig = 0.0)
{
if ( scaleOrig == 0.0 )
scaleOrig = scale;
return BitmapAtScaleMatcher(scale, scaleOrig);
}
} // anonymous namespace
namespace Catch
{
template <>
struct StringMaker<BitmapAtScale>
{
static std::string convert(const BitmapAtScale& bitmapAtScale)
{
const wxBitmap& bmp = bitmapAtScale.bitmap;
wxString scaleError;
if ( bmp.GetSize() != bitmapAtScale.size )
{
scaleError.Printf(" (DIFFERENT from expected %.1f)",
SizeToScale(bitmapAtScale.size));
}
return wxString::Format
(
"x%.1f bitmap%s created from x%.1f",
SizeToScale(bmp.GetSize()),
scaleError,
GetScaleFromColour(GetBitmapColour(bmp))
).utf8_string();
}
};
}
TEST_CASE("BitmapBundle::GetPreferredSize", "[bmpbundle]")
{
CHECK( wxBitmapBundle().GetPreferredBitmapSizeAtScale(1) == wxDefaultSize );
// Check that empty bundle doesn't have any preferred size.
wxBitmapBundle b;
CHECK( b.GetPreferredBitmapSizeAtScale(1) == wxDefaultSize );
const wxSize normal(32, 32);
const wxSize bigger(64, 64);
const wxBitmap normal = MakeSolidBitmap(1.0);
const wxBitmap middle = MakeSolidBitmap(1.5);
const wxBitmap bigger = MakeSolidBitmap(2.0);
const wxBitmapBundle
b = wxBitmapBundle::FromBitmaps(wxBitmap(normal), wxBitmap(bigger));
// Then check what happens if there is only a single bitmap.
b = wxBitmapBundle::FromBitmap(normal);
// We should avoid scaling as long as the size is close enough to the
// actual bitmap size.
CHECK_THAT( BitmapAtScale(b, 0 ), SameAs(1) );
CHECK_THAT( BitmapAtScale(b, 1 ), SameAs(1) );
CHECK_THAT( BitmapAtScale(b, 1.25), SameAs(1) );
CHECK_THAT( BitmapAtScale(b, 1.4 ), SameAs(1) );
CHECK_THAT( BitmapAtScale(b, 1.5 ), SameAs(1) );
// Once it becomes too big, we're going to need to scale, but we should be
// scaling by an integer factor.
CHECK_THAT( BitmapAtScale(b, 1.75), SameAs(2, 1) );
CHECK_THAT( BitmapAtScale(b, 2 ), SameAs(2, 1) );
CHECK_THAT( BitmapAtScale(b, 2.25), SameAs(2, 1) );
CHECK_THAT( BitmapAtScale(b, 2.5 ), SameAs(3, 1) );
// Now check what happens when there is also a double size bitmap.
b = wxBitmapBundle::FromBitmaps(normal, bigger);
// Check that the existing bitmaps are used without scaling for most of the
// typical scaling values.
CHECK( b.GetPreferredBitmapSizeAtScale(0 ) == normal );
CHECK( b.GetPreferredBitmapSizeAtScale(1 ) == normal );
CHECK( b.GetPreferredBitmapSizeAtScale(1.25) == normal );
CHECK( b.GetPreferredBitmapSizeAtScale(1.4 ) == normal );
CHECK( b.GetPreferredBitmapSizeAtScale(1.5 ) == bigger );
CHECK( b.GetPreferredBitmapSizeAtScale(1.75) == bigger );
CHECK( b.GetPreferredBitmapSizeAtScale(2 ) == bigger );
CHECK( b.GetPreferredBitmapSizeAtScale(2.5 ) == bigger );
CHECK_THAT( BitmapAtScale(b, 0 ), SameAs(1) );
CHECK_THAT( BitmapAtScale(b, 1 ), SameAs(1) );
CHECK_THAT( BitmapAtScale(b, 1.25), SameAs(1) );
CHECK_THAT( BitmapAtScale(b, 1.4 ), SameAs(1) );
CHECK_THAT( BitmapAtScale(b, 1.5 ), SameAs(1) );
CHECK_THAT( BitmapAtScale(b, 1.75), SameAs(2) );
CHECK_THAT( BitmapAtScale(b, 2 ), SameAs(2) );
CHECK_THAT( BitmapAtScale(b, 2.5 ), SameAs(2) );
CHECK_THAT( BitmapAtScale(b, 3 ), SameAs(2) );
// This scale is too big to use any of the existing bitmaps, so they will
// be scaled.
CHECK( b.GetPreferredBitmapSizeAtScale(3 ) == 3*normal );
// be scaled, but use integer factors and, importantly, scale the correct
// bitmap using them: we need to scale the small bitmap by a factor of 3,
// rather than scaling the larger bitmap by a factor of 1.5 here, but we
// must also scale the larger one by a factor of 2 rather than scaling the
// small one by a factor of 4.
CHECK_THAT( BitmapAtScale(b, 3.33), SameAs(3, 1) );
CHECK_THAT( BitmapAtScale(b, 4 ), SameAs(4, 2) );
CHECK_THAT( BitmapAtScale(b, 5 ), SameAs(5, 1) );
// Finally check that things work as expected when we have 3 versions.
wxVector<wxBitmap> bitmaps;
bitmaps.push_back(normal);
bitmaps.push_back(middle);
bitmaps.push_back(bigger);
b = wxBitmapBundle::FromBitmaps(bitmaps);
CHECK_THAT( BitmapAtScale(b, 0 ), SameAs(1.0) );
CHECK_THAT( BitmapAtScale(b, 1 ), SameAs(1.0) );
CHECK_THAT( BitmapAtScale(b, 1.25), SameAs(1.0) );
CHECK_THAT( BitmapAtScale(b, 1.4 ), SameAs(1.5) );
CHECK_THAT( BitmapAtScale(b, 1.5 ), SameAs(1.5) );
CHECK_THAT( BitmapAtScale(b, 1.75), SameAs(1.5) );
CHECK_THAT( BitmapAtScale(b, 2 ), SameAs(2.0) );
CHECK_THAT( BitmapAtScale(b, 2.5 ), SameAs(2.0) );
CHECK_THAT( BitmapAtScale(b, 3 ), SameAs(2.0) );
CHECK_THAT( BitmapAtScale(b, 3.33), SameAs(3.0, 1.5) );
CHECK_THAT( BitmapAtScale(b, 4.25), SameAs(4.0, 2.0) );
CHECK_THAT( BitmapAtScale(b, 4.50), SameAs(4.5, 1.5) );
CHECK_THAT( BitmapAtScale(b, 5 ), SameAs(5.0, 1.0) );
}
#ifdef wxHAS_DPI_INDEPENDENT_PIXELS