From 6e8da8641cf1f3a0161111ff05f1200e5fd42f9a Mon Sep 17 00:00:00 2001 From: Eric Raijmakers Date: Wed, 23 Sep 2020 11:56:58 +0200 Subject: [PATCH] Add alpha blending for wxImage::Paste Add test cases for wxImage::Paste. Closes #12458. Co-Authored-By: Rachel Mark --- include/wx/image.h | 19 +- interface/wx/image.h | 26 +- src/common/image.cpp | 85 ++++- tests/image/image.cpp | 315 ++++++++++++++++++ tests/image/paste_input_background.png | Bin 0 -> 410 bytes ...erlay_transparent_border_opaque_square.png | Bin 0 -> 263 bytes ...nsparent_border_semitransparent_circle.png | Bin 0 -> 1632 bytes ...nsparent_border_semitransparent_square.png | Bin 0 -> 330 bytes ...ult_background_plus_circle_plus_square.png | Bin 0 -> 1220 bytes ...erlay_transparent_border_opaque_square.png | Bin 0 -> 265 bytes ...nsparent_border_semitransparent_square.png | Bin 0 -> 560 bytes ...esult_no_background_square_over_circle.png | Bin 0 -> 1648 bytes tests/testimage.h | 71 +++- 13 files changed, 490 insertions(+), 26 deletions(-) create mode 100644 tests/image/paste_input_background.png create mode 100644 tests/image/paste_input_overlay_transparent_border_opaque_square.png create mode 100644 tests/image/paste_input_overlay_transparent_border_semitransparent_circle.png create mode 100644 tests/image/paste_input_overlay_transparent_border_semitransparent_square.png create mode 100644 tests/image/paste_result_background_plus_circle_plus_square.png create mode 100644 tests/image/paste_result_background_plus_overlay_transparent_border_opaque_square.png create mode 100644 tests/image/paste_result_background_plus_overlay_transparent_border_semitransparent_square.png create mode 100644 tests/image/paste_result_no_background_square_over_circle.png diff --git a/include/wx/image.h b/include/wx/image.h index d53ff0b116..f73fa70fab 100644 --- a/include/wx/image.h +++ b/include/wx/image.h @@ -73,6 +73,16 @@ enum wxImageResizeQuality 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 // transparent pixels from opaque for a few functions dealing with alpha and // fully opaque @@ -348,9 +358,12 @@ public: wxImage Size( const wxSize& size, const wxPoint& pos, int r = -1, int g = -1, int b = -1 ) const; - // pastes image into this instance and takes care of - // the mask colour and out of bounds problems - void Paste( const wxImage &image, int x, int y ); + // Copy the data of the given image to the specified position of this one + // taking care of the out of bounds problems. Mask is respected, but alpha + // 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 wxImage Scale( int width, int height, diff --git a/interface/wx/image.h b/interface/wx/image.h index 57bee46b41..a2c5b25f98 100644 --- a/interface/wx/image.h +++ b/interface/wx/image.h @@ -60,6 +60,21 @@ enum wxImageResizeQuality 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. @@ -803,8 +818,17 @@ public: /** 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. diff --git a/src/common/image.cpp b/src/common/image.cpp index 880c1c4ea7..8d935a1cfb 100644 --- a/src/common/image.cpp +++ b/src/common/image.cpp @@ -1608,7 +1608,9 @@ wxImage wxImage::Size( const wxSize& size, const wxPoint& pos, 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( image.IsOk(), wxT("invalid image") ); @@ -1639,15 +1641,18 @@ void wxImage::Paste( const wxImage &image, int x, int y ) if (width < 1) return; if (height < 1) return; + bool copiedPixels = false; + // 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 - // one meaning that either it must not have one at all or it must use the - // same masked colour. - if ( !image.HasMask() || + // for this we must not do alpha compositing and the image being pasted + // must have "compatible" mask with this one meaning that either it must + // not have one at all or it must use the same masked colour. + if (alphaBlend == wxIMAGE_ALPHA_BLEND_OVER && + (!image.HasMask() || ((HasMask() && (GetMaskRed()==image.GetMaskRed()) && (GetMaskGreen()==image.GetMaskGreen()) && - (GetMaskBlue()==image.GetMaskBlue()))) ) + (GetMaskBlue()==image.GetMaskBlue())))) ) { const unsigned char* source_data = image.GetData() + 3*(xx + yy*image.GetWidth()); int source_step = image.GetWidth()*3; @@ -1660,6 +1665,8 @@ void wxImage::Paste( const wxImage &image, int x, int y ) source_data += source_step; target_data += target_step; } + + copiedPixels = true; } // 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() ) InitAlpha(); - const unsigned char* source_data = image.GetAlpha() + xx + yy*image.GetWidth(); - int source_step = image.GetWidth(); + const unsigned char* + 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; - int target_step = M_IMGDATA->m_width; + unsigned char* + 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++, - source_data += source_step, - target_data += target_step) + switch (alphaBlend) { - 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 g = image.GetMaskGreen(); diff --git a/tests/image/image.cpp b/tests/image/image.cpp index f76e79162c..aa67d7678f 100644 --- a/tests/image/image.cpp +++ b/tests/image/image.cpp @@ -1454,6 +1454,321 @@ void ImageTestCase::ScaleCompare() #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 diff --git a/tests/image/paste_input_background.png b/tests/image/paste_input_background.png new file mode 100644 index 0000000000000000000000000000000000000000..fd93b550ce660b5fbb5507e9ea1757415cb2236e GIT binary patch literal 410 zcmeAS@N?(olHy`uVBq!ia0vp^DIm!lvI6;>0X`wF46^?l8XErpXP6lg^8_SV;1OBOz`%D1gc(IOyc&R}NO`(AhD02G zJN>la5d|J+XZAv?h+q0epXCjVZrpnONO{%^U!e_0jk2%lY+})0eziu4^O{Qz$F)f( zvYd>rr2LMMxM_5$YA6( zTgo)!`@|^YX;Wi653Y{gFUKPNP4U7;e#dFb93NyHqb?gRDU@~IvX*^OjqQY6H`rGi z*m?ARboA;G4occHLFjSImKlai4)!QYJ#N(KVGl~KnZWh9xnlc=evwD9kK%iN=zrYr z|7PP4Kecn-e?o-P?e8Ua*onQL=HlO1UYE4*$Z`oo?Z?71XZ-$H;+P_#q$yb0<5BCf zEk#IY;^dhoekpa&(j4!^&xjT(6rXMSx;`s%-#qUw*9Gn~*nmOD;OXk;vd$@?2>>K( Bp>F^H literal 0 HcmV?d00001 diff --git a/tests/image/paste_input_overlay_transparent_border_opaque_square.png b/tests/image/paste_input_overlay_transparent_border_opaque_square.png new file mode 100644 index 0000000000000000000000000000000000000000..fa32073e5c9dce4557a83df8ce75d477b426ec60 GIT binary patch literal 263 zcmeAS@N?(olHy`uVBq!ia0vp^DImNS%G|>0G|+7ApKvKf#*MvW>DKFaRErNlmz(&|NsA=!9i=~7a*Urz$3Dlfr0N3 z2s4Umcr^e8OFUg1Lo%G-p54gXY{0;9P~iQ3<6ds@hA#q39m<*i)E|?dp`zC5nKTJV zoxGS*ofamhCZ3jBX!i47GDr+6g^kMnbYP8+kKUP6k1{?L!xWy3R!e@<{U?bbuXzza P(0&F_S3j3^P6Px#1ZP1_K>z@;j|==^1poj532;bRa{vGibN~PjbOCE5v(^9r1@%cpK~#8N?VU|% zTvZgu&&woDw6T6Q)J2dkQrbnJfw~ZJ5g`;8MY5>Sg$qR&Qbk*+QbY)%6kSz8OTmR8 zT?i_;$V#$!%OZkBx@a-0Qc#H?)Co}iseV!kLmv?6|@1Fmhd(QoQ z#4rrQFbu;m48t%C!!QivLSn9!LVWQ;O21ztTK+>9-1?1Z?KaWi2+^L$bQ!K=6O_Dk zX<8RlgGvY@&P}Cs^NP9iV0?;_cdnFLpi&AVzCM-G)p??&MN=Xf7$n;LsPuwi=^0Bz zcmFjXbYbW|qR}y;^;PXn8*AnR$s_xT4$o5Z{Kusds8oV4!_YDH)KSupxiBMbZBKM@~@k@>hijQ-~m9elDf&Khl{IVnb_}Y(oAqpB4z3NHk~a z2O^*A=vhjh`yf|gau-BgoK5NH@8mZR=;kS8d;d1B8Yf> zFjejH$b+(p`pcA72&BqrT9XzfpRGp~CAuKuB&F(~q5I1bxC4+}plG5*6(sYFx)IS+ zWU3Pf6*C|PJfm)ibrPpXwdV&WiXbYRLI6k1s7W)m1;iAYn25QF;GzTkE2xIuJpFx_DzGRYTqA;Wh<~ zILN=`)3qQ$KA4B7fkO0Z(Nr}F)K?}i;`8G{yd+$cg;84!$dz$yDki8e>iyFXfK}*BC&n(Bo%+DdL$Fx}^s*(OiCDWhq`z*0 z*quvEcd)aoPyAvC6C5`|9F%V)xp_zx7D|@^2Rpe5g4Gtx&N$e~O%S{FnCXs#oj9nM z3^zeW_nLp0uH@z+`wrWbGscK;ZH zUOS7S!BSF(-g!Gg_82NJDf+ytqA~UNJ>|jQL%;#~3=pHKV_T><>c9U4b>-y~39)O+%nDexnhhX8fh4ECt=K6Pipc?dXqR*TV; zId{Yk)c2`ZOo2Z^Jp^P&zS@)6>WZzO4CKk%mxFi*2rdXvr{rK6WnBjzZM_~Er{vtt zU}AtMf&g`%RGmAF!*R}Fj3x*WueD^5UpIGk0VWecQrztb8`SS1_JNW4qs5?S-_^Gf zh$;wBzoew4Z_xHuk?gs(7%@+~j@o3klf{&(WncDrBgKq+iS8j5RT+$g+LPF-0Qwnt zUSv}_hyijF1gPV>56@O)3d8nn~b3%SLR?NGfvMoSe^92uq=YSky^u_V6!E z2N(5*oxsB&E|fwLOJVPz%JFp+J5ee@Ec}+XlGEWiuJC&d<51@}eKHKgFbu;m48t%C e!!S%WAo>p+&e_F!fJ0000}0^zU(`1 zbZ3Gur;xHokAh*-5e~XC9e2RbGnNRK)Y;dCG|;6O^)foQh_Nr4Rw4F_Vq z4rIh0C@DG+<8dI`{XlX4fyVX&5zYr{8yjTn5BNF!?_>CoWdFfk_<@!D11rS`Hp(B| z1wSO({ZE(s;3n|FQ}lzU$o~|{|M3F(+*Tr{EuM%5NrNFN%VgVSL-9a+@lHwK#$m$1o;L3 zrwlAsnLG<<6lZ})WHAE+UkL~^W~*$z1`M`fPZ!6KjK;S&Bf}pVh%h{G;@l)IdW13c zqLv$**NVlDjsE?gpQ@J|c}Z<&>m+0Tz4`@ylk3zoCoMCdHq~9O^2tPZwm^+rWt~%U zcE?!o7PuX;I4!m!|&5!=t zg}5qbCl|cm#UI7==wj*eW{-nPjUQd+$6j}p*mK2rt}B-iXV3i-KTWrXsT#2tDpx!Z zFtEu#8P?PO{!WMDh&Jle=HfJ12Qqqv_y{CO^Zw zN2f~Ly^?h6l65y`HEW&-|^So zUXvbv{+j3_aE$fg>vgkB+HF-Ye)uV6!go*qm4avpaBqVA+MCwf=x_6WQ=;g;Q`6(@Q*R%*_6dd1WfQ1(2d zQ`yTkV)kNo|5HUr{9Hu3cKpj1 zur^f+P0Bao37VuJr222$9R*cOr<9Jgr~S%Z4^x+@y3OmHcri6)p2Ww-`mX zMrR#4@>^xK`QobQsgGpy)3zt2Z+ai(zSJQ5qB5w z-y+iec$+5MKFPJ&Cnjd{-n{<)@dw%F2w~>?AJ5$tlJH`9SxVvmr&@)}&$Zg)w_!Qo{7iq@Yl3nedj!w?JX!tg^W?Qm`(xjF z22^gZ$Wr~Y*zWSGuA}_Dm*<}2Ec{e_OvA4(X%;9yeEffezm4fOb3-ZrT40`I@O1Ta JS?83{1ONcPPA&id literal 0 HcmV?d00001 diff --git a/tests/image/paste_result_background_plus_overlay_transparent_border_opaque_square.png b/tests/image/paste_result_background_plus_overlay_transparent_border_opaque_square.png new file mode 100644 index 0000000000000000000000000000000000000000..60da07730df542040281e9c86dd657cc9bf3fda2 GIT binary patch literal 265 zcmeAS@N?(olHy`uVBq!ia0vp^DImXiIvio%aD9%z6fMT2l9+AZi416UZ z%$TjR`5FTQ<7`hC$B>N1w=?f{|v`qQ3^iAS>4IgUHVClI0 zq-bZ1-u1;?4?REkPd<4zqs~I|PS3N;Cizj*Qe(Hzm%MyartOWN#Zm)z#eDTCL9?1C ztQU4$dB)JAtV@3Bl(=1H-Rn!2IrL2UAJL+j?slfwF-fPz=4VF`m)jX_HkVIgZ?icA zHC2_J!xZAJ1X_a(13Y%ea4k(+aQf<=iq=a855(hJH#lWVR&cyO8BtxXA?tQzvf=ZB z*YUwsJt-R6(o4;q9A6quTIz7FxW1?4pvSTxd+)_BdZr653ObS=*5Nd9{tU%O5vOF{ z3S^q3%Caj6xu)_B;JN9;ZVSjpwio{+~ujw6^-Y+V7XJPB%XFm0^__LB7mV2uX tg}2B0*V|j(nP+T&XP!wpD5UpalIINYOVU&*Jpqh622WQ%mvv4FO#rlR?5F?$ literal 0 HcmV?d00001 diff --git a/tests/image/paste_result_no_background_square_over_circle.png b/tests/image/paste_result_no_background_square_over_circle.png new file mode 100644 index 0000000000000000000000000000000000000000..d27564a130f7ed7dddf49dc84da24cb9fafd2b71 GIT binary patch literal 1648 zcmV-$29NoPP)Pe>g{9LK-&_C5c^V5m_{i$+NlEDBnpkV7GR$T5c;Qph3j zQgRS_N}=S?OG^)-&_kM|0|yQR`DS42CE&i^-%z0yXzK#}9nqWoa`d;3eqi)0*cU(By8Txn zQG&eF6AG<>RRFd(^;Oc+4tVaHf_4vqefPxHSBX4{5QJYp?cnJ>@k`S|036uaGGB31 zo4AH`4ccH|JGS|XKTKT(;kPb2crY!#N^%9jckAcO?)p!h2fK9Ju7?|11@U|FeN~m( z6GbdP1bh!*49Ho4SKhy%X)FuyeSmY}*U@qC*|}0RMRP0FwS8YZI*il;8$VXW-*W)> zfP6tTJ_%v~faeK+tO5WIJf8#mthX4a3$Y5qhC7N_|4Z};4xn7euc*E7T;X5N0{&Y8 z`}9z3qQoW$8*C|J>tFHT=X-HgtA_6he_wBa9WTTpO5UXL_UGa{d7l-*w*jjO@ODgA zX#iJ<;<*4=aq1K=0l;RTnc9r^kjYsT!MT{&HJlg$Uij6@MSBclRFKL0EOOpY$iS_E z$ysJh6k~$0!Ir3Jqz-OuOfez|8}2B=4aSTV^^6L^m}27YOS>R!{8)51)u>~}et2$l z_oaEBqE!%PPek(!FQIb`U3hLZPx*XQn<#pF$nrz+Fs4!F8LIf6XzxFZ+DvK_gx|X8 zR3=hc!V-^v_>FUp4s-f?$b)I|KrX3|LrhV2MZ4$kYB8rp5Pto%BOak!I?Od5LA&`i z+fg>9Y5)N6^qyExNXk>&xbcGZoIf39Q_6zyt7F28dM+*{fuQ}Bcu1n&Lsl1pWmf`C zZBX4Eqa+BwbJ+=gQgR|~@VnnQvSrnJ$fFr?jblSXBM`=6@fT?mYJ#jUiLE+BM4|P~ z{bV(wK89p%BDf!l@sJjzayOxK6I&Vx;=z9$cVvpo_mI_vl&*DXAeRT0f0rw&RPlks zO2t!)LR{1Y$u}z%Pb~^@krIU8yx;&3yU>Yu3F1mx#UDd{J|e*!T7ldm|@^gz}jrwjNU6mMWGxB;q6|NWMj?Smuz3lbj&o(PLt7 zB;rJadcu$s}H&RM$J*0a`Txl)j{e*Gp8mPIblpvLK6>VKI#nVp^7n!skYGa6> z(#_PWD8xlg5PF32eiY&&FG$Z=I?sOSz%PsVNFXgpC96(LyR3yY|9B<<%w zYDF;~YJzn1)0)WJxUWTABvBJ&^sKmsS#`2b7>6(}5-150CI3o9mZ!v^k`zsS4Cx*M zc3vr)Nd-GwKxgf<^W7*5Qpv=Y%lHn)p#AKJC0ogi4qq^6i}MjyMo&;#mS}(fNo7-7 z1gWIO^L-uW8jqme?8XE{Iitm#HbK~>+cp@>-qAK+oWf;a z&Dc83>0L2AIv!t}!e{47_^20|NFZPpM2^PxZt!3Ks47urHE3T` z=kqHiv>u8Qw4{+R!(4M4_~M#2F#toG9QfmnQj``ig#`a>{OOifF#vX`o2Hp#yO{wi7iR* z4;eY5?b%eVBx0h#uh~u|`By#Pu+x81%Oi9Z5+O)P-QGdfIiS4Kr;9`h5<+ijOE~l% u!-SLOH+>QWK@bE%5ClOG1VIo)qwqf-Rt3m* { public: - ImageRGBMatcher(const wxImage& image) + ImageRGBMatcher(const wxImage& image, int tolerance) : m_image(image) + , m_tolerance(tolerance) { } @@ -53,7 +54,8 @@ public: { 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 ( @@ -72,11 +74,11 @@ public: } } - // We should never get here as we know that the images are different - // and so should have returned from inside the loop above. - wxFAIL_MSG("unreachable"); + // We can only get here when the images are different AND we've not exited the + // method from the loop. That implies the tolerance must have caused this. + wxASSERT_MSG(m_tolerance > 0, "Unreachable without tolerance"); - return false; + return true; } std::string describe() const wxOVERRIDE @@ -92,12 +94,67 @@ public: private: const wxImage m_image; + const int m_tolerance; mutable wxString m_diffDesc; }; 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 +{ +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_