File: bmpbundle.cpp

package info (click to toggle)
wxpython4.0 4.2.3%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 221,752 kB
  • sloc: cpp: 962,555; python: 230,573; ansic: 170,731; makefile: 51,756; sh: 9,342; perl: 1,564; javascript: 584; php: 326; xml: 200
file content (514 lines) | stat: -rw-r--r-- 16,953 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
///////////////////////////////////////////////////////////////////////////////
// Name:        tests/graphics/bmpbundle.cpp
// Purpose:     wxBitmapBundle unit test
// Author:      Vadim Zeitlin
// Created:     2021-09-27
// Copyright:   (c) 2021 Vadim Zeitlin <vadim@wxwidgets.org>
///////////////////////////////////////////////////////////////////////////////

// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------

#include "testprec.h"

#include "wx/bmpbndl.h"

#include "wx/artprov.h"
#include "wx/dcmemory.h"

#include "asserthelper.h"

// ----------------------------------------------------------------------------
// tests
// ----------------------------------------------------------------------------

TEST_CASE("BitmapBundle::Create", "[bmpbundle]")
{
    wxBitmapBundle b;
    CHECK( !b.IsOk() );
    CHECK( b.GetDefaultSize() == wxDefaultSize );

    b = wxBitmap(16, 16);
    CHECK( b.IsOk() );
    CHECK( b.GetDefaultSize() == wxSize(16, 16) );
}

TEST_CASE("BitmapBundle::FromBitmaps", "[bmpbundle]")
{
    wxVector<wxBitmap> bitmaps;
    bitmaps.push_back(wxBitmap(16, 16));
    bitmaps.push_back(wxBitmap(24, 24));

    wxBitmapBundle b = wxBitmapBundle::FromBitmaps(bitmaps);
    REQUIRE( b.IsOk() );
    CHECK( b.GetDefaultSize() == wxSize(16, 16) );

    CHECK( b.GetBitmap(wxDefaultSize ).GetSize() == wxSize(16, 16) );
    CHECK( b.GetBitmap(wxSize(16, 16)).GetSize() == wxSize(16, 16) );
    CHECK( b.GetBitmap(wxSize(20, 20)).GetSize() == wxSize(20, 20) );
    CHECK( b.GetBitmap(wxSize(24, 24)).GetSize() == wxSize(24, 24) );
}

TEST_CASE("BitmapBundle::GetBitmap", "[bmpbundle]")
{
    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) );

    // Test for the special case when the requested size uses the same height
    // but not the same width.
    wxBitmap nonSquare(wxSize(51, 41));
    b = wxBitmapBundle::FromBitmap(nonSquare);

    const wxSize scaledSize(52, 41);
    CHECK( b.GetBitmap(scaledSize).GetSize() == scaledSize );

    // Test upscaling too.
    b = wxBitmapBundle::FromBitmap(wxBitmap(32, 32));
    CHECK( b.GetBitmap(wxSize(24, 24)).GetSize() == wxSize(24, 24) );
    CHECK( b.GetBitmap(wxSize(48, 48)).GetSize() == wxSize(48, 48) );
}

// 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 that empty bundle doesn't have any preferred size.
    wxBitmapBundle b;
    CHECK( b.GetPreferredBitmapSizeAtScale(1) == wxDefaultSize );

    const wxBitmap normal = MakeSolidBitmap(1.0);
    const wxBitmap middle = MakeSolidBitmap(1.5);
    const wxBitmap bigger = MakeSolidBitmap(2.0);


    // 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_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, 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) );


    // Another check to detect that the scale is computed correctly even when
    // rounding is involved.
    wxBitmap nonSquare(wxSize(51, 41));
    nonSquare.SetScaleFactor(1.5);
    b = wxBitmapBundle::FromBitmap(nonSquare);
    CHECK( b.GetPreferredBitmapSizeAtScale(1.5) == nonSquare.GetSize() );
}

#ifdef wxHAS_DPI_INDEPENDENT_PIXELS

TEST_CASE("BitmapBundle::Scaled", "[bmpbundle]")
{
    // Adding a bitmap with scale factor > 1 should create the bundle using the
    // scaled size as default size.
    wxBitmap scaled2x(64, 64);
    scaled2x.SetScaleFactor(2);
    CHECK( scaled2x.GetLogicalSize() == wxSize(32, 32) );

    wxBitmapBundle b(scaled2x);
    CHECK( b.GetDefaultSize() == wxSize(32, 32) );

    // Retrieving this bitmap back from the bundle should preserve its scale.
    scaled2x = b.GetBitmap(wxSize(64, 64));
    CHECK( scaled2x.GetSize() == wxSize(64, 64) );
    CHECK( scaled2x.GetScaleFactor() == 2 );

    // And retrieving the bitmap from the bundle should set scale factor for it
    // even if it hadn't originally been added with it.
    b = wxBitmapBundle::FromBitmaps(wxBitmap(32, 32), wxBitmap(64, 64));
    scaled2x = b.GetBitmap(wxSize(64, 64));
    CHECK( scaled2x.GetSize() == wxSize(64, 64) );
    CHECK( scaled2x.GetScaleFactor() == 2 );

    // Using scaled bitmaps when there is more than one of them is a bad idea
    // in general, as only physical size matters, but the default size should
    // still be the scaled size of the smallest one.
    b = wxBitmapBundle::FromBitmaps(scaled2x, wxBitmap(64, 64));
    CHECK( b.GetDefaultSize() == wxSize(32, 32) );
}

#endif // wxHAS_DPI_INDEPENDENT_PIXELS

#ifdef wxHAS_SVG

