wxwidgets/tests/image/image.cpp
Vadim Zeitlin c2e5749443 Fix crash when reading truncated GIF image
Add a unit test case checking that we don't crash any longer and still
read the image meta data correctly, even if not the image itself.

Also add another check for EOF and possibly invalid "bits" value in
another place where we were not checking for it when reading from the
stream.

Closes #23409.

Co-authored-by: PB <PBfordev@gmail.com>
2023-07-02 18:06:03 +01:00

2456 lines
75 KiB
C++

///////////////////////////////////////////////////////////////////////////////
// Name: tests/image/image.cpp
// Purpose: Test wxImage
// Author: Francesco Montorsi
// Created: 2009-05-31
// Copyright: (c) 2009 Francesco Montorsi
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------
#include "testprec.h"
#if wxUSE_IMAGE
#ifndef WX_PRECOMP
#endif // WX_PRECOMP
#include "wx/anidecod.h" // wxImageArray
#include "wx/bitmap.h"
#include "wx/cursor.h"
#include "wx/icon.h"
#include "wx/palette.h"
#include "wx/url.h"
#include "wx/log.h"
#include "wx/mstream.h"
#include "wx/zstream.h"
#include "wx/wfstream.h"
#include "wx/clipbrd.h"
#include "wx/dataobj.h"
// Check if we can use wxDIB::ConvertToBitmap(), which only exists for MSW and
// which assumes the target is little-endian (matching the file format)
#if defined(__WXMSW__) && wxUSE_WXDIB && wxBYTE_ORDER == wxLITTLE_ENDIAN
#define CAN_LOAD_BITMAP_DIRECTLY
#include "wx/msw/dib.h"
#endif
#include "testimage.h"
#include <memory>
#define CHECK_EQUAL_COLOUR_RGB(c1, c2) \
CHECK( (int)c1.Red() == (int)c2.Red() ); \
CHECK( (int)c1.Green() == (int)c2.Green() ); \
CHECK( (int)c1.Blue() == (int)c2.Blue() )
#define CHECK_EQUAL_COLOUR_RGBA(c1, c2) \
CHECK( (int)c1.Red() == (int)c2.Red() ); \
CHECK( (int)c1.Green() == (int)c2.Green() ); \
CHECK( (int)c1.Blue() == (int)c2.Blue() ); \
CHECK( (int)c1.Alpha() == (int)c2.Alpha() )
struct testData {
const char* file;
wxBitmapType type;
unsigned bitDepth;
} g_testfiles[] =
{
{ "horse.ico", wxBITMAP_TYPE_ICO, 4 },
{ "horse.xpm", wxBITMAP_TYPE_XPM, 8 },
{ "horse.png", wxBITMAP_TYPE_PNG, 24 },
{ "horse.ani", wxBITMAP_TYPE_ANI, 24 },
{ "horse.bmp", wxBITMAP_TYPE_BMP, 8 },
{ "horse.cur", wxBITMAP_TYPE_CUR, 1 },
{ "horse.gif", wxBITMAP_TYPE_GIF, 8 },
{ "horse.jpg", wxBITMAP_TYPE_JPEG, 24 },
{ "horse.pcx", wxBITMAP_TYPE_PCX, 8 },
{ "horse.pnm", wxBITMAP_TYPE_PNM, 24 },
{ "horse.tga", wxBITMAP_TYPE_TGA, 8 },
{ "horse.tif", wxBITMAP_TYPE_TIFF, 8 }
};
class ImageHandlersInit
{
public:
ImageHandlersInit();
private:
static bool ms_initialized;
wxDECLARE_NO_COPY_CLASS(ImageHandlersInit);
};
bool ImageHandlersInit::ms_initialized = false;
ImageHandlersInit::ImageHandlersInit()
{
if ( ms_initialized )
return;
ms_initialized = true;
// the formats we're going to test:
wxImage::AddHandler(new wxICOHandler);
wxImage::AddHandler(new wxXPMHandler);
wxImage::AddHandler(new wxPNGHandler);
wxImage::AddHandler(new wxANIHandler);
wxImage::AddHandler(new wxBMPHandler);
wxImage::AddHandler(new wxCURHandler);
#if wxUSE_GIF
wxImage::AddHandler(new wxGIFHandler);
#endif // wxUSE_GIF
wxImage::AddHandler(new wxJPEGHandler);
wxImage::AddHandler(new wxPCXHandler);
wxImage::AddHandler(new wxPNMHandler);
wxImage::AddHandler(new wxTGAHandler);
#if wxUSE_LIBTIFF
wxImage::AddHandler(new wxTIFFHandler);
#endif // wxUSE_LIBTIFF
}
TEST_CASE_METHOD(ImageHandlersInit, "wxImage::LoadFromFile", "[image]")
{
wxImage img;
for (unsigned int i=0; i<WXSIZEOF(g_testfiles); i++)
{
const wxString file(g_testfiles[i].file);
INFO("Loading " << file);
CHECK(img.LoadFile(file));
}
CHECK(img.LoadFile("image/bitfields.bmp", wxBITMAP_TYPE_BMP));
}
TEST_CASE_METHOD(ImageHandlersInit, "wxImage::LoadFromSocketStream", "[image]")
{
// This test doesn't work any more even using the IP address below as the
// HTTP server now redirects everything to HTTPs, so skip it for now unless
// a test URL pointing to a PNG image is defined.
wxString urlStr;
if ( !wxGetEnv("WX_TEST_IMAGE_URL_PNG", &urlStr) )
return;
wxSocketInitializer socketInit;
wxURL url(urlStr);
REQUIRE( url.GetError() == wxURL_NOERR );
std::unique_ptr<wxInputStream> in_stream(url.GetInputStream());
REQUIRE( in_stream );
REQUIRE( in_stream->IsOk() );
wxImage img;
// NOTE: it's important to inform wxImage about the type of the image being
// loaded otherwise it will try to autodetect the format, but that
// requires a seekable stream!
CHECK( img.LoadFile(*in_stream, wxBITMAP_TYPE_PNG) );
}
TEST_CASE_METHOD(ImageHandlersInit, "wxImage::LoadFromZipStream", "[image]")
{
for (unsigned int i=0; i<WXSIZEOF(g_testfiles); i++)
{
SECTION(std::string("Testing file ") + g_testfiles[i].file)
{
switch (g_testfiles[i].type)
{
case wxBITMAP_TYPE_XPM:
case wxBITMAP_TYPE_GIF:
case wxBITMAP_TYPE_PCX:
case wxBITMAP_TYPE_TGA:
case wxBITMAP_TYPE_TIFF:
continue; // skip testing those wxImageHandlers which cannot
// load data from non-seekable streams
default:
; // proceed
}
// compress the test file on the fly:
wxMemoryOutputStream memOut;
{
wxFileInputStream file(g_testfiles[i].file);
REQUIRE(file.IsOk());
wxZlibOutputStream compressFilter(memOut, 5, wxZLIB_GZIP);
REQUIRE(compressFilter.IsOk());
file.Read(compressFilter);
REQUIRE(file.GetLastError() == wxSTREAM_EOF);
}
// now fetch the compressed memory to wxImage, decompressing it on the fly; this
// allows us to test loading images from non-seekable streams other than socket streams
wxMemoryInputStream memIn(memOut);
REQUIRE(memIn.IsOk());
wxZlibInputStream decompressFilter(memIn, wxZLIB_GZIP);
REQUIRE(decompressFilter.IsOk());
wxImage img;
// NOTE: it's important to inform wxImage about the type of the image being
// loaded otherwise it will try to autodetect the format, but that
// requires a seekable stream!
CHECK( img.LoadFile(decompressFilter, g_testfiles[i].type) );
}
}
}
TEST_CASE_METHOD(ImageHandlersInit, "wxImage::SizeImage", "[image]")
{
// Test the wxImage::Size() function which takes a rectangle from source and
// places it in a new image at a given position. This test checks, if the
// correct areas are chosen, and clipping is done correctly.
// our test image:
static const char * xpm_orig[] = {
"10 10 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
" .....",
" ++++@@@@.",
" +... @.",
" +.@@++ @.",
" +.@ .+ @.",
".@ +. @.+ ",
".@ ++@@.+ ",
".@ ...+ ",
".@@@@++++ ",
"..... "
};
// the expected results for all tests:
static const char * xpm_l_t[] = {
"10 10 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
"... @.BB",
".@@++ @.BB",
".@ .+ @.BB",
" +. @.+ BB",
" ++@@.+ BB",
" ...+ BB",
"@@@++++ BB",
"... BB",
"BBBBBBBBBB",
"BBBBBBBBBB"
};
static const char * xpm_t[] = {
"10 10 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
" +... @.",
" +.@@++ @.",
" +.@ .+ @.",
".@ +. @.+ ",
".@ ++@@.+ ",
".@ ...+ ",
".@@@@++++ ",
"..... ",
"BBBBBBBBBB",
"BBBBBBBBBB"
};
static const char * xpm_r_t[] = {
"10 10 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
"BB +... ",
"BB +.@@++ ",
"BB +.@ .+ ",
"BB.@ +. @.",
"BB.@ ++@@.",
"BB.@ ...",
"BB.@@@@+++",
"BB..... ",
"BBBBBBBBBB",
"BBBBBBBBBB"
};
static const char * xpm_l[] = {
"10 10 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
" .....BB",
"+++@@@@.BB",
"... @.BB",
".@@++ @.BB",
".@ .+ @.BB",
" +. @.+ BB",
" ++@@.+ BB",
" ...+ BB",
"@@@++++ BB",
"... BB"
};
static const char * xpm_r[] = {
"10 10 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
"BB ...",
"BB ++++@@@",
"BB +... ",
"BB +.@@++ ",
"BB +.@ .+ ",
"BB.@ +. @.",
"BB.@ ++@@.",
"BB.@ ...",
"BB.@@@@+++",
"BB..... "
};
static const char * xpm_l_b[] = {
"10 10 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
"BBBBBBBBBB",
"BBBBBBBBBB",
" .....BB",
"+++@@@@.BB",
"... @.BB",
".@@++ @.BB",
".@ .+ @.BB",
" +. @.+ BB",
" ++@@.+ BB",
" ...+ BB"
};
static const char * xpm_b[] = {
"10 10 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
"BBBBBBBBBB",
"BBBBBBBBBB",
" .....",
" ++++@@@@.",
" +... @.",
" +.@@++ @.",
" +.@ .+ @.",
".@ +. @.+ ",
".@ ++@@.+ ",
".@ ...+ "
};
static const char * xpm_r_b[] = {
"10 10 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
"BBBBBBBBBB",
"BBBBBBBBBB",
"BB ...",
"BB ++++@@@",
"BB +... ",
"BB +.@@++ ",
"BB +.@ .+ ",
"BB.@ +. @.",
"BB.@ ++@@.",
"BB.@ ..."
};
static const char * xpm_sm[] = {
"8 8 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
" .....",
" ++++@@@",
" +... ",
" +.@@++ ",
" +.@ .+ ",
".@ +. @.",
".@ ++@@.",
".@ ..."
};
static const char * xpm_gt[] = {
"12 12 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
" .....BB",
" ++++@@@@.BB",
" +... @.BB",
" +.@@++ @.BB",
" +.@ .+ @.BB",
".@ +. @.+ BB",
".@ ++@@.+ BB",
".@ ...+ BB",
".@@@@++++ BB",
"..... BB",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB"
};
static const char * xpm_gt_l_t[] = {
"12 12 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
"... @.BBBB",
".@@++ @.BBBB",
".@ .+ @.BBBB",
" +. @.+ BBBB",
" ++@@.+ BBBB",
" ...+ BBBB",
"@@@++++ BBBB",
"... BBBB",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB"
};
static const char * xpm_gt_l[] = {
"12 12 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
" .....BBBB",
"+++@@@@.BBBB",
"... @.BBBB",
".@@++ @.BBBB",
".@ .+ @.BBBB",
" +. @.+ BBBB",
" ++@@.+ BBBB",
" ...+ BBBB",
"@@@++++ BBBB",
"... BBBB",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB"
};
static const char * xpm_gt_l_b[] = {
"12 12 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB",
" .....BBBB",
"+++@@@@.BBBB",
"... @.BBBB",
".@@++ @.BBBB",
".@ .+ @.BBBB",
" +. @.+ BBBB",
" ++@@.+ BBBB",
" ...+ BBBB",
"@@@++++ BBBB",
"... BBBB"
};
static const char * xpm_gt_l_bb[] = {
"12 12 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB",
" .....BBBB",
"+++@@@@.BBBB",
"... @.BBBB",
".@@++ @.BBBB",
".@ .+ @.BBBB",
" +. @.+ BBBB",
" ++@@.+ BBBB",
" ...+ BBBB"
};
static const char * xpm_gt_t[] = {
"12 12 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
" +... @.BB",
" +.@@++ @.BB",
" +.@ .+ @.BB",
".@ +. @.+ BB",
".@ ++@@.+ BB",
".@ ...+ BB",
".@@@@++++ BB",
"..... BB",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB"
};
static const char * xpm_gt_b[] = {
"12 12 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB",
" .....BB",
" ++++@@@@.BB",
" +... @.BB",
" +.@@++ @.BB",
" +.@ .+ @.BB",
".@ +. @.+ BB",
".@ ++@@.+ BB",
".@ ...+ BB",
".@@@@++++ BB",
"..... BB"
};
static const char * xpm_gt_bb[] = {
"12 12 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB",
" .....BB",
" ++++@@@@.BB",
" +... @.BB",
" +.@@++ @.BB",
" +.@ .+ @.BB",
".@ +. @.+ BB",
".@ ++@@.+ BB",
".@ ...+ BB"
};
static const char * xpm_gt_r_t[] = {
"12 12 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
"BB +... @.",
"BB +.@@++ @.",
"BB +.@ .+ @.",
"BB.@ +. @.+ ",
"BB.@ ++@@.+ ",
"BB.@ ...+ ",
"BB.@@@@++++ ",
"BB..... ",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB"
};
static const char * xpm_gt_r[] = {
"12 12 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
"BB .....",
"BB ++++@@@@.",
"BB +... @.",
"BB +.@@++ @.",
"BB +.@ .+ @.",
"BB.@ +. @.+ ",
"BB.@ ++@@.+ ",
"BB.@ ...+ ",
"BB.@@@@++++ ",
"BB..... ",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB"
};
static const char * xpm_gt_r_b[] = {
"12 12 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB",
"BB .....",
"BB ++++@@@@.",
"BB +... @.",
"BB +.@@++ @.",
"BB +.@ .+ @.",
"BB.@ +. @.+ ",
"BB.@ ++@@.+ ",
"BB.@ ...+ ",
"BB.@@@@++++ ",
"BB..... "
};
static const char * xpm_gt_r_bb[] = {
"12 12 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB",
"BB .....",
"BB ++++@@@@.",
"BB +... @.",
"BB +.@@++ @.",
"BB +.@ .+ @.",
"BB.@ +. @.+ ",
"BB.@ ++@@.+ ",
"BB.@ ...+ "
};
static const char * xpm_gt_rr_t[] = {
"12 12 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
"BBBB +... ",
"BBBB +.@@++ ",
"BBBB +.@ .+ ",
"BBBB.@ +. @.",
"BBBB.@ ++@@.",
"BBBB.@ ...",
"BBBB.@@@@+++",
"BBBB..... ",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB"
};
static const char * xpm_gt_rr[] = {
"12 12 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
"BBBB ...",
"BBBB ++++@@@",
"BBBB +... ",
"BBBB +.@@++ ",
"BBBB +.@ .+ ",
"BBBB.@ +. @.",
"BBBB.@ ++@@.",
"BBBB.@ ...",
"BBBB.@@@@+++",
"BBBB..... ",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB"
};
static const char * xpm_gt_rr_b[] = {
"12 12 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB",
"BBBB ...",
"BBBB ++++@@@",
"BBBB +... ",
"BBBB +.@@++ ",
"BBBB +.@ .+ ",
"BBBB.@ +. @.",
"BBBB.@ ++@@.",
"BBBB.@ ...",
"BBBB.@@@@+++",
"BBBB..... "
};
static const char * xpm_gt_rr_bb[] = {
"12 12 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB",
"BBBBBBBBBBBB",
"BBBB ...",
"BBBB ++++@@@",
"BBBB +... ",
"BBBB +.@@++ ",
"BBBB +.@ .+ ",
"BBBB.@ +. @.",
"BBBB.@ ++@@.",
"BBBB.@ ..."
};
static const char * xpm_sm_ll_tt[] = {
"8 8 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
" .+ @.BB",
". @.+ BB",
"+@@.+ BB",
" ...+ BB",
"@++++ BB",
". BB",
"BBBBBBBB",
"BBBBBBBB"
};
static const char * xpm_sm_ll_t[] = {
"8 8 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
". @.BB",
"@++ @.BB",
" .+ @.BB",
". @.+ BB",
"+@@.+ BB",
" ...+ BB",
"@++++ BB",
". BB"
};
static const char * xpm_sm_ll[] = {
"8 8 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
" .....BB",
"+@@@@.BB",
". @.BB",
"@++ @.BB",
" .+ @.BB",
". @.+ BB",
"+@@.+ BB",
" ...+ BB"
};
static const char * xpm_sm_ll_b[] = {
"8 8 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
"BBBBBBBB",
"BBBBBBBB",
" .....BB",
"+@@@@.BB",
". @.BB",
"@++ @.BB",
" .+ @.BB",
". @.+ BB"
};
static const char * xpm_sm_l_tt[] = {
"8 8 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
".@ .+ @.",
" +. @.+ ",
" ++@@.+ ",
" ...+ ",
"@@@++++ ",
"... ",
"BBBBBBBB",
"BBBBBBBB"
};
static const char * xpm_sm_l_t[] = {
"8 8 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
"... @.",
".@@++ @.",
".@ .+ @.",
" +. @.+ ",
" ++@@.+ ",
" ...+ ",
"@@@++++ ",
"... "
};
static const char * xpm_sm_l[] = {
"8 8 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
" .....",
"+++@@@@.",
"... @.",
".@@++ @.",
".@ .+ @.",
" +. @.+ ",
" ++@@.+ ",
" ...+ "
};
static const char * xpm_sm_l_b[] = {
"8 8 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
"BBBBBBBB",
"BBBBBBBB",
" .....",
"+++@@@@.",
"... @.",
".@@++ @.",
".@ .+ @.",
" +. @.+ "
};
static const char * xpm_sm_tt[] = {
"8 8 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
" +.@ .+ ",
".@ +. @.",
".@ ++@@.",
".@ ...",
".@@@@+++",
"..... ",
"BBBBBBBB",
"BBBBBBBB"
};
static const char * xpm_sm_t[] = {
"8 8 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
" +... ",
" +.@@++ ",
" +.@ .+ ",
".@ +. @.",
".@ ++@@.",
".@ ...",
".@@@@+++",
"..... "
};
static const char * xpm_sm_b[] = {
"8 8 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
"BBBBBBBB",
"BBBBBBBB",
" ...",
" ++++@@@",
" +... ",
" +.@@++ ",
" +.@ .+ ",
".@ +. @."
};
static const char * xpm_sm_r_tt[] = {
"8 8 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
"BB +.@ .",
"BB.@ +. ",
"BB.@ ++@",
"BB.@ .",
"BB.@@@@+",
"BB..... ",
"BBBBBBBB",
"BBBBBBBB"
};
static const char * xpm_sm_r_t[] = {
"8 8 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
"BB +... ",
"BB +.@@+",
"BB +.@ .",
"BB.@ +. ",
"BB.@ ++@",
"BB.@ .",
"BB.@@@@+",
"BB..... "
};
static const char * xpm_sm_r[] = {
"8 8 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
"BB .",
"BB ++++@",
"BB +... ",
"BB +.@@+",
"BB +.@ .",
"BB.@ +. ",
"BB.@ ++@",
"BB.@ ."
};
static const char * xpm_sm_r_b[] = {
"8 8 5 1", "B c Black", " c #00ff00", ". c #0000ff", "+ c #7f7f7f", "@ c #FF0000",
"BBBBBBBB",
"BBBBBBBB",
"BB .",
"BB ++++@",
"BB +... ",
"BB +.@@+",
"BB +.@ .",
"BB.@ +. "
};
// this table defines all tests
struct SizeTestData
{
int w, h, dx, dy; // first parameters for Size()
const char **ref_xpm; // expected result
} sizeTestData[] =
{
{ 10, 10, 0, 0, xpm_orig}, // same size, same position
{ 12, 12, 0, 0, xpm_gt}, // target larger, same position
{ 8, 8, 0, 0, xpm_sm}, // target smaller, same position
{ 10, 10, -2, -2, xpm_l_t}, // same size, move left up
{ 10, 10, -2, 0, xpm_l}, // same size, move left
{ 10, 10, -2, 2, xpm_l_b}, // same size, move left down
{ 10, 10, 0, -2, xpm_t}, // same size, move up
{ 10, 10, 0, 2, xpm_b}, // same size, move down
{ 10, 10, 2, -2, xpm_r_t}, // same size, move right up
{ 10, 10, 2, 0, xpm_r}, // same size, move right
{ 10, 10, 2, 2, xpm_r_b}, // same size, move right down
{ 12, 12, -2, -2, xpm_gt_l_t}, // target larger, move left up
{ 12, 12, -2, 0, xpm_gt_l}, // target larger, move left
{ 12, 12, -2, 2, xpm_gt_l_b}, // target larger, move left down
{ 12, 12, -2, 4, xpm_gt_l_bb}, // target larger, move left down
{ 12, 12, 0, -2, xpm_gt_t}, // target larger, move up
{ 12, 12, 0, 2, xpm_gt_b}, // target larger, move down
{ 12, 12, 0, 4, xpm_gt_bb}, // target larger, move down
{ 12, 12, 2, -2, xpm_gt_r_t}, // target larger, move right up
{ 12, 12, 2, 0, xpm_gt_r}, // target larger, move right
{ 12, 12, 2, 2, xpm_gt_r_b}, // target larger, move right down
{ 12, 12, 2, 4, xpm_gt_r_bb}, // target larger, move right down
{ 12, 12, 4, -2, xpm_gt_rr_t}, // target larger, move right up
{ 12, 12, 4, 0, xpm_gt_rr}, // target larger, move right
{ 12, 12, 4, 2, xpm_gt_rr_b}, // target larger, move right down
{ 12, 12, 4, 4, xpm_gt_rr_bb}, // target larger, move right down
{ 8, 8, -4, -4, xpm_sm_ll_tt}, // target smaller, move left up
{ 8, 8, -4, -2, xpm_sm_ll_t}, // target smaller, move left up
{ 8, 8, -4, 0, xpm_sm_ll}, // target smaller, move left
{ 8, 8, -4, 2, xpm_sm_ll_b}, // target smaller, move left down
{ 8, 8, -2, -4, xpm_sm_l_tt}, // target smaller, move left up
{ 8, 8, -2, -2, xpm_sm_l_t}, // target smaller, move left up
{ 8, 8, -2, 0, xpm_sm_l}, // target smaller, move left
{ 8, 8, -2, 2, xpm_sm_l_b}, // target smaller, move left down
{ 8, 8, 0, -4, xpm_sm_tt}, // target smaller, move up
{ 8, 8, 0, -2, xpm_sm_t}, // target smaller, move up
{ 8, 8, 0, 2, xpm_sm_b}, // target smaller, move down
{ 8, 8, 2, -4, xpm_sm_r_tt}, // target smaller, move right up
{ 8, 8, 2, -2, xpm_sm_r_t}, // target smaller, move right up
{ 8, 8, 2, 0, xpm_sm_r}, // target smaller, move right
{ 8, 8, 2, 2, xpm_sm_r_b}, // target smaller, move right down
};
const wxImage src_img(xpm_orig);
for ( unsigned i = 0; i < WXSIZEOF(sizeTestData); i++ )
{
SizeTestData& st = sizeTestData[i];
wxImage
actual(src_img.Size(wxSize(st.w, st.h), wxPoint(st.dx, st.dy), 0, 0, 0)),
expected(st.ref_xpm);
// to check results with an image viewer uncomment this:
//actual.SaveFile(wxString::Format("imagetest-%02d-actual.png", i), wxBITMAP_TYPE_PNG);
//expected.SaveFile(wxString::Format("imagetest-%02d-exp.png", i), wxBITMAP_TYPE_PNG);
wxINFO_FMT("Resize test #%u: (%d, %d), (%d, %d)",
i, st.w, st.h, st.dx, st.dy);
CHECK_THAT( actual, RGBSameAs(expected) );
}
}
TEST_CASE_METHOD(ImageHandlersInit, "wxImage::CompareLoadedImage", "[image]")
{
wxImage expected8("horse.xpm");
REQUIRE( expected8.IsOk() );
wxImage expected24("horse.png");
REQUIRE( expected24.IsOk() );
for (size_t i=0; i<WXSIZEOF(g_testfiles); i++)
{
if ( !(g_testfiles[i].bitDepth == 8 || g_testfiles[i].bitDepth == 24)
|| g_testfiles[i].type == wxBITMAP_TYPE_JPEG /*skip lossy JPEG*/)
{
continue;
}
wxImage actual(g_testfiles[i].file);
if ( actual.GetSize() != expected8.GetSize() )
{
continue;
}
wxINFO_FMT("Compare test '%s' for loading", g_testfiles[i].file);
CHECK_THAT( actual,
RGBSameAs(g_testfiles[i].bitDepth == 8 ? expected8
: expected24) );
}
}
enum
{
wxIMAGE_HAVE_ALPHA = (1 << 0),
wxIMAGE_HAVE_PALETTE = (1 << 1),
wxIMAGE_HAVE_DELTA_RLE_BITMAP = (1 << 2)
};
static
void CompareImage(const wxImageHandler& handler, const wxImage& image,
int properties = 0, const wxImage *compareTo = nullptr)
{
wxBitmapType type = handler.GetType();
const bool testPalette = (properties & wxIMAGE_HAVE_PALETTE) != 0;
/*
This is getting messy and should probably be transformed into a table
with image format features before it gets hairier.
*/
if ( testPalette
&& ( !(type == wxBITMAP_TYPE_BMP
|| type == wxBITMAP_TYPE_GIF
|| type == wxBITMAP_TYPE_ICO
|| type == wxBITMAP_TYPE_PNG)
|| type == wxBITMAP_TYPE_XPM) )
{
return;
}
const bool testAlpha = (properties & wxIMAGE_HAVE_ALPHA) != 0;
if (testAlpha
&& !(type == wxBITMAP_TYPE_PNG || type == wxBITMAP_TYPE_TGA
|| type == wxBITMAP_TYPE_TIFF) )
{
// don't test images with alpha if this handler doesn't support alpha
return;
}
if (type == wxBITMAP_TYPE_JPEG /* skip lossy JPEG */)
{
return;
}
wxMemoryOutputStream memOut;
if ( !image.SaveFile(memOut, type) )
{
// Unfortunately we can't know if the handler just doesn't support
// saving images, or if it failed to save.
return;
}
unsigned bitsPerPixel = testPalette ? 8 : (testAlpha ? 32 : 24);
wxINFO_FMT("Compare test '%s (%d-bit)' for saving",
handler.GetExtension(), bitsPerPixel);
wxMemoryInputStream memIn(memOut);
REQUIRE(memIn.IsOk());
wxImage actual(memIn);
REQUIRE(actual.IsOk());
const wxImage *expected = compareTo ? compareTo : &image;
CHECK_THAT(actual, RGBSameAs(*expected));
#if wxUSE_PALETTE
REQUIRE(actual.HasPalette()
== (testPalette || type == wxBITMAP_TYPE_XPM));
#endif
REQUIRE( actual.HasAlpha() == testAlpha);
if (!testAlpha)
{
return;
}
wxINFO_FMT("Compare alpha test '%s' for saving", handler.GetExtension());
CHECK_THAT(actual, RGBSameAs(*expected));
}
static void SetAlpha(wxImage *image)
{
image->SetAlpha();
unsigned char *ptr = image->GetAlpha();
const int width = image->GetWidth();
const int height = image->GetHeight();
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x)
{
ptr[y*width + x] = (x*y) & wxIMAGE_ALPHA_OPAQUE;
}
}
}
TEST_CASE_METHOD(ImageHandlersInit, "wxImage::CompareSavedImage", "[image]")
{
wxImage expected24("horse.png");
REQUIRE( expected24.IsOk() );
REQUIRE( !expected24.HasAlpha() );
wxImage expected8 = expected24.ConvertToGreyscale();
#if wxUSE_PALETTE
unsigned char greys[256];
for (int i = 0; i < 256; ++i)
{
greys[i] = i;
}
wxPalette palette(256, greys, greys, greys);
expected8.SetPalette(palette);
#endif // #if wxUSE_PALETTE
expected8.SetOption(wxIMAGE_OPTION_BMP_FORMAT, wxBMP_8BPP_PALETTE);
// Create an image with alpha based on the loaded image
wxImage expected32(expected24);
SetAlpha(&expected32);
const wxList& list = wxImage::GetHandlers();
for ( wxList::compatibility_iterator node = list.GetFirst();
node; node = node->GetNext() )
{
wxImageHandler *handler = (wxImageHandler *) node->GetData();
#if wxUSE_PALETTE
CompareImage(*handler, expected8, wxIMAGE_HAVE_PALETTE);
#endif
CompareImage(*handler, expected24);
CompareImage(*handler, expected32, wxIMAGE_HAVE_ALPHA);
}
}
TEST_CASE_METHOD(ImageHandlersInit, "wxImage::SavePNG", "[image]")
{
wxImage expected24("horse.png");
REQUIRE( expected24.IsOk() );
#if wxUSE_PALETTE
REQUIRE( !expected24.HasPalette() );
#endif // #if wxUSE_PALETTE
wxImage expected8 = expected24.ConvertToGreyscale();
/*
horse.png converted to greyscale should be saved without a palette.
*/
CompareImage(*wxImage::FindHandler(wxBITMAP_TYPE_PNG), expected8);
/*
But if we explicitly ask for trying to save with a palette, it should work.
*/
expected8.SetOption(wxIMAGE_OPTION_PNG_FORMAT, wxPNG_TYPE_PALETTE);
CompareImage(*wxImage::FindHandler(wxBITMAP_TYPE_PNG),
expected8, wxIMAGE_HAVE_PALETTE);
REQUIRE( expected8.LoadFile("horse.gif") );
#if wxUSE_PALETTE
REQUIRE( expected8.HasPalette() );
#endif // #if wxUSE_PALETTE
CompareImage(*wxImage::FindHandler(wxBITMAP_TYPE_PNG),
expected8, wxIMAGE_HAVE_PALETTE);
/*
Add alpha to the image in such a way that there will still be a maximum
of 256 unique RGBA combinations. This should result in a saved
PNG image still being palettised and having alpha.
*/
expected8.SetAlpha();
int x, y;
const int width = expected8.GetWidth();
const int height = expected8.GetHeight();
for (y = 0; y < height; ++y)
{
for (x = 0; x < width; ++x)
{
expected8.SetAlpha(x, y, expected8.GetRed(x, y));
}
}
CompareImage(*wxImage::FindHandler(wxBITMAP_TYPE_PNG),
expected8, wxIMAGE_HAVE_ALPHA|wxIMAGE_HAVE_PALETTE);
/*
Now change the alpha of the first pixel so that we can't save palettised
anymore because there will be 256+1 entries which is beyond PNGs limit
of 256 entries.
*/
expected8.SetAlpha(0, 0, 1);
CompareImage(*wxImage::FindHandler(wxBITMAP_TYPE_PNG),
expected8, wxIMAGE_HAVE_ALPHA);
/*
Even if we explicitly ask for saving palettised it should not be done.
*/
expected8.SetOption(wxIMAGE_OPTION_PNG_FORMAT, wxPNG_TYPE_PALETTE);
CompareImage(*wxImage::FindHandler(wxBITMAP_TYPE_PNG),
expected8, wxIMAGE_HAVE_ALPHA);
}
#if wxUSE_LIBTIFF
static void TestTIFFImage(const wxString& option, int value,
const wxImage *compareImage = nullptr)
{
INFO("Using option " << option << "=" << value);
wxImage image;
if (compareImage)
{
image = *compareImage;
}
else
{
(void) image.LoadFile("horse.png");
}
REQUIRE( image.IsOk() );
wxMemoryOutputStream memOut;
image.SetOption(option, value);
REQUIRE(image.SaveFile(memOut, wxBITMAP_TYPE_TIFF));
wxMemoryInputStream memIn(memOut);
REQUIRE(memIn.IsOk());
wxImage savedImage(memIn);
REQUIRE(savedImage.IsOk());
CHECK( savedImage.HasOption(option) );
CHECK( savedImage.GetOptionInt(option) == value );
CHECK( savedImage.HasAlpha() == image.HasAlpha() );
}
TEST_CASE_METHOD(ImageHandlersInit, "wxImage::SaveTIFF", "[image]")
{
TestTIFFImage(wxIMAGE_OPTION_TIFF_BITSPERSAMPLE, 1);
TestTIFFImage(wxIMAGE_OPTION_TIFF_SAMPLESPERPIXEL, 1);
TestTIFFImage(wxIMAGE_OPTION_TIFF_PHOTOMETRIC, 0/*PHOTOMETRIC_MINISWHITE*/);
TestTIFFImage(wxIMAGE_OPTION_TIFF_PHOTOMETRIC, 1/*PHOTOMETRIC_MINISBLACK*/);
wxImage alphaImage("horse.png");
REQUIRE( alphaImage.IsOk() );
SetAlpha(&alphaImage);
// RGB with alpha
TestTIFFImage(wxIMAGE_OPTION_TIFF_SAMPLESPERPIXEL, 4, &alphaImage);
// Grey with alpha
TestTIFFImage(wxIMAGE_OPTION_TIFF_SAMPLESPERPIXEL, 2, &alphaImage);
// B/W with alpha
alphaImage.SetOption(wxIMAGE_OPTION_TIFF_BITSPERSAMPLE, 1);
TestTIFFImage(wxIMAGE_OPTION_TIFF_SAMPLESPERPIXEL, 2, &alphaImage);
}
#endif // wxUSE_LIBTIFF
TEST_CASE_METHOD(ImageHandlersInit, "wxImage::ReadCorruptedTGA", "[image]")
{
static unsigned char corruptTGA[18+1+3] =
{
0,
0,
10, // RLE compressed image.
0, 0,
0, 0,
0,
0, 0,
0, 0,
1, 0, // Width is 1.
1, 0, // Height is 1.
24, // Bits per pixel.
0,
0xff, // Run length (repeat next pixel 127+1 times).
0xff, 0xff, 0xff // One 24-bit pixel.
};
wxMemoryInputStream memIn(corruptTGA, WXSIZEOF(corruptTGA));
REQUIRE(memIn.IsOk());
wxImage tgaImage;
REQUIRE( !tgaImage.LoadFile(memIn) );
/*
Instead of repeating a pixel 127+1 times, now tell it there will
follow 127+1 uncompressed pixels (while we only should have 1 in total).
*/
corruptTGA[18] = 0x7f;
REQUIRE( !tgaImage.LoadFile(memIn) );
}
#if wxUSE_GIF
TEST_CASE_METHOD(ImageHandlersInit, "wxImage::SaveAnimatedGIF", "[image]")
{
#if wxUSE_PALETTE
wxImage image("horse.gif");
REQUIRE( image.IsOk() );
wxImageArray images;
images.push_back(image);
for (int i = 0; i < 4-1; ++i)
{
images.push_back( images[i].Rotate90() );
images[i+1].SetPalette(images[0].GetPalette());
}
wxMemoryOutputStream memOut;
REQUIRE( wxGIFHandler().SaveAnimation(images, &memOut) );
wxGIFHandler handler;
wxMemoryInputStream memIn(memOut);
REQUIRE(memIn.IsOk());
const int imageCount = handler.GetImageCount(memIn);
CHECK( imageCount == 4 );
for (int i = 0; i < imageCount; ++i)
{
wxFileOffset pos = memIn.TellI();
REQUIRE( handler.LoadFile(&image, memIn, true, i) );
memIn.SeekI(pos);
wxINFO_FMT("Compare test for GIF frame number %d failed", i);
CHECK_THAT(image, RGBSameAs(images[i]));
}
#endif // #if wxUSE_PALETTE
}
static void TestGIFComment(const wxString& comment)
{
wxImage image("horse.gif");
image.SetOption(wxIMAGE_OPTION_GIF_COMMENT, comment);
wxMemoryOutputStream memOut;
REQUIRE(image.SaveFile(memOut, wxBITMAP_TYPE_GIF));
wxMemoryInputStream memIn(memOut);
REQUIRE( image.LoadFile(memIn) );
CHECK( image.GetOption(wxIMAGE_OPTION_GIF_COMMENT) == comment );
}
TEST_CASE_METHOD(ImageHandlersInit, "wxImage::GIFComment", "[image]")
{
// Test reading a comment.
wxImage image("horse.gif");
CHECK( image.GetOption(wxIMAGE_OPTION_GIF_COMMENT) ==
" Imported from GRADATION image: gray" );
// Test writing a comment and reading it back.
TestGIFComment("Giving the GIF a gifted giraffe as a gift");
// Test writing and reading a comment again but with a long comment.
TestGIFComment(wxString(wxT('a'), 256)
+ wxString(wxT('b'), 256)
+ wxString(wxT('c'), 256));
// Test writing comments in an animated GIF and reading them back.
REQUIRE( image.LoadFile("horse.gif") );
#if wxUSE_PALETTE
wxImageArray images;
int i;
for (i = 0; i < 4; ++i)
{
if (i)
{
images.push_back( images[i-1].Rotate90() );
images[i].SetPalette(images[0].GetPalette());
}
else
{
images.push_back(image);
}
images[i].SetOption(wxIMAGE_OPTION_GIF_COMMENT,
wxString::Format("GIF comment for frame #%d", i+1));
}
wxMemoryOutputStream memOut;
REQUIRE( wxGIFHandler().SaveAnimation(images, &memOut) );
wxGIFHandler handler;
wxMemoryInputStream memIn(memOut);
REQUIRE(memIn.IsOk());
const int imageCount = handler.GetImageCount(memIn);
for (i = 0; i < imageCount; ++i)
{
wxFileOffset pos = memIn.TellI();
REQUIRE( handler.LoadFile(&image, memIn, true /*verbose?*/, i) );
CHECK( image.GetOption(wxIMAGE_OPTION_GIF_COMMENT) ==
wxString::Format("GIF comment for frame #%d", i+1) );
memIn.SeekI(pos);
}
#endif //wxUSE_PALETTE
}
TEST_CASE_METHOD(ImageHandlersInit, "wxImage::BadGIF", "[image][gif][error]")
{
wxImage image("image/bad_truncated.gif");
REQUIRE( image.IsOk() );
CHECK( image.GetSize() == wxSize(1200, 800) );
}
#endif // wxUSE_GIF
TEST_CASE_METHOD(ImageHandlersInit, "wxImage::DibPadding", "[image]")
{
/*
There used to be an error with calculating the DWORD aligned scan line
pitch for a BMP/ICO resulting in buffer overwrites (with at least MSVC9
Debug this gave a heap corruption assertion when saving the mask of
an ICO). Test for it here.
*/
wxImage image("horse.gif");
REQUIRE( image.IsOk() );
image = image.Scale(99, 99);
wxMemoryOutputStream memOut;
REQUIRE( image.SaveFile(memOut, wxBITMAP_TYPE_ICO) );
}
static void CompareBMPImage(const wxString& file1, const wxString& file2)
{
wxImage image1(file1);
REQUIRE( image1.IsOk() );
wxImage image2(file2);
REQUIRE( image2.IsOk() );
INFO("Comparing " << file1 << " and " << file2);
CompareImage(*wxImage::FindHandler(wxBITMAP_TYPE_BMP), image1, 0, &image2);
}
TEST_CASE_METHOD(ImageHandlersInit, "wxImage::BMPFlippingAndRLECompression", "[image]")
{
CompareBMPImage("image/horse_grey.bmp", "image/horse_grey_flipped.bmp");
CompareBMPImage("image/horse_rle8.bmp", "image/horse_grey.bmp");
CompareBMPImage("image/horse_rle8.bmp", "image/horse_rle8_flipped.bmp");
CompareBMPImage("image/horse_rle4.bmp", "image/horse_rle4_flipped.bmp");
CompareBMPImage("image/rle8-delta-320x240.bmp",
"image/rle8-delta-320x240-expected.bmp");
CompareBMPImage("image/rle4-delta-320x240.bmp",
"image/rle8-delta-320x240-expected.bmp");
}
// If possible, check loading some BMP files by comparing loading them directly
// and via wxImage.
#ifdef CAN_LOAD_BITMAP_DIRECTLY
static wxImage ImageFromBMPFile(const wxString& filename)
{
INFO("Loading pixel data from " << filename);
wxFile file(filename);
// Read the BMP file header
const size_t hdrLen = 14;
char hdr[hdrLen];
ssize_t cbRead = file.Read(hdr, hdrLen);
REQUIRE((size_t)cbRead == hdrLen);
REQUIRE(hdr[0] == 'B');
REQUIRE(hdr[1] == 'M');
wxUint32 ofstDeclared = *(const wxUint32*)&hdr[10];
// Now read the data
size_t dataLen = file.Length() - hdrLen;
std::vector<unsigned char> buf(dataLen);
cbRead = file.Read(&buf[0], dataLen);
REQUIRE((size_t)cbRead == dataLen);
// Check there is no gap in the file between the end of the header
// and the start of the pixel data. If there is, we can't tell
// wxDIB::ConvertToBitmap() about it because we need to test its
// code path where bits==nullptr, so we just have to fail the test
const BITMAPINFO* pbmi = reinterpret_cast<const BITMAPINFO*>(&buf[0]);
wxUint32 numColors = 0;
if ( pbmi->bmiHeader.biCompression == BI_BITFIELDS )
{
if ( pbmi->bmiHeader.biSize == sizeof(BITMAPINFOHEADER) )
numColors = 3;
}
else
{
if ( pbmi->bmiHeader.biClrUsed )
numColors = pbmi->bmiHeader.biClrUsed;
else if ( pbmi->bmiHeader.biBitCount <= 8 )
numColors = 1 << pbmi->bmiHeader.biBitCount;
}
wxUint32 cbColorTable = numColors * sizeof(RGBQUAD);
size_t ofstComputed = hdrLen + pbmi->bmiHeader.biSize + cbColorTable;
REQUIRE(ofstComputed == ofstDeclared);
// All good; now make an image out of it
AutoHBITMAP ahbmp(wxDIB::ConvertToBitmap(pbmi));
wxDIB dib(ahbmp);
REQUIRE(dib.IsOk());
return dib.ConvertToImage();
}
// Compare the results of loading a BMP using:
// 1. Windows' own conversion (GDI CreateDIBitmap() via wxDIB::ConvertToBitmap())
// 2. wxBMPHandler via wxImage::LoadFile()
// 3. wxBMPFileHandler via wxBitmap::LoadFile()
static void CompareBMPImageLoad(const wxString& filename, int properties = 0)
{
INFO("Comparing loading methods for " << filename);
wxImage image1 = ImageFromBMPFile(filename);
REQUIRE( image1.IsOk() );
wxImage image2(filename);
REQUIRE( image2.IsOk() );
wxBitmap bitmap3(filename, wxBITMAP_TYPE_BMP);
REQUIRE( bitmap3.IsOk() );
wxImage image3(bitmap3.ConvertToImage());
REQUIRE( image3.IsOk() );
// It is impossible (in general) for both of these
// tests to pass on delta-RLE bitmaps. See #23638
if ( !(properties & wxIMAGE_HAVE_DELTA_RLE_BITMAP) )
{
INFO("wxDIB::ConvertToBitmap vs wxImage::LoadFile");
CompareImage(*wxImage::FindHandler(wxBITMAP_TYPE_BMP),
image1, properties, &image2);
}
INFO("wxBitmap::LoadFile vs wxImage::LoadFile");
CompareImage(*wxImage::FindHandler(wxBITMAP_TYPE_BMP),
image3, properties, &image2);
}
TEST_CASE_METHOD(ImageHandlersInit, "wxImage::BMPLoadMethod", "[image][bmp]")
{
CompareBMPImageLoad("image/bitfields.bmp");
// We would check the alpha on this one, but at the moment it fails
// because of the way CompareImage() saves and reloads images,
// which for BMPs does not preserve the alpha channel
CompareBMPImageLoad("image/bitfields-alpha.bmp"/*, wxIMAGE_HAVE_ALPHA*/);
CompareBMPImageLoad("image/horse_grey.bmp");
CompareBMPImageLoad("image/horse_rle8.bmp");
CompareBMPImageLoad("image/horse_rle4.bmp");
CompareBMPImageLoad("image/rle8-delta-320x240.bmp",
wxIMAGE_HAVE_DELTA_RLE_BITMAP);
CompareBMPImageLoad("image/rle4-delta-320x240.bmp",
wxIMAGE_HAVE_DELTA_RLE_BITMAP);
}
#endif // CAN_LOAD_BITMAP_DIRECTLY
static int
FindMaxChannelDiff(const wxImage& i1, const wxImage& i2)
{
if ( i1.GetWidth() != i2.GetWidth() )
return false;
if ( i1.GetHeight() != i2.GetHeight() )
return false;
const unsigned char* p1 = i1.GetData();
const unsigned char* p2 = i2.GetData();
const int numBytes = i1.GetWidth()*i1.GetHeight()*3;
int maxDiff = 0;
for ( int n = 0; n < numBytes; n++, p1++, p2++ )
{
const int diff = std::abs(*p1 - *p2);
if ( diff > maxDiff )
maxDiff = diff;
}
return maxDiff;
}
// Note that we accept up to one pixel difference, this happens because of
// different rounding behaviours in different compiler versions
// even under the same architecture, see the example in
// http://thread.gmane.org/gmane.comp.lib.wxwidgets.devel/151149/focus=151154
// The 0 below can be replaced with 1 to generate, instead of comparing with,
// the test files.
#define ASSERT_IMAGE_EQUAL_TO_FILE(image, file) \
if ( 0 ) \
{ \
INFO("Failed to save \"" << file << "\""); \
CHECK( image.SaveFile(file) ); \
} \
else \
{ \
const wxImage imageFromFile(file); \
if ( imageFromFile.IsOk() ) \
{ \
INFO("Wrong scaled \"" << file << "\" " << Catch::StringMaker<wxImage>::convert(image)); \
CHECK(FindMaxChannelDiff(imageFromFile, image) <= 1); \
} \
else \
{ \
FAIL("Failed to load \"" << file << "\""); \
} \
}
TEST_CASE_METHOD(ImageHandlersInit, "wxImage::ScaleCompare", "[image]")
{
wxImage original;
REQUIRE(original.LoadFile("horse.bmp"));
ASSERT_IMAGE_EQUAL_TO_FILE(original.Scale( 50, 50, wxIMAGE_QUALITY_BICUBIC),
"image/horse_bicubic_50x50.png");
ASSERT_IMAGE_EQUAL_TO_FILE(original.Scale(100, 100, wxIMAGE_QUALITY_BICUBIC),
"image/horse_bicubic_100x100.png");
ASSERT_IMAGE_EQUAL_TO_FILE(original.Scale(150, 150, wxIMAGE_QUALITY_BICUBIC),
"image/horse_bicubic_150x150.png");
ASSERT_IMAGE_EQUAL_TO_FILE(original.Scale(300, 300, wxIMAGE_QUALITY_BICUBIC),
"image/horse_bicubic_300x300.png");
ASSERT_IMAGE_EQUAL_TO_FILE(original.Scale( 50, 50, wxIMAGE_QUALITY_BOX_AVERAGE),
"image/horse_box_average_50x50.png");
ASSERT_IMAGE_EQUAL_TO_FILE(original.Scale(100, 100, wxIMAGE_QUALITY_BOX_AVERAGE),
"image/horse_box_average_100x100.png");
ASSERT_IMAGE_EQUAL_TO_FILE(original.Scale(150, 150, wxIMAGE_QUALITY_BOX_AVERAGE),
"image/horse_box_average_150x150.png");
ASSERT_IMAGE_EQUAL_TO_FILE(original.Scale(300, 300, wxIMAGE_QUALITY_BOX_AVERAGE),
"image/horse_box_average_300x300.png");
ASSERT_IMAGE_EQUAL_TO_FILE(original.Scale( 50, 50, wxIMAGE_QUALITY_BILINEAR),
"image/horse_bilinear_50x50.png");
ASSERT_IMAGE_EQUAL_TO_FILE(original.Scale(100, 100, wxIMAGE_QUALITY_BILINEAR),
"image/horse_bilinear_100x100.png");
ASSERT_IMAGE_EQUAL_TO_FILE(original.Scale(150, 150, wxIMAGE_QUALITY_BILINEAR),
"image/horse_bilinear_150x150.png");
ASSERT_IMAGE_EQUAL_TO_FILE(original.Scale(300, 300, wxIMAGE_QUALITY_BILINEAR),
"image/horse_bilinear_300x300.png");
// Test scaling symmetric image
const static char* cross_xpm[] =
{
"9 9 5 1",
" c None",
"r c #FF0000",
"g c #00FF00",
"b c #0000FF",
"w c #FFFFFF",
" r ",
" g ",
" b ",
" w ",
"rgbw wbgr",
" w ",
" b ",
" g ",
" r "
};
wxImage imgCross(cross_xpm);
ASSERT_IMAGE_EQUAL_TO_FILE(imgCross.Scale(256, 256, wxIMAGE_QUALITY_BILINEAR),
"image/cross_bilinear_256x256.png");
ASSERT_IMAGE_EQUAL_TO_FILE(imgCross.Scale(256, 256, wxIMAGE_QUALITY_BICUBIC),
"image/cross_bicubic_256x256.png");
ASSERT_IMAGE_EQUAL_TO_FILE(imgCross.Scale(256, 256, wxIMAGE_QUALITY_BOX_AVERAGE),
"image/cross_box_average_256x256.png");
ASSERT_IMAGE_EQUAL_TO_FILE(imgCross.Scale(256, 256, wxIMAGE_QUALITY_NEAREST),
"image/cross_nearest_neighb_256x256.png");
}
TEST_CASE_METHOD(ImageHandlersInit, "wxImage::CreateBitmapFromCursor", "[image]")
{
#if !defined __WXOSX_IPHONE__ && !defined __WXDFB__ && !defined __WXX11__
wxImage image("image/wx.png");
wxCursor cursor(image);
wxBitmap bitmap(cursor);
#if defined(__WXGTK__)
// cursor to bitmap could fail depending on windowing system and cursor (gdk-cursor-get-image)
if ( !bitmap.IsOk() )
return;
#endif
wxImage result = bitmap.ConvertToImage();
// on Windows the cursor is always scaled to 32x32px (96 DPI)
// on macOS the resulting bitmap size depends on the DPI
if ( image.GetSize() == result.GetSize() )
{
CHECK_THAT(image, RGBASimilarTo(result, 2));
}
else
{
wxVector<wxPoint> coords;
coords.push_back(wxPoint(14, 10)); // blue square
coords.push_back(wxPoint(8, 22)); // red square
coords.push_back(wxPoint(26, 18)); // yellow square
coords.push_back(wxPoint(25, 5)); // empty / tranparent
for ( size_t i = 0; i < coords.size(); ++i )
{
wxPoint const& p1 = coords[i];
wxPoint p2 = wxPoint(p1.x * (result.GetWidth() / (double)image.GetWidth()), p1.y * (result.GetHeight() / (double)image.GetHeight()));
#if defined(__WXMSW__)
// when the cursor / result image is larger than the source image, the original image is centered in the result image
if ( result.GetWidth() > image.GetWidth() )
p2.x = (result.GetWidth() / 2) + (p1.x - (image.GetWidth() / 2));
if ( result.GetHeight() > image.GetHeight() )
p2.y = (result.GetHeight() / 2) + (p1.y - (image.GetHeight() / 2));
#endif
wxColour cSrc(image.GetRed(p1.x, p1.y), image.GetGreen(p1.x, p1.y), image.GetBlue(p1.x, p1.y), image.GetAlpha(p1.x, p1.y));
wxColour cRes(result.GetRed(p2.x, p2.y), result.GetGreen(p2.x, p2.y), result.GetBlue(p2.x, p2.y), result.GetAlpha(p2.x, p2.y));
CHECK_EQUAL_COLOUR_RGBA(cRes, cSrc);
}
}
#endif
}
// This function assumes that the file is malformed in a way that it cannot
// be loaded but that doesn't fail a wxCHECK.
static void LoadMalformedImage(const wxString& file, wxBitmapType type)
{
// If the file doesn't exist, it's a test bug.
// (The product code would fail but for the wrong reason.)
INFO("Checking that image exists: " << file);
REQUIRE(wxFileExists(file));
// Load the image, expecting a failure.
wxImage image;
INFO("Loading malformed image: " << file);
REQUIRE(!image.LoadFile(file, type));
}
// This function assumes that the file is malformed in a way that wxImage
// fails a wxCHECK when trying to load it.
static void LoadMalformedImageWithException(const wxString& file,
wxBitmapType type)
{
// If the file doesn't exist, it's a test bug.
// (The product code would fail but for the wrong reason.)
INFO("Checking that image exists: " << file);
REQUIRE(wxFileExists(file));
wxImage image;
INFO("Loading malformed image: " << file);
#ifdef __WXDEBUG__
REQUIRE_THROWS(image.LoadFile(file, type));
#else
REQUIRE(!image.LoadFile(file, type));
#endif
}
TEST_CASE_METHOD(ImageHandlersInit, "wxImage::BMP", "[image][bmp]")
{
SECTION("Load malformed bitmaps")
{
LoadMalformedImage("image/8bpp-colorsused-negative.bmp",
wxBITMAP_TYPE_BMP);
LoadMalformedImage("image/8bpp-colorsused-large.bmp",
wxBITMAP_TYPE_BMP);
LoadMalformedImageWithException("image/width-times-height-overflow.bmp",
wxBITMAP_TYPE_BMP);
}
}
TEST_CASE_METHOD(ImageHandlersInit, "wxImage::Paste", "[image][paste]")
{
const static char* squares_xpm[] =
{
"9 9 7 1",
" c None",
"y c #FFFF00",
"r c #FF0000",
"g c #00FF00",
"b c #0000FF",
"o c #FF6600",
"w c #FFFFFF",
"rrrrwgggg",
"rrrrwgggg",
"rrrrwgggg",
"rrrrwgggg",
"wwwwwwwww",
"bbbbwoooo",
"bbbbwoooo",
"bbbbwoooo",
"bbbbwoooo"
};
const static char* toggle_equal_size_xpm[] =
{
"9 9 2 1",
" c None",
"y c #FFFF00",
"y y y y y",
" y y y y ",
"y y y y y",
" y y y y ",
"y y y y y",
" y y y y ",
"y y y y y",
" y y y y ",
"y y y y y",
};
const static char* transparent_image_xpm[] =
{
"5 5 2 1",
" c None", // Mask
"y c #FFFF00",
" ",
" ",
" ",
" ",
" ",
};
const static char* light_image_xpm[] =
{
"5 5 2 1",
" c None",
"y c #FFFF00",
"yyyyy",
"yyyyy",
"yyyyy",
"yyyyy",
"yyyyy",
};
const static char* black_image_xpm[] =
{
"5 5 2 1",
" c #000000",
"y c None", // Mask
" ",
" ",
" ",
" ",
" ",
};
// Execute AddHandler() just once.
static bool s_registeredHandler = false;
if ( !s_registeredHandler )
{
wxImage::AddHandler(new wxPNGHandler());
s_registeredHandler = true;
}
SECTION("Paste same size image")
{
wxImage actual(squares_xpm);
wxImage paste(toggle_equal_size_xpm);
wxImage expected(toggle_equal_size_xpm);
actual.Paste(paste, 0, 0);
CHECK_THAT(actual, RGBSameAs(expected));
// Without alpha using "compose" doesn't change anything.
actual.Paste(paste, 0, 0, wxIMAGE_ALPHA_BLEND_COMPOSE);
CHECK_THAT(actual, RGBSameAs(expected));
}
SECTION("Paste larger image")
{
const static char* toggle_larger_size_xpm[] =
{
"13 13 2 1",
" c None",
"y c #FFFF00",
"y y y y y y y",
" y y y y y y ",
"y y y y y y y",
" y y y y y y ",
"y y y y y y y",
" y y y y y y ",
"y y y y y y y",
" y y y y y y ",
"y y y y y y y",
" y y y y y y ",
"y y y y y y y",
" y y y y y y ",
"y y y y y y y",
};
wxImage actual(squares_xpm);
wxImage paste(toggle_larger_size_xpm);
wxImage expected(toggle_equal_size_xpm);
actual.Paste(paste, -2, -2);
CHECK_THAT(actual, RGBSameAs(expected));
}
SECTION("Paste smaller image")
{
const static char* toggle_smaller_size_xpm[] =
{
"5 5 2 1",
" c None",
"y c #FFFF00",
"y y y",
" y y ",
"y y y",
" y y ",
"y y y",
};
const static char* expected_xpm[] =
{
"9 9 7 1",
" c None",
"y c #FFFF00",
"r c #FF0000",
"g c #00FF00",
"b c #0000FF",
"o c #FF6600",
"w c #FFFFFF",
"rrrrwgggg",
"rrrrwgggg",
"rry y ygg",
"rr y y gg",
"wwy y yww",
"bb y y oo",
"bby y yoo",
"bbbbwoooo",
"bbbbwoooo"
};
wxImage actual(squares_xpm);
wxImage paste(toggle_smaller_size_xpm);
wxImage expected(expected_xpm);
actual.Paste(paste, 2, 2);
CHECK_THAT(actual, RGBSameAs(expected));
}
SECTION("Paste beyond top left corner")
{
const static char* expected_xpm[] =
{
"9 9 7 1",
" c None",
"y c #FFFF00",
"r c #FF0000",
"g c #00FF00",
"b c #0000FF",
"o c #FF6600",
"w c #FFFFFF",
"oooowgggg",
"oooowgggg",
"oooowgggg",
"oooowgggg",
"wwwwwwwww",
"bbbbwoooo",
"bbbbwoooo",
"bbbbwoooo",
"bbbbwoooo"
};
wxImage actual(squares_xpm);
wxImage paste(squares_xpm);
wxImage expected(expected_xpm);
actual.Paste(paste, -5, -5);
CHECK_THAT(actual, RGBSameAs(expected));
}
SECTION("Paste beyond top right corner")
{
const static char* expected_xpm[] =
{
"9 9 7 1",
" c None",
"y c #FFFF00",
"r c #FF0000",
"g c #00FF00",
"b c #0000FF",
"o c #FF6600",
"w c #FFFFFF",
"rrrrwbbbb",
"rrrrwbbbb",
"rrrrwbbbb",
"rrrrwbbbb",
"wwwwwwwww",
"bbbbwoooo",
"bbbbwoooo",
"bbbbwoooo",
"bbbbwoooo"
};
wxImage actual(squares_xpm);
wxImage paste(squares_xpm);
wxImage expected(expected_xpm);
actual.Paste(paste, 5, -5);
CHECK_THAT(actual, RGBSameAs(expected));
}
SECTION("Paste beyond bottom right corner")
{
const static char* expected_xpm[] =
{
"9 9 7 1",
" c None",
"y c #FF0000",
"r c #FF0000",
"g c #00FF00",
"b c #0000FF",
"o c #FF6600",
"w c #FFFFFF",
"rrrrwgggg",
"rrrrwgggg",
"rrrrwgggg",
"rrrrwgggg",
"wwwwwwwww",
"bbbbwrrrr",
"bbbbwrrrr",
"bbbbwrrrr",
"bbbbwrrrr"
};
wxImage actual(squares_xpm);
wxImage paste(squares_xpm);
wxImage expected(expected_xpm);
actual.Paste(paste, 5, 5);
CHECK_THAT(actual, RGBSameAs(expected));
}
SECTION("Paste beyond bottom left corner")
{
const static char* expected_xpm[] =
{
"9 9 7 1",
" c None",
"y c #FFFF00",
"r c #FF0000",
"g c #00FF00",
"b c #0000FF",
"o c #FF6600",
"w c #FFFFFF",
"rrrrwgggg",
"rrrrwgggg",
"rrrrwgggg",
"rrrrwgggg",
"wwwwwwwww",
"ggggwoooo",
"ggggwoooo",
"ggggwoooo",
"ggggwoooo"
};
wxImage actual(squares_xpm);
wxImage paste(squares_xpm);
wxImage expected(expected_xpm);
actual.Paste(paste, -5, 5);
CHECK_THAT(actual, RGBSameAs(expected));
}
SECTION("Paste fully opaque image onto blank image without alpha")
{
const wxImage background("image/paste_input_background.png");
REQUIRE(background.IsOk());
wxImage actual(background.GetSize());
actual.Paste(background, 0, 0, wxIMAGE_ALPHA_BLEND_COMPOSE);
CHECK_THAT(actual, RGBSameAs(background));
CHECK(!actual.HasAlpha());
}
SECTION("Paste fully opaque image onto blank image with alpha")
{
const wxImage background("image/paste_input_background.png");
REQUIRE(background.IsOk());
wxImage actual(background.GetSize());
actual.InitAlpha();
actual.Paste(background, 0, 0, wxIMAGE_ALPHA_BLEND_COMPOSE);
CHECK_THAT(actual, RGBSameAs(background));
CHECK_THAT(actual, CenterAlphaPixelEquals(wxALPHA_OPAQUE));
}
SECTION("Paste fully transparent image")
{
const wxImage background("image/paste_input_background.png");
REQUIRE(background.IsOk());
wxImage actual = background.Copy();
wxImage transparent(actual.GetSize());
transparent.InitAlpha();
memset(transparent.GetAlpha(), 0, transparent.GetWidth() * transparent.GetHeight());
actual.Paste(transparent, 0, 0, wxIMAGE_ALPHA_BLEND_COMPOSE);
CHECK_THAT(actual, RGBSameAs(background));
CHECK_THAT(actual, CenterAlphaPixelEquals(wxALPHA_OPAQUE));
}
SECTION("Paste image with transparent region")
{
wxImage actual("image/paste_input_background.png");
REQUIRE(actual.IsOk());
const wxImage opaque_square("image/paste_input_overlay_transparent_border_opaque_square.png");
REQUIRE(opaque_square.IsOk());
actual.Paste(opaque_square, 0, 0, wxIMAGE_ALPHA_BLEND_COMPOSE);
CHECK_THAT(actual, RGBSameAs(wxImage("image/paste_result_background_plus_overlay_transparent_border_opaque_square.png")));
CHECK_THAT(actual, CenterAlphaPixelEquals(wxALPHA_OPAQUE));
}
SECTION("Paste image with semi transparent region")
{
wxImage actual("image/paste_input_background.png");
REQUIRE(actual.IsOk());
const wxImage transparent_square("image/paste_input_overlay_transparent_border_semitransparent_square.png");
REQUIRE(transparent_square.IsOk());
actual.Paste(transparent_square, 0, 0, wxIMAGE_ALPHA_BLEND_COMPOSE);
CHECK_THAT(actual, RGBSameAs(wxImage("image/paste_result_background_plus_overlay_transparent_border_semitransparent_square.png")));
CHECK_THAT(actual, CenterAlphaPixelEquals(wxALPHA_OPAQUE));
}
SECTION("Paste two semi transparent images on top of background")
{
wxImage actual("image/paste_input_background.png");
REQUIRE(actual.IsOk());
const wxImage transparent_square("image/paste_input_overlay_transparent_border_semitransparent_square.png");
REQUIRE(transparent_square.IsOk());
const wxImage transparent_circle("image/paste_input_overlay_transparent_border_semitransparent_circle.png");
REQUIRE(transparent_circle.IsOk());
actual.Paste(transparent_circle, 0, 0, wxIMAGE_ALPHA_BLEND_COMPOSE);
actual.Paste(transparent_square, 0, 0, wxIMAGE_ALPHA_BLEND_COMPOSE);
CHECK_THAT(actual, RGBSimilarTo(wxImage("image/paste_result_background_plus_circle_plus_square.png"), 1));
CHECK_THAT(actual, CenterAlphaPixelEquals(wxALPHA_OPAQUE));
}
SECTION("Paste two semi transparent images together first, then on top of background")
{
wxImage actual("image/paste_input_background.png");
REQUIRE(actual.IsOk());
const wxImage transparent_square("image/paste_input_overlay_transparent_border_semitransparent_square.png");
REQUIRE(transparent_square.IsOk());
const wxImage transparent_circle("image/paste_input_overlay_transparent_border_semitransparent_circle.png");
REQUIRE(transparent_circle.IsOk());
wxImage circle = transparent_circle.Copy();
circle.Paste(transparent_square, 0, 0, wxIMAGE_ALPHA_BLEND_COMPOSE);
actual.Paste(circle, 0, 0, wxIMAGE_ALPHA_BLEND_COMPOSE);
// When applied in this order, two times a rounding difference is triggered.
CHECK_THAT(actual, RGBSimilarTo(wxImage("image/paste_result_background_plus_circle_plus_square.png"), 2));
CHECK_THAT(actual, CenterAlphaPixelEquals(wxALPHA_OPAQUE));
}
SECTION("Paste semitransparent image over transparent image")
{
const wxImage transparent_square("image/paste_input_overlay_transparent_border_semitransparent_square.png");
REQUIRE(transparent_square.IsOk());
const wxImage transparent_circle("image/paste_input_overlay_transparent_border_semitransparent_circle.png");
REQUIRE(transparent_circle.IsOk());
wxImage actual(transparent_circle.GetSize());
actual.InitAlpha();
memset(actual.GetAlpha(), 0, actual.GetWidth() * actual.GetHeight());
actual.Paste(transparent_circle, 0, 0, wxIMAGE_ALPHA_BLEND_COMPOSE);
CHECK_THAT(actual, CenterAlphaPixelEquals(192));
actual.Paste(transparent_square, 0, 0, wxIMAGE_ALPHA_BLEND_COMPOSE);
CHECK_THAT(actual, RGBSimilarTo(wxImage("image/paste_result_no_background_square_over_circle.png"), 1));
CHECK_THAT(actual, CenterAlphaPixelEquals(224));
}
SECTION("Paste fully transparent (masked) image over light image") // todo make test case for 'blend with mask'
{
wxImage actual(light_image_xpm);
actual.InitAlpha();
wxImage paste(transparent_image_xpm);
wxImage expected(light_image_xpm);
actual.Paste(paste, 0, 0, wxIMAGE_ALPHA_BLEND_COMPOSE);
CHECK_THAT(actual, RGBSameAs(expected));
}
SECTION("Paste fully black (masked) image over light image") // todo make test case for 'blend with mask'
{
wxImage actual(light_image_xpm);
actual.InitAlpha();
wxImage paste(black_image_xpm);
wxImage expected(black_image_xpm);
actual.Paste(paste, 0, 0, wxIMAGE_ALPHA_BLEND_COMPOSE);
CHECK_THAT(actual, RGBSameAs(expected));
}
SECTION("Paste dark image over light image")
{
wxImage black("image/paste_input_black.png");
wxImage actual("image/paste_input_background.png");
actual.InitAlpha();
actual.Paste(black, 0, 0, wxIMAGE_ALPHA_BLEND_COMPOSE);
CHECK_THAT(actual, CenterAlphaPixelEquals(255));
CHECK_THAT(actual, RGBSameAs(black));
}
SECTION("Paste large image with negative vertical offset")
{
wxImage target(442, 249);
wxImage to_be_pasted(345, 24900);
target.InitAlpha();
target.Paste(to_be_pasted, 48, -12325, wxIMAGE_ALPHA_BLEND_COMPOSE);
}
SECTION("Paste large image with negative horizontal offset")
{
wxImage target(249, 442);
wxImage to_be_pasted(24900, 345);
target.InitAlpha();
target.Paste(to_be_pasted, -12325, 48, wxIMAGE_ALPHA_BLEND_COMPOSE);
}
}
TEST_CASE("wxImage::RGBtoHSV", "[image][rgb][hsv]")
{
SECTION("RGB(0,0,0) (Black) to HSV")
{
wxImage::RGBValue rgbBlack(0, 0, 0);
wxImage::HSVValue hsvBlack = wxImage::RGBtoHSV(rgbBlack);
CHECK(hsvBlack.value == Approx(0.0));
// saturation and hue are undefined
}
SECTION("RGB(255,255,255) (White) to HSV")
{
wxImage::RGBValue rgbWhite(255, 255, 255);
wxImage::HSVValue hsvWhite = wxImage::RGBtoHSV(rgbWhite);
CHECK(hsvWhite.saturation == Approx(0.0));
CHECK(hsvWhite.value == Approx(1.0));
// hue is undefined
}
SECTION("RGB(0,255,0) (Green) to HSV")
{
wxImage::RGBValue rgbGreen(0, 255, 0);
wxImage::HSVValue hsvGreen = wxImage::RGBtoHSV(rgbGreen);
CHECK(hsvGreen.hue == Approx(1.0/3.0));
CHECK(hsvGreen.saturation == Approx(1.0));
CHECK(hsvGreen.value == Approx(1.0));
}
SECTION("RGB to HSV to RGB")
{
const wxImage::RGBValue rgbValues[] =
{
wxImage::RGBValue( 0, 0, 0 ),
wxImage::RGBValue( 10, 10, 10 ),
wxImage::RGBValue( 255, 255, 255 ),
wxImage::RGBValue( 255, 0, 0 ),
wxImage::RGBValue( 0, 255, 0 ),
wxImage::RGBValue( 0, 0, 255 ),
wxImage::RGBValue( 1, 2, 3 ),
wxImage::RGBValue( 10, 20, 30 ),
wxImage::RGBValue( 0, 1, 6 ),
wxImage::RGBValue( 9, 0, 99 ),
};
for (unsigned i = 0; i < WXSIZEOF(rgbValues); i++)
{
wxImage::RGBValue rgbValue = rgbValues[i];
wxImage::HSVValue hsvValue = wxImage::RGBtoHSV(rgbValue);
wxImage::RGBValue rgbRoundtrip = wxImage::HSVtoRGB(hsvValue);
CHECK(rgbRoundtrip.red == rgbValue.red);
CHECK(rgbRoundtrip.green == rgbValue.green);
CHECK(rgbRoundtrip.blue == rgbValue.blue);
}
}
}
TEST_CASE_METHOD(ImageHandlersInit, "wxImage::Clipboard", "[image][clipboard]")
{
#if wxUSE_CLIPBOARD && wxUSE_DATAOBJ
wxInitAllImageHandlers();
wxImage imgOriginal;
REQUIRE(imgOriginal.LoadFile("horse.png") == true);
wxImageDataObject* dobj1 = new wxImageDataObject(imgOriginal);
{
wxClipboardLocker lockClip;
REQUIRE(wxTheClipboard->SetData(dobj1) == true);
}
wxImageDataObject dobj2;
{
wxClipboardLocker lockClip;
REQUIRE(wxTheClipboard->GetData(dobj2) == true);
}
wxImage imgRetrieved = dobj2.GetImage();
REQUIRE(imgRetrieved.IsOk());
CHECK_THAT(imgOriginal, RGBASameAs(imgRetrieved));
#endif // wxUSE_CLIPBOARD && wxUSE_DATAOBJ
}
TEST_CASE("wxImage::InitAlpha", "[image][initalpha]")
{
const wxColour maskCol(*wxRED);
const wxColour fillCol(*wxGREEN);
SECTION("RGB image without mask")
{
wxImage img(2, 2);
img.SetRGB(0, 0, maskCol.Red(), maskCol.Green(), maskCol.Blue());
img.SetRGB(0, 1, maskCol.Red(), maskCol.Green(), maskCol.Blue());
img.SetRGB(1, 0, fillCol.Red(), fillCol.Green(), fillCol.Blue());
img.SetRGB(1, 1, fillCol.Red(), fillCol.Green(), fillCol.Blue());
REQUIRE_FALSE(img.HasAlpha());
REQUIRE_FALSE(img.HasMask());
wxImage imgRes = img;
imgRes.InitAlpha();
REQUIRE(imgRes.HasAlpha() == true);
REQUIRE_FALSE(imgRes.HasMask());
for (int y = 0; y < img.GetHeight(); y++)
for (int x = 0; x < img.GetWidth(); x++)
{
wxColour cSrc(img.GetRed(x, y), img.GetGreen(x, y), img.GetBlue(x, y));
wxColour cRes(imgRes.GetRed(x, y), imgRes.GetGreen(x, y), imgRes.GetBlue(x, y), imgRes.GetAlpha(x, y));
CHECK_EQUAL_COLOUR_RGB(cRes, cSrc);
CHECK((int)cRes.Alpha() == (int)wxIMAGE_ALPHA_OPAQUE);
}
}
SECTION("RGB image with mask")
{
wxImage img(2, 2);
img.SetRGB(0, 0, maskCol.Red(), maskCol.Green(), maskCol.Blue());
img.SetRGB(0, 1, maskCol.Red(), maskCol.Green(), maskCol.Blue());
img.SetRGB(1, 0, fillCol.Red(), fillCol.Green(), fillCol.Blue());
img.SetRGB(1, 1, fillCol.Red(), fillCol.Green(), fillCol.Blue());
img.SetMaskColour(maskCol.Red(), maskCol.Green(), maskCol.Blue());
REQUIRE_FALSE(img.HasAlpha());
REQUIRE(img.HasMask() == true);
wxImage imgRes = img;
imgRes.InitAlpha();
REQUIRE(imgRes.HasAlpha() == true);
REQUIRE_FALSE(imgRes.HasMask());
for ( int y = 0; y < img.GetHeight(); y++ )
for ( int x = 0; x < img.GetWidth(); x++ )
{
wxColour cSrc(img.GetRed(x, y), img.GetGreen(x, y), img.GetBlue(x, y));
wxColour cRes(imgRes.GetRed(x, y), imgRes.GetGreen(x, y), imgRes.GetBlue(x, y), imgRes.GetAlpha(x, y));
CHECK_EQUAL_COLOUR_RGB(cRes, cSrc);
if ( cSrc == maskCol )
{
CHECK((int)cRes.Alpha() == (int)wxIMAGE_ALPHA_TRANSPARENT);
}
else
{
CHECK((int)cRes.Alpha() == (int)wxIMAGE_ALPHA_OPAQUE);
}
}
}
SECTION("RGBA image without mask")
{
wxImage img(2, 2);
img.SetRGB(0, 0, maskCol.Red(), maskCol.Green(), maskCol.Blue());
img.SetRGB(0, 1, maskCol.Red(), maskCol.Green(), maskCol.Blue());
img.SetRGB(1, 0, fillCol.Red(), fillCol.Green(), fillCol.Blue());
img.SetRGB(1, 1, fillCol.Red(), fillCol.Green(), fillCol.Blue());
img.SetAlpha();
img.SetAlpha(0, 0, 128);
img.SetAlpha(0, 1, 0);
img.SetAlpha(1, 0, 128);
img.SetAlpha(1, 1, 0);
REQUIRE(img.HasAlpha() == true);
REQUIRE_FALSE(img.HasMask());
wxImage imgRes = img;
#ifdef __WXDEBUG__
CHECK_THROWS(imgRes.InitAlpha());
#else
imgRes.InitAlpha();
#endif
REQUIRE(imgRes.HasAlpha() == true);
REQUIRE_FALSE(imgRes.HasMask());
for ( int y = 0; y < img.GetHeight(); y++ )
for ( int x = 0; x < img.GetWidth(); x++ )
{
wxColour cSrc(img.GetRed(x, y), img.GetGreen(x, y), img.GetBlue(x, y), img.GetAlpha(x, y));
wxColour cRes(imgRes.GetRed(x, y), imgRes.GetGreen(x, y), imgRes.GetBlue(x, y), imgRes.GetAlpha(x, y));
CHECK_EQUAL_COLOUR_RGBA(cRes, cSrc);
}
}
SECTION("RGBA image with mask")
{
wxImage img(2, 2);
img.SetRGB(0, 0, maskCol.Red(), maskCol.Green(), maskCol.Blue());
img.SetRGB(0, 1, maskCol.Red(), maskCol.Green(), maskCol.Blue());
img.SetRGB(1, 0, fillCol.Red(), fillCol.Green(), fillCol.Blue());
img.SetRGB(1, 1, fillCol.Red(), fillCol.Green(), fillCol.Blue());
img.SetAlpha();
img.SetAlpha(0, 0, 128);
img.SetAlpha(0, 1, 0);
img.SetAlpha(1, 0, 128);
img.SetAlpha(1, 1, 0);
img.SetMaskColour(maskCol.Red(), maskCol.Green(), maskCol.Blue());
REQUIRE(img.HasAlpha() == true);
REQUIRE(img.HasMask() == true);
wxImage imgRes = img;
#ifdef __WXDEBUG__
CHECK_THROWS(imgRes.InitAlpha());
#else
imgRes.InitAlpha();
#endif
REQUIRE(imgRes.HasAlpha() == true);
REQUIRE(imgRes.HasMask() == true);
for ( int y = 0; y < img.GetHeight(); y++ )
for ( int x = 0; x < img.GetWidth(); x++ )
{
wxColour cSrc(img.GetRed(x, y), img.GetGreen(x, y), img.GetBlue(x, y), img.GetAlpha(x, y));
wxColour cRes(imgRes.GetRed(x, y), imgRes.GetGreen(x, y), imgRes.GetBlue(x, y), imgRes.GetAlpha(x, y));
CHECK_EQUAL_COLOUR_RGBA(cRes, cSrc);
}
}
}
TEST_CASE("wxImage::XPM", "[image][xpm]")
{
static const char * dummy_xpm[] = {
"16 16 2 1",
"@ c Black",
" c None",
"@ ",
" @ ",
" @ ",
" @ ",
" @ ",
" @ ",
" @ ",
" @ ",
" @ ",
" @ ",
" @ ",
" @ ",
" @ ",
" @ ",
" @ ",
" @"
};
wxImage image(dummy_xpm);
CHECK( image.IsOk() );
// The goal here is mostly just to check that this code compiles, i.e. that
// creating all these classes from XPM works.
CHECK( wxBitmap(dummy_xpm).IsOk() );
CHECK( wxCursor(dummy_xpm).IsOk() );
CHECK( wxIcon(dummy_xpm).IsOk() );
}
TEST_CASE_METHOD(ImageHandlersInit, "wxImage::PNM", "[image][pnm]")
{
#if wxUSE_PNM
wxImage::AddHandler(new wxPNMHandler);
SECTION("width*height*3 overflow")
{
// wxImage should reject the file as malformed (wxTrac#19326)
LoadMalformedImageWithException("image/width_height_32_bit_overflow.pgm",
wxBITMAP_TYPE_PNM);
}
#endif
}
namespace
{
inline ImageRGBMatcher RGBSimilarToFile(const char* name)
{
INFO("Loading file from " << name);
wxImage expected;
REQUIRE(expected.LoadFile(name));
return ImageRGBMatcher(expected, 1);
}
} // anonymous namespace
TEST_CASE_METHOD(ImageHandlersInit, "wxImage::ChangeColours", "[image]")
{
wxImage original;
REQUIRE(original.LoadFile("image/toucan.png", wxBITMAP_TYPE_PNG));
wxImage test;
wxImage expected;
test = original;
test.RotateHue(0.538);
CHECK_THAT(test, RGBSimilarToFile("image/toucan_hue_0.538.png"));
test = original;
test.ChangeSaturation(-0.41);
CHECK_THAT(test, RGBSimilarToFile("image/toucan_sat_-0.41.png"));
test = original;
test.ChangeBrightness(-0.259);
CHECK_THAT(test, RGBSimilarToFile("image/toucan_bright_-0.259.png"));
test = original;
test.ChangeHSV(0.538, -0.41, -0.259);
CHECK_THAT(test, RGBSimilarToFile("image/toucan_hsv_0.538_-0.41_-0.259.png"));
test = original;
test = test.ChangeLightness(46);
CHECK_THAT(test, RGBSimilarToFile("image/toucan_light_46.png"));
test = original;
test = test.ConvertToDisabled(240);
CHECK_THAT(test, RGBSimilarToFile("image/toucan_dis_240.png"));
test = original;
test = test.ConvertToGreyscale();
CHECK_THAT(test, RGBSimilarToFile("image/toucan_grey.png"));
test = original;
test = test.ConvertToMono(255, 255, 255);
CHECK_THAT(test, RGBSimilarToFile("image/toucan_mono_255_255_255.png"));
}
TEST_CASE("wxImage::Clear", "[image]")
{
wxImage image(2, 2);
image.SetRGB(0, 0, 0xff, 0x00, 0x00);
image.SetRGB(0, 1, 0x00, 0xff, 0x00);
image.SetRGB(1, 0, 0x00, 0x00, 0xff);
image.SetRGB(1, 1, 0xff, 0xff, 0xff);
wxImage image2{image};
// Check that the image has the expected red component values initially.
CHECK( image2.GetRed(0, 0) == 0xff );
CHECK( image2.GetRed(0, 1) == 0x00 );
CHECK( image2.GetRed(1, 0) == 0x00 );
CHECK( image2.GetRed(1, 1) == 0xff );
// Check that the image got cleared.
image2.Clear();
CHECK( image2.GetRed(0, 0) == 0x00 );
CHECK( image2.GetRed(0, 1) == 0x00 );
CHECK( image2.GetRed(1, 0) == 0x00 );
CHECK( image2.GetRed(1, 1) == 0x00 );
// Check that the original image didn't change (see #23553).
CHECK( image.GetRed(0, 0) == 0xff );
CHECK( image.GetRed(1, 1) == 0xff );
}
TEST_CASE("wxImage::SizeLimits", "[image]")
{
#if SIZEOF_VOID_P == 8
// Check that we can resample an image of size greater than 2^16, which is
// the limit used in 32-bit code to avoid integer overflows.
wxImage image(100000, 2);
REQUIRE_NOTHROW( image = image.ResampleNearest(100000, 1) );
CHECK( image.GetWidth() == 100000 );
CHECK( image.GetHeight() == 1 );
#endif // SIZEOF_VOID_P == 8
}
// This can be used to test loading an arbitrary image file by setting the
// environment variable WX_TEST_IMAGE_PATH to point to it.
TEST_CASE_METHOD(ImageHandlersInit, "wxImage::LoadPath", "[.]")
{
wxString path;
REQUIRE( wxGetEnv("WX_TEST_IMAGE_PATH", &path) );
TestLogEnabler enableLogs;
wxImage image;
REQUIRE( image.LoadFile(path) );
WARN("Image "
<< image.GetWidth()
<< "*"
<< image.GetHeight()
<< (image.HasAlpha() ? " with alpha" : "")
<< " loaded");
}
/*
TODO: add lots of more tests to wxImage functions
*/
#endif //wxUSE_IMAGE