From 6e8da8641cf1f3a0161111ff05f1200e5fd42f9a Mon Sep 17 00:00:00 2001 From: Eric Raijmakers Date: Wed, 23 Sep 2020 11:56:58 +0200 Subject: [PATCH 1/2] 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_ From 451ed78dcdacfd94d04bda6dddd73dbb4bbc92e4 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Fri, 25 Sep 2020 01:09:47 +0200 Subject: [PATCH 2/2] Mark wxImageAlphaBlendMode as being new since 3.1.5 It doesn't matter much, but it, and the corresponding parameter, will be available in this version and not (only) in 3.2.0. --- interface/wx/image.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/wx/image.h b/interface/wx/image.h index a2c5b25f98..1c7288bd82 100644 --- a/interface/wx/image.h +++ b/interface/wx/image.h @@ -64,7 +64,7 @@ enum wxImageResizeQuality /** Constants for wxImage::Paste() for specifying alpha blending option. - @since 3.2.0 + @since 3.1.5 */ enum wxImageAlphaBlendMode { @@ -822,7 +822,7 @@ public: 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 + This parameter (new in wx 3.1.5) 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.