TEST_CASE("BitmapBundle::FromSVG", "[bmpbundle][svg]")
{
    static const char svg_data[] =
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>"
"<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">"
"<svg width=\"200\" height=\"200\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns=\"http://www.w3.org/2000/svg\">"
"<g>"
"<circle cx=\"100\" cy=\"100\" r=\"50\" fill=\"blue\"/>"
"</g>"
"</svg>"
    ;

    wxBitmapBundle b = wxBitmapBundle::FromSVG(svg_data, wxSize(20, 20));
    REQUIRE( b.IsOk() );
    CHECK( b.GetDefaultSize() == wxSize(20, 20) );

    CHECK( b.GetBitmap(wxSize(32, 32)).GetSize() == wxSize(32, 32) );

    // Check that not using XML header works too.
    const char* svg_tag_start = strstr(svg_data, "<svg");
    REQUIRE( svg_tag_start );

    b = wxBitmapBundle::FromSVG(svg_data, wxSize(20, 20));
    REQUIRE( b.IsOk() );
    CHECK( b.GetBitmap(wxSize(16, 16)).GetSize() == wxSize(16, 16) );
}

TEST_CASE("BitmapBundle::FromSVGFile", "[bmpbundle][svg][file]")
{
    const wxSize size(20, 20); // completely arbitrary

    CHECK( !wxBitmapBundle::FromSVGFile("horse.bmp", size).IsOk() );

    wxBitmapBundle b = wxBitmapBundle::FromSVGFile("horse.svg", size);
    REQUIRE( b.IsOk() );
    CHECK( b.GetDefaultSize() == size );
}

#endif // wxHAS_SVG

TEST_CASE("BitmapBundle::ArtProvider", "[bmpbundle][art]")
{
    // Check that creating a bogus bundle fails as expected.
    wxBitmapBundle b = wxArtProvider::GetBitmapBundle("bloordyblop");
    CHECK( !b.IsOk() );

    // And that creating a bundle using a standard icon works.
    const wxSize size(16, 16);
    b = wxArtProvider::GetBitmapBundle(wxART_INFORMATION, wxART_MENU, size);
    CHECK( b.IsOk() );
    CHECK( b.GetDefaultSize() == size );

#if wxUSE_ARTPROVIDER_TANGO
    // Tango art provider is supposed to use 16px for the default size of the
    // menu and button images and 24px for all the other ones, but we need to
    // choose the client kind for which the current platform doesn't define its
    // own default/fallback size to be able to test for it, i.e. this is the
    // client for which GetNativeSizeHint() of the native art provider returns
    // wxDefaultSize.
    const wxArtClient artClient =
#ifdef __WXMSW__
        wxART_TOOLBAR
#else
        wxART_LIST
#endif
        ;

    // We also need to use an image provided by Tango but not by the native art
    // provider, but here we can at least avoid the platform checks by using an
    // image not provided by any native providers.
    b = wxArtProvider::GetBitmapBundle(wxART_REFRESH, artClient);

    CHECK( b.IsOk() );
    CHECK( b.GetDefaultSize() == wxSize(24, 24) );
#endif // wxUSE_ARTPROVIDER_TANGO
}

// This test only makes sense for the ports that actually support scaled
// bitmaps, which is the case for the ports using real logical pixels (they
// have to support bitmap scale for things to work) and MSW, which doesn't, but
// still implements support for at least storing and retrieving bitmap scale in
// its wxBitmap.
#if defined(wxHAS_DPI_INDEPENDENT_PIXELS) || defined(__WXMSW__)

TEST_CASE("BitmapBundle::Scale", "[bmpbundle][scale]")
{
    // This is not a wxBitmapBundle test, strictly speaking, but check that
    // setting scale factor works correctly for bitmaps, as wxBitmapBundle does
    // this internally.
    wxBitmap bmp;
    bmp.CreateWithDIPSize(8, 8, 2);
#ifdef wxHAS_DPI_INDEPENDENT_PIXELS
    CHECK( bmp.GetLogicalSize() == wxSize(8, 8) );
#endif
    CHECK( bmp.GetDIPSize() == wxSize(8, 8) );
    CHECK( bmp.GetSize() == wxSize(16, 16) );
    CHECK( bmp.GetScaleFactor() == 2 );

    wxBitmap bmp2(bmp);
    bmp.SetScaleFactor(3);
    CHECK( bmp2.GetScaleFactor() == 2 );
    CHECK( bmp.GetScaleFactor() == 3 );

    // Check that creating bitmap bundle from a bitmap with a scale factor
    // works as expected.
    wxBitmapBundle b = bmp2;
    CHECK( b.GetDefaultSize() == wxSize(8, 8) );
}

#endif // ports with scaled bitmaps support

TEST_CASE("BitmapBundle::GetConsensusSize", "[bmpbundle]")
{
    // Just a trivial helper to make writing the tests below simpler.
    struct Bundles
    {
        wxVector<wxBitmapBundle> vec;

        void Add(int size)
        {
            vec.push_back(wxBitmapBundle::FromBitmap(wxSize(size, size)));
        }

        int GetConsensusSize(double scale) const
        {
            return wxBitmapBundle::GetConsensusSizeFor(scale, vec).y;
        }
    } bundles;

    // When there is a tie, a larger size is chosen by default.
    bundles.Add(16);
    bundles.Add(24);
    CHECK( bundles.GetConsensusSize(2) == 48 );

    // Breaking the tie results in the smaller size winning now.
    bundles.Add(16);
    CHECK( bundles.GetConsensusSize(2) == 32 );

    // Integer scaling factors should be preferred.
    CHECK( bundles.GetConsensusSize(1.5) == 16 );
}