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:
@@ -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}
|
||||
==============================
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,25 +298,15 @@ 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);
|
||||
|
||||
const Entry entryNew(m_entries[i], 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()
|
||||
@@ -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__
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,16 +229,9 @@ 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:
|
||||
|
||||
@@ -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;
|
||||
scales[i] = m_resourceInfos[i].scale;
|
||||
}
|
||||
|
||||
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;
|
||||
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);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user