Add alpha blending for wxImage::Paste
Add test cases for wxImage::Paste. Closes #12458. Co-Authored-By: Rachel Mark <kramdar@gmail.com>
@@ -73,6 +73,16 @@ enum wxImageResizeQuality
|
|||||||
wxIMAGE_QUALITY_HIGH = 4
|
wxIMAGE_QUALITY_HIGH = 4
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Constants for wxImage::Paste() for specifying alpha blending option.
|
||||||
|
enum wxImageAlphaBlendMode
|
||||||
|
{
|
||||||
|
// Overwrite the original alpha values with the ones being pasted.
|
||||||
|
wxIMAGE_ALPHA_BLEND_OVER = 0,
|
||||||
|
|
||||||
|
// Compose the original alpha values with the ones being pasted.
|
||||||
|
wxIMAGE_ALPHA_BLEND_COMPOSE = 1
|
||||||
|
};
|
||||||
|
|
||||||
// alpha channel values: fully transparent, default threshold separating
|
// alpha channel values: fully transparent, default threshold separating
|
||||||
// transparent pixels from opaque for a few functions dealing with alpha and
|
// transparent pixels from opaque for a few functions dealing with alpha and
|
||||||
// fully opaque
|
// fully opaque
|
||||||
@@ -348,9 +358,12 @@ public:
|
|||||||
wxImage Size( const wxSize& size, const wxPoint& pos,
|
wxImage Size( const wxSize& size, const wxPoint& pos,
|
||||||
int r = -1, int g = -1, int b = -1 ) const;
|
int r = -1, int g = -1, int b = -1 ) const;
|
||||||
|
|
||||||
// pastes image into this instance and takes care of
|
// Copy the data of the given image to the specified position of this one
|
||||||
// the mask colour and out of bounds problems
|
// taking care of the out of bounds problems. Mask is respected, but alpha
|
||||||
void Paste( const wxImage &image, int x, int y );
|
// is simply replaced by default, use wxIMAGE_ALPHA_BLEND_COMPOSE to
|
||||||
|
// combine it with the original image alpha values if needed.
|
||||||
|
void Paste(const wxImage& image, int x, int y,
|
||||||
|
wxImageAlphaBlendMode alphaBlend = wxIMAGE_ALPHA_BLEND_OVER);
|
||||||
|
|
||||||
// return the new image with size width*height
|
// return the new image with size width*height
|
||||||
wxImage Scale( int width, int height,
|
wxImage Scale( int width, int height,
|
||||||
|
@@ -60,6 +60,21 @@ enum wxImageResizeQuality
|
|||||||
wxIMAGE_QUALITY_HIGH
|
wxIMAGE_QUALITY_HIGH
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Constants for wxImage::Paste() for specifying alpha blending option.
|
||||||
|
|
||||||
|
@since 3.2.0
|
||||||
|
*/
|
||||||
|
enum wxImageAlphaBlendMode
|
||||||
|
{
|
||||||
|
/// Overwrite the original alpha values with the ones being pasted.
|
||||||
|
wxIMAGE_ALPHA_BLEND_OVER = 0,
|
||||||
|
|
||||||
|
/// Compose the original alpha values with the ones being pasted.
|
||||||
|
wxIMAGE_ALPHA_BLEND_COMPOSE = 1
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Possible values for PNG image type option.
|
Possible values for PNG image type option.
|
||||||
|
|
||||||
@@ -803,8 +818,17 @@ public:
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
Copy the data of the given @a image to the specified position in this image.
|
Copy the data of the given @a image to the specified position in this image.
|
||||||
|
|
||||||
|
Takes care of the mask colour and out of bounds problems.
|
||||||
|
|
||||||
|
@param alphaBlend
|
||||||
|
This parameter (new in wx 3.2.0) determines whether the alpha values
|
||||||
|
of the original image replace (default) or are composed with the
|
||||||
|
alpha channel of this image. Notice that alpha blending overrides
|
||||||
|
the mask handling.
|
||||||
*/
|
*/
|
||||||
void Paste(const wxImage& image, int x, int y);
|
void Paste(const wxImage& image, int x, int y,
|
||||||
|
wxImageAlphaBlendMode alphaBlend = wxIMAGE_ALPHA_BLEND_OVER);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Replaces the colour specified by @e r1,g1,b1 by the colour @e r2,g2,b2.
|
Replaces the colour specified by @e r1,g1,b1 by the colour @e r2,g2,b2.
|
||||||
|
@@ -1608,7 +1608,9 @@ wxImage wxImage::Size( const wxSize& size, const wxPoint& pos,
|
|||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
void wxImage::Paste( const wxImage &image, int x, int y )
|
void
|
||||||
|
wxImage::Paste(const wxImage & image, int x, int y,
|
||||||
|
wxImageAlphaBlendMode alphaBlend)
|
||||||
{
|
{
|
||||||
wxCHECK_RET( IsOk(), wxT("invalid image") );
|
wxCHECK_RET( IsOk(), wxT("invalid image") );
|
||||||
wxCHECK_RET( image.IsOk(), wxT("invalid image") );
|
wxCHECK_RET( image.IsOk(), wxT("invalid image") );
|
||||||
@@ -1639,15 +1641,18 @@ void wxImage::Paste( const wxImage &image, int x, int y )
|
|||||||
if (width < 1) return;
|
if (width < 1) return;
|
||||||
if (height < 1) return;
|
if (height < 1) return;
|
||||||
|
|
||||||
|
bool copiedPixels = false;
|
||||||
|
|
||||||
// If we can, copy the data using memcpy() as this is the fastest way. But
|
// If we can, copy the data using memcpy() as this is the fastest way. But
|
||||||
// for this the image being pasted must have "compatible" mask with this
|
// for this we must not do alpha compositing and the image being pasted
|
||||||
// one meaning that either it must not have one at all or it must use the
|
// must have "compatible" mask with this one meaning that either it must
|
||||||
// same masked colour.
|
// not have one at all or it must use the same masked colour.
|
||||||
if ( !image.HasMask() ||
|
if (alphaBlend == wxIMAGE_ALPHA_BLEND_OVER &&
|
||||||
|
(!image.HasMask() ||
|
||||||
((HasMask() &&
|
((HasMask() &&
|
||||||
(GetMaskRed()==image.GetMaskRed()) &&
|
(GetMaskRed()==image.GetMaskRed()) &&
|
||||||
(GetMaskGreen()==image.GetMaskGreen()) &&
|
(GetMaskGreen()==image.GetMaskGreen()) &&
|
||||||
(GetMaskBlue()==image.GetMaskBlue()))) )
|
(GetMaskBlue()==image.GetMaskBlue())))) )
|
||||||
{
|
{
|
||||||
const unsigned char* source_data = image.GetData() + 3*(xx + yy*image.GetWidth());
|
const unsigned char* source_data = image.GetData() + 3*(xx + yy*image.GetWidth());
|
||||||
int source_step = image.GetWidth()*3;
|
int source_step = image.GetWidth()*3;
|
||||||
@@ -1660,6 +1665,8 @@ void wxImage::Paste( const wxImage &image, int x, int y )
|
|||||||
source_data += source_step;
|
source_data += source_step;
|
||||||
target_data += target_step;
|
target_data += target_step;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
copiedPixels = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy over the alpha channel from the original image
|
// Copy over the alpha channel from the original image
|
||||||
@@ -1668,21 +1675,69 @@ void wxImage::Paste( const wxImage &image, int x, int y )
|
|||||||
if ( !HasAlpha() )
|
if ( !HasAlpha() )
|
||||||
InitAlpha();
|
InitAlpha();
|
||||||
|
|
||||||
const unsigned char* source_data = image.GetAlpha() + xx + yy*image.GetWidth();
|
const unsigned char*
|
||||||
int source_step = image.GetWidth();
|
alpha_source_data = image.GetAlpha() + xx + yy * image.GetWidth();
|
||||||
|
const int source_step = image.GetWidth();
|
||||||
|
|
||||||
unsigned char* target_data = GetAlpha() + (x+xx) + (y+yy)*M_IMGDATA->m_width;
|
unsigned char*
|
||||||
int target_step = M_IMGDATA->m_width;
|
alpha_target_data = GetAlpha() + (x + xx) + (y + yy) * M_IMGDATA->m_width;
|
||||||
|
const int target_step = M_IMGDATA->m_width;
|
||||||
|
|
||||||
for (int j = 0; j < height; j++,
|
switch (alphaBlend)
|
||||||
source_data += source_step,
|
|
||||||
target_data += target_step)
|
|
||||||
{
|
{
|
||||||
memcpy( target_data, source_data, width );
|
case wxIMAGE_ALPHA_BLEND_OVER:
|
||||||
|
{
|
||||||
|
// Copy just the alpha values.
|
||||||
|
for (int j = 0; j < height; j++,
|
||||||
|
alpha_source_data += source_step,
|
||||||
|
alpha_target_data += target_step)
|
||||||
|
{
|
||||||
|
memcpy(alpha_target_data, alpha_source_data, width);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case wxIMAGE_ALPHA_BLEND_COMPOSE:
|
||||||
|
{
|
||||||
|
const unsigned char*
|
||||||
|
source_data = image.GetData() + 3 * (xx + yy * image.GetWidth());
|
||||||
|
|
||||||
|
unsigned char*
|
||||||
|
target_data = GetData() + 3 * ((x + xx) + (y + yy) * M_IMGDATA->m_width);
|
||||||
|
|
||||||
|
// Combine the alpha values but also apply alpha blending to
|
||||||
|
// the pixels themselves while we copy them.
|
||||||
|
for (int j = 0; j < height; j++,
|
||||||
|
alpha_source_data += source_step,
|
||||||
|
alpha_target_data += target_step,
|
||||||
|
source_data += 3 * source_step,
|
||||||
|
target_data += 3 * target_step)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < width; i++)
|
||||||
|
{
|
||||||
|
float source_alpha = alpha_source_data[i] / 255.0f;
|
||||||
|
float light_left = (alpha_target_data[i] / 255.0f) * (1.0f - source_alpha);
|
||||||
|
float result_alpha = source_alpha + light_left;
|
||||||
|
alpha_target_data[i] = (unsigned char)((result_alpha * 255) +0.5);
|
||||||
|
for (int c = 3 * i; c < 3 * (i + 1); c++)
|
||||||
|
{
|
||||||
|
target_data[c] =
|
||||||
|
(unsigned char)(((source_data[c] * source_alpha +
|
||||||
|
target_data[c] * light_left) /
|
||||||
|
result_alpha) + 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
copiedPixels = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!HasMask() && image.HasMask())
|
// If we hadn't copied them yet we must need to take the mask of the image
|
||||||
|
// being pasted into account.
|
||||||
|
if (!copiedPixels)
|
||||||
{
|
{
|
||||||
unsigned char r = image.GetMaskRed();
|
unsigned char r = image.GetMaskRed();
|
||||||
unsigned char g = image.GetMaskGreen();
|
unsigned char g = image.GetMaskGreen();
|
||||||
|
@@ -1454,6 +1454,321 @@ void ImageTestCase::ScaleCompare()
|
|||||||
|
|
||||||
#endif //wxUSE_IMAGE
|
#endif //wxUSE_IMAGE
|
||||||
|
|
||||||
|
TEST_CASE("wxImage::Paste", "[image][paste]")
|
||||||
|
{
|
||||||
|
const static char* squares_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",
|
||||||
|
"bbbbwoooo",
|
||||||
|
"bbbbwoooo",
|
||||||
|
"bbbbwoooo",
|
||||||
|
"bbbbwoooo"
|
||||||
|
};
|
||||||
|
|
||||||
|
const static char* toggle_equal_size_xpm[] =
|
||||||
|
{
|
||||||
|
"9 9 2 1",
|
||||||
|
" c None",
|
||||||
|
"y c #FF0000",
|
||||||
|
"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",
|
||||||
|
};
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Paste larger image")
|
||||||
|
{
|
||||||
|
const static char* toggle_larger_size_xpm[] =
|
||||||
|
{
|
||||||
|
"13 13 2 1",
|
||||||
|
" c None",
|
||||||
|
"y c #FF0000",
|
||||||
|
"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 #FF0000",
|
||||||
|
"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 #FF0000",
|
||||||
|
"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 #FF0000",
|
||||||
|
"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 #FF0000",
|
||||||
|
"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 #FF0000",
|
||||||
|
"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));
|
||||||
|
}
|
||||||
|
|
||||||
|
wxImage::AddHandler(new wxPNGHandler());
|
||||||
|
wxImage background("image/paste_input_background.png");
|
||||||
|
CHECK(background.IsOk());
|
||||||
|
wxImage opaque_square("image/paste_input_overlay_transparent_border_opaque_square.png");
|
||||||
|
CHECK(opaque_square.IsOk());
|
||||||
|
wxImage transparent_square("image/paste_input_overlay_transparent_border_semitransparent_square.png");
|
||||||
|
CHECK(transparent_square.IsOk());
|
||||||
|
wxImage transparent_circle("image/paste_input_overlay_transparent_border_semitransparent_circle.png");
|
||||||
|
CHECK(transparent_circle.IsOk());
|
||||||
|
|
||||||
|
SECTION("Paste fully opaque image onto blank image without alpha")
|
||||||
|
{
|
||||||
|
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")
|
||||||
|
{
|
||||||
|
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")
|
||||||
|
{
|
||||||
|
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 = background.Copy();
|
||||||
|
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 = background.Copy();
|
||||||
|
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 = background.Copy();
|
||||||
|
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 circle = transparent_circle.Copy();
|
||||||
|
wxImage actual = background.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")
|
||||||
|
{
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
TODO: add lots of more tests to wxImage functions
|
TODO: add lots of more tests to wxImage functions
|
||||||
|
BIN
tests/image/paste_input_background.png
Normal file
After Width: | Height: | Size: 410 B |
After Width: | Height: | Size: 263 B |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 330 B |
BIN
tests/image/paste_result_background_plus_circle_plus_square.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 265 B |
After Width: | Height: | Size: 560 B |
BIN
tests/image/paste_result_no_background_square_over_circle.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
@@ -30,8 +30,9 @@ namespace Catch
|
|||||||
class ImageRGBMatcher : public Catch::MatcherBase<wxImage>
|
class ImageRGBMatcher : public Catch::MatcherBase<wxImage>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ImageRGBMatcher(const wxImage& image)
|
ImageRGBMatcher(const wxImage& image, int tolerance)
|
||||||
: m_image(image)
|
: m_image(image)
|
||||||
|
, m_tolerance(tolerance)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +54,8 @@ public:
|
|||||||
{
|
{
|
||||||
for ( int y = 0; y < m_image.GetHeight(); ++y )
|
for ( int y = 0; y < m_image.GetHeight(); ++y )
|
||||||
{
|
{
|
||||||
if ( *d1 != *d2 )
|
const unsigned char diff = *d1 > * d2 ? *d1 - *d2 : *d2 - *d1;
|
||||||
|
if (diff > m_tolerance)
|
||||||
{
|
{
|
||||||
m_diffDesc.Printf
|
m_diffDesc.Printf
|
||||||
(
|
(
|
||||||
@@ -72,11 +74,11 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We should never get here as we know that the images are different
|
// We can only get here when the images are different AND we've not exited the
|
||||||
// and so should have returned from inside the loop above.
|
// method from the loop. That implies the tolerance must have caused this.
|
||||||
wxFAIL_MSG("unreachable");
|
wxASSERT_MSG(m_tolerance > 0, "Unreachable without tolerance");
|
||||||
|
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string describe() const wxOVERRIDE
|
std::string describe() const wxOVERRIDE
|
||||||
@@ -92,12 +94,67 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
const wxImage m_image;
|
const wxImage m_image;
|
||||||
|
const int m_tolerance;
|
||||||
mutable wxString m_diffDesc;
|
mutable wxString m_diffDesc;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline ImageRGBMatcher RGBSameAs(const wxImage& image)
|
inline ImageRGBMatcher RGBSameAs(const wxImage& image)
|
||||||
{
|
{
|
||||||
return ImageRGBMatcher(image);
|
return ImageRGBMatcher(image, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allows small differences (within given tolerance) for r, g, and b values.
|
||||||
|
inline ImageRGBMatcher RGBSimilarTo(const wxImage& image, int tolerance)
|
||||||
|
{
|
||||||
|
return ImageRGBMatcher(image, tolerance);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ImageAlphaMatcher : public Catch::MatcherBase<wxImage>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ImageAlphaMatcher(unsigned char alpha)
|
||||||
|
: m_alpha(alpha)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool match(const wxImage& other) const wxOVERRIDE
|
||||||
|
{
|
||||||
|
if (!other.HasAlpha())
|
||||||
|
{
|
||||||
|
m_diffDesc = "no alpha data";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char center_alpha =
|
||||||
|
*(other.GetAlpha() + (other.GetWidth() / 2) + (other.GetHeight() / 2 * other.GetWidth()));
|
||||||
|
|
||||||
|
if (m_alpha != center_alpha)
|
||||||
|
{
|
||||||
|
m_diffDesc.Printf("got alpha %u", center_alpha);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string describe() const wxOVERRIDE
|
||||||
|
{
|
||||||
|
std::string desc;
|
||||||
|
|
||||||
|
if (!m_diffDesc.empty())
|
||||||
|
desc = m_diffDesc.ToStdString(wxConvUTF8);
|
||||||
|
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const unsigned char m_alpha;
|
||||||
|
mutable wxString m_diffDesc;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline ImageAlphaMatcher CenterAlphaPixelEquals(unsigned char alpha)
|
||||||
|
{
|
||||||
|
return ImageAlphaMatcher(alpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // _WX_TESTS_TESTIMAGE_H_
|
#endif // _WX_TESTS_TESTIMAGE_H_
|
||||||
|