From a59bfc55111ad232c26af6dcf59f55a1b921b76b Mon Sep 17 00:00:00 2001 From: gered Date: Sun, 6 Nov 2022 22:17:04 -0500 Subject: [PATCH] update rotozoom blit algorithm to something a little bit better but there are still performance improvements we can make. but at least now we're not doing some silly double-pixel draw as a band-aid over missing/gap pixels. and we no longer sometimes have missing scanlines due to floating-point rounding issues. --- libretrogd/src/graphics/bitmap/blit.rs | 138 +++++++++--------- .../tests/ref/blended_rotozoom_blits.pcx | Bin 32034 -> 30752 bytes .../blended_rotozoom_transparent_blits.pcx | Bin 31887 -> 30686 bytes libretrogd/tests/ref/rotozoom_blits.pcx | Bin 6712 -> 6373 bytes .../tests/ref/rotozoom_offset_blits.pcx | Bin 6712 -> 6373 bytes .../tests/ref/rotozoom_transparent_blits.pcx | Bin 6792 -> 6437 bytes .../ref/rotozoom_transparent_offset_blits.pcx | Bin 6792 -> 6437 bytes 7 files changed, 68 insertions(+), 70 deletions(-) diff --git a/libretrogd/src/graphics/bitmap/blit.rs b/libretrogd/src/graphics/bitmap/blit.rs index fe3aff1..8e1d281 100644 --- a/libretrogd/src/graphics/bitmap/blit.rs +++ b/libretrogd/src/graphics/bitmap/blit.rs @@ -302,6 +302,16 @@ unsafe fn per_pixel_flipped_blit( } } +#[inline] +fn rotate_xy(x: f32, y: f32, angle: f32, origin_x: f32, origin_y: f32) -> (f32, f32) { + let sin = angle.sin(); + let cos = angle.cos(); + ( + origin_x + ((x - origin_x) * cos) - ((y - origin_y) * sin), + origin_y + ((x - origin_x) * sin) + ((y - origin_y) * cos) + ) +} + #[inline] unsafe fn per_pixel_rotozoom_blit( dest: &mut Bitmap, @@ -314,49 +324,72 @@ unsafe fn per_pixel_rotozoom_blit( scale_y: f32, pixel_fn: impl Fn(u8, &mut Bitmap, i32, i32), ) { - // TODO: this isn't the best rotozoom algorithm i guess. it has some floating point issues - // that result in missing pixels/rows still in a few places. also the double pixel - // write exists to mask that issue (even worse without it). - // need to re-do this with a better rotozoom algorithm! + let dest_width = (src_region.width as f32 * scale_x) as i32; + let dest_height = (src_region.height as f32 * scale_y) as i32; - let new_width = src_region.width as f32 * scale_x; - let new_height = src_region.height as f32 * scale_y; - if new_width as i32 <= 0 || new_height as i32 <= 0 { - return; - } - let half_new_width = new_width * 0.5; - let half_new_height = new_height * 0.5; + let half_src_width = (src_region.width / 2) as f32; + let half_src_height = (src_region.height / 2) as f32; + let half_dest_width = (dest_width / 2) as f32; + let half_dest_height = (dest_height / 2) as f32; - let angle_cos = angle.cos(); - let angle_sin = angle.sin(); + // calculate the destination bitmap axis-aligned bounding box of the region we're drawing to + // based on the source bitmap bounds when rotated and scaled. this is to prevent potentially + // cutting off the corners of the drawn bitmap depending on the exact rotation angle, since + // dest_width and dest_height can only really be used (by themselves) to calculate bounding box + // extents for 90-degree angle rotations. this feels kinda ugly to me, but not sure what other + // clever way to calculate this that there might be (if any). - let src_delta_x = src_region.width as f32 / new_width; - let src_delta_y = src_region.height as f32 / new_height; + let left = 0.0; + let top = 0.0; + let right = (dest_width - 1) as f32; + let bottom = (dest_height - 1) as f32; - let mut src_x = 0.0; - let mut src_y = 0.0; + let (top_left_x, top_left_y) = rotate_xy(left, top, angle, half_dest_width, half_dest_height); + let (top_right_x, top_right_y) = rotate_xy(right, top, angle, half_dest_width, half_dest_height); + let (bottom_left_x, bottom_left_y) = rotate_xy(left, bottom, angle, half_dest_width, half_dest_height); + let (bottom_right_x, bottom_right_y) = rotate_xy(right, bottom, angle, half_dest_width, half_dest_height); - let dest_center_x = dest_x as f32 + half_new_width; - let dest_center_y = dest_y as f32 + half_new_height; + let x1 = top_left_x.min(bottom_left_x).min(top_right_x).min(bottom_right_x) as i32; + let x2 = top_left_x.max(bottom_left_x).max(top_right_x).max(bottom_right_x) as i32; + let y1 = top_left_y.min(bottom_left_y).min(top_right_y).min(bottom_right_y) as i32; + let y2 = top_left_y.max(bottom_left_y).max(top_right_y).max(bottom_right_y) as i32; - for point_y in 0..new_height as i32 { - let src_pixels = src.pixels_at_unchecked(src_region.x, src_region.y + src_y as i32); + // now we're ready to draw. we'll be iterating through each pixel on the area we calculated + // just above -- that is (x1,y1)-(x2,y2) -- on the DESTINATION bitmap and for each of these + // x/y coordinates we'll sample the source bitmap after applying a reverse rotation/scale to get + // the equivalent source bitmap x/y pixel coordinate to be drawn. this is to ensure we don't + // end up with any "gap" pixels which would likely result if we instead simply iterated through + // the source bitmap pixels and only drew the resulting pixels. - for point_x in 0..new_width as i32 { - let pixel = src_pixels[src_x as usize]; - let draw_x = ((angle_cos * (point_x as f32 - half_new_width)) - - (angle_sin * (point_y as f32 - half_new_height)) - + dest_center_x) as i32; - let draw_y = ((angle_cos * (point_y as f32 - half_new_height)) - + (angle_sin * (point_x as f32 - half_new_width)) - + dest_center_y) as i32; + let sin = -angle.sin(); + let cos = angle.cos(); - pixel_fn(pixel, dest, draw_x, draw_y); - src_x += src_delta_x; + let scale_x = 1.0 / scale_x; + let scale_y = 1.0 / scale_y; + + for y in y1..=y2 { + for x in x1..=x2 { + // map the destination bitmap x/y coordinate we're currently at to it's source bitmap + // x/y coordinate by applying a reverse rotation/scale. + // note that for these transformations, we're doing a "weird" thing by utilizing the + // destination bitmap's center point as the origin _except_ for the final post-transform + // offset where we instead use the source bitmap's center point to re-translate the + // coordinates back. this is necessary because of the (potential) scale differences! + let src_x = ((x as f32 - half_dest_width) * cos * scale_x) - ((y as f32 - half_dest_height) * sin * scale_x) + half_src_width; + let src_y = ((x as f32 - half_dest_width) * sin * scale_y) + ((y as f32 - half_dest_height) * cos * scale_y) + half_src_height; + + // ensure the source x,y is in bounds, as it very well might not be depending on exactly + // where we are inside the destination area currently. also, we're not interested in + // wrapping of course, since we just want to draw a single instance of this source + // bitmap. + if src_x >= 0.0 && (src_x as i32) < (src_region.width as i32) && src_y >= 0.0 && (src_y as i32) < (src_region.height as i32) { + let pixel = src.get_pixel_unchecked(src_x as i32 + src_region.x, src_y as i32 + src_region.y); + + let draw_x = x + dest_x as i32; + let draw_y = y + dest_y as i32; + pixel_fn(pixel, dest, draw_x, draw_y); + } } - - src_x = 0.0; - src_y += src_delta_y; } } @@ -647,10 +680,8 @@ impl Bitmap { per_pixel_rotozoom_blit( self, src, src_region, dest_x, dest_y, angle, scale_x, scale_y, |src_pixel, dest_bitmap, draw_x, draw_y| { - // write the same pixel twice to mask some floating point issues (?) which would - // manifest as "gap" pixels on the destination. ugh! dest_bitmap.set_pixel(draw_x, draw_y, src_pixel); - dest_bitmap.set_pixel(draw_x + 1, draw_y, src_pixel); + //dest_bitmap.set_pixel(draw_x + 1, draw_y, src_pixel); } ); } @@ -669,9 +700,6 @@ impl Bitmap { per_pixel_rotozoom_blit( self, src, src_region, dest_x, dest_y, angle, scale_x, scale_y, |src_pixel, dest_bitmap, draw_x, draw_y| { - // write the same pixel twice to mask some floating point issues (?) which would - // manifest as "gap" pixels on the destination. ugh! - if let Some(dest_pixel) = dest_bitmap.get_pixel(draw_x, draw_y) { let draw_pixel = if let Some(blended_pixel) = blend_map.blend(src_pixel, dest_pixel) { blended_pixel @@ -680,15 +708,6 @@ impl Bitmap { }; dest_bitmap.set_pixel(draw_x, draw_y, draw_pixel); } - - if let Some(dest_pixel) = dest_bitmap.get_pixel(draw_x + 1, draw_y) { - let draw_pixel = if let Some(blended_pixel) = blend_map.blend(src_pixel, dest_pixel) { - blended_pixel - } else { - src_pixel - }; - dest_bitmap.set_pixel(draw_x + 1, draw_y, draw_pixel); - } } ); } @@ -708,10 +727,7 @@ impl Bitmap { self, src, src_region, dest_x, dest_y, angle, scale_x, scale_y, |src_pixel, dest_bitmap, draw_x, draw_y| { if transparent_color != src_pixel { - // write the same pixel twice to mask some floating point issues (?) which would - // manifest as "gap" pixels on the destination. ugh! dest_bitmap.set_pixel(draw_x, draw_y, src_pixel); - dest_bitmap.set_pixel(draw_x + 1, draw_y, src_pixel); } } ); @@ -733,9 +749,6 @@ impl Bitmap { self, src, src_region, dest_x, dest_y, angle, scale_x, scale_y, |src_pixel, dest_bitmap, draw_x, draw_y| { if transparent_color != src_pixel { - // write the same pixel twice to mask some floating point issues (?) which would - // manifest as "gap" pixels on the destination. ugh! - if let Some(dest_pixel) = dest_bitmap.get_pixel(draw_x, draw_y) { let draw_pixel = if let Some(blended_pixel) = blend_map.blend(src_pixel, dest_pixel) { blended_pixel @@ -744,15 +757,6 @@ impl Bitmap { }; dest_bitmap.set_pixel(draw_x, draw_y, draw_pixel); } - - if let Some(dest_pixel) = dest_bitmap.get_pixel(draw_x + 1, draw_y) { - let draw_pixel = if let Some(blended_pixel) = blend_map.blend(src_pixel, dest_pixel) { - blended_pixel - } else { - src_pixel - }; - dest_bitmap.set_pixel(draw_x + 1, draw_y, draw_pixel); - } } } ); @@ -773,10 +777,7 @@ impl Bitmap { self, src, src_region, dest_x, dest_y, angle, scale_x, scale_y, |src_pixel, dest_bitmap, draw_x, draw_y| { let src_pixel = src_pixel.wrapping_add(offset); - // write the same pixel twice to mask some floating point issues (?) which would - // manifest as "gap" pixels on the destination. ugh! dest_bitmap.set_pixel(draw_x, draw_y, src_pixel); - dest_bitmap.set_pixel(draw_x + 1, draw_y, src_pixel); } ); } @@ -798,10 +799,7 @@ impl Bitmap { |src_pixel, dest_bitmap, draw_x, draw_y| { if transparent_color != src_pixel { let src_pixel = src_pixel.wrapping_add(offset); - // write the same pixel twice to mask some floating point issues (?) which would - // manifest as "gap" pixels on the destination. ugh! dest_bitmap.set_pixel(draw_x, draw_y, src_pixel); - dest_bitmap.set_pixel(draw_x + 1, draw_y, src_pixel); } } ); diff --git a/libretrogd/tests/ref/blended_rotozoom_blits.pcx b/libretrogd/tests/ref/blended_rotozoom_blits.pcx index 79d6530a0743d1cae380624dbf2495761039777b..c628b83467be7077765ea4b6f1dc35c8d07fd9c9 100644 GIT binary patch literal 30752 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 H=lTBt3GPX3 literal 32034 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 IA3V?h57vmj(EtDd diff --git a/libretrogd/tests/ref/blended_rotozoom_transparent_blits.pcx b/libretrogd/tests/ref/blended_rotozoom_transparent_blits.pcx index 673af85a978dc230ab69fd6ac9f28263644d4ec1..1f13edd847c6b81097702a1b73899eaaf7640847 100644 GIT binary patch literal 30686 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 Udn<91;Zc_}=KodjU$8&_0c*w6j{pDw literal 6712 zcmeHMxpEXo81C8COu1YR2nY!_B-lU!!3I=$0zJS63J4Vv&I4?~Bk%wOgnEDtm8fU- z;Km1KT;+hk#zG)*2xyKG>+k#fm>y}dpbE+k&<@hffA@dS@_m|or||P*@>J!?oVTC% zljKPP&S#&;6{z=&&Z_Q2O%k2m#QmhJRh1^C%5LGQRP(grZJAY_iK zc`AYg87zq{w{$lH9gaoa$IY~acZ9HwJ!;&-{A`{@MpLi#vz76%!A739xLt>9$AY*$ z!Nd+*0P@^9gcNt=$X3qe+r4<;>-b@2CP;JpdBuuE6v`xkUf@wD0HKxxaE~;qL~*mk z19%tUDZx_t0sM$L1CU4PfY}L{GBgNp3vsUMwXMDw!&+8H;MBuHRcIb8(U_h!1-R5$ z8$M4_AW~BvE^+`8IBLklX%XdQq3mOft0VNX()BctmGcAUFhSm9p5DP(Bc7gRZabLH zq0oWmbdxjbJKJO4x~-0rl1} z=&z0L(yncYaVhW|DKbyc&7G7gduggJ&i28)p)t${(#(=6XxKU?5O)BY3PiOr)NS*_ zlFgJ}sj?>kPZMyU33QoSG@+r@*;B}rluKu5xH{W4v=a>b2H+G1CD}XSXhsEjkf6u~ zay%jj(lgrw$0QB(ZY)^hd8%fhmE=okTA3kC9W_l+6YwRxw+B<*JYeIVsq5*)vCzPo zD#5gCkOiDkaHK>G?81orL2B|=!y`9fctX&m@Ez6nLWhF3c`%k=~)ZQ~En_GA|LFX$Kr2xklu|NX?-~w5VBpOmKL= zll>(UU84{OkQ@nsurUaBqtF!I$^LZekseaO-eJ;FXbekrj^Dad3h@+vU~Fl-UlBt4 zsc};A51nc4rE{=hzyJmcObfGvtcE+PjtFT8+$us6WFI5n0sV8DY#UaqF^MA}goa2z z=)gbHxkL%7DuXi?1~Eb;1m}CyOW;ajv`7s@e4}EzYx2F48yp?def3M+hulbONYDQUY;I(M9u@o^sA_&t9EiDF#9}!>z?b#@ueVaWE<;?}y zDWKG*=Mh`jZwJ3^s(vLm@!cYhhN)I zK4RAL_TXwx^YwS7FQw~Ev~$gj)Eoxx?*Y1F@Iv6;HSh7phY$#^(|FlQvY$MKwF(Qq zmagJnEcwlv&;_)L(DoZiM|wXfzJ@Xos^P8Li~) z&31AtRGRv&ww-~{HgFQU?r(We$Vui6MiX5TsrEY!A+~%sj$7^BdkakR663yNv|RG8 zL#`!9a3Qb)FoYU*bQrG`8EcicPZgpR;ur+z(Am8Vh@*_-2M)mu;;akqf|^s&cY6wI z#EPur{vgrOBnj5tH9yD~*qdrosm)FP!k=~CuJfnHXT7fZOa9;%^rugs?)7^8et$3+ ztgWpLhr^AHjiM+nUAlDn^5v^nua3v#`F#H1!2_YMb5)O2y`{j6YpYhHTICQFBO>T> z#AsAs8P!{(`j1gv^ZKJ6ZPlYI^{9o2pvw{Cab1n;`{R0NT-UsI>hb-0T-9wv1YM3$ zsyL&HfhyKjQSe$X)SysjiZ&vGE=LRo#oC})9~2vdqTsbr4AzUmTG2*C(B+8Xu(&WR zHipH;VNvk9SPVCc;f11&h@i_6O68|i-c$Li%5z?;x$5QWRNh8J(B+6;FF)JMS9|%O zm*>0&d2coEoz2^b2)Z26@8_%i{9Hdj-_LVi=kxx#yuX^a5fOAbBK${9e1rV_2MO}8 A^Z)<= diff --git a/libretrogd/tests/ref/rotozoom_offset_blits.pcx b/libretrogd/tests/ref/rotozoom_offset_blits.pcx index 675c66c125253f4ae76c2741ace885b3564660c0..382da4990f1a05b5b3bc25e26bd485036836c526 100644 GIT binary patch literal 6373 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 lbQBdy#$Moj{udkr>V5zK diff --git a/libretrogd/tests/ref/rotozoom_transparent_blits.pcx b/libretrogd/tests/ref/rotozoom_transparent_blits.pcx index 65ead5fb2d454e2f1d30a7a1688186dad680a550..bfc33a212c6421e008b1cd3eb07f4716f551f0f5 100644 GIT binary patch literal 6437 zcmeHMy>iq@5SI2wb#-+C0U^PL1RFRY*np~2;0M^i0pW&(dVmdh1Rj8Z&=0VolO1V) zIIyXM;)=Q=Ab~SK#$W@357~R)*R7F8Mwp}Os3X9uT5Gz$?&;~C?w+xK?uI`F`1v*5 z58`mi*RT5t<1mEe>#vh4(XVXQp4nL#-IbSl812|7waad1^)2hBcGls0)&j76>tqm# z(pDC&>;tQ6M0(avZQ2b}${VO!P!*s};#tdBDtHYAzFd^Hofs8NZQ5}vbY2y_?+iGE zypy5*B5ia5i>@=8bCn`1d#@5Q(Ke(SSFI~l6CGr`X<;kN&jL!zRf{V>F<*g8yU!^L zZh`bIRoEQ|150eI;^8b?*}Dp=ScR`u#S^&qQ|~M9spI)>Rn~K5opRdB?vY;2y|Q;? z;VLxy@h%?t_I_B}6p5oBi!GO7bEjcRI>69`Q+!XE-43}gge;6C=NC3Qw(OZ^e*|G)gw5s%M(1#!fZ!9G-Mgrq*cjsOXwm%8;Vq zSkGN9Q3R>t=%Q-T?O+#c9jh@&>(p{(Wub`_AtNaq{zXaFilua!diti1tCLd-$6;3Mkjc+HfqypR46e%M7ZZ+79ClH*hKGb+J;Mjn2Ih5 zI5Er6h$eUqst%#CjZ%DKKS;Q-D?nO?b6{ZdOdu(v8b&!Wx*HBe(b9I(2sL&tDzR*6 z?!ptHLt=-#0g}?)aDhHyBDidUHXw4w&bo!(s?NUw0CAbwd57S+n1UB$=M27@;mSI| zb8HImIuT;TMb52(RT!8SwA<(y)mLV7^?x50&FyV&2yBGRhF#1@@wA1^pa=qys#Ev) zCT-72Wmp5mW>{&)#wQb{w%cLA;C}J*o-A6L6}&P-ATS+hyA+(kTnLJ4%=;d4*tMbL z9-LWLf3}Qyn}R9_GcY&p$U27-th$isG|>XH-J;N6K+b4<2az;U2Z3euD1^A^^t%1` z;Gj8n!5m96kc;rnOhbQNS(E{hDl;247TNI{a`37(UDHY-`IMWd|Dilr7TX0;I?tn3l|XDGyi z0h+-q{9sui^0$up{q395=jLJ+a6FbYuTdcbYaQxHlEiCS??A|~jyehZcFN7|w7$jO zMctHhXGPOZDKS?fT#F*uWk%DT`q9eR%{1^84JG0fO$r={<)$H+I$0z9zC)FuY0~-g zxMJdH5^EGAw+To`C^Kv^Hg5+*sVAwjnT1PsghTv)!F3jJgH;ByUzsXCY;d0+9%HE< z7sGaKi(shjk#nK^)`tiwYm&3$qr|wva|{Q2e54G#D>YAr;7zI2o{BTaz_+}^!7+xV zdTHe?r-^QeywT|}L6bs%oCN;UlK@Xm|Na4Xd`_SezInX6P?cu!hlsSAr1kNr+??$ym@#K+aWy$ip_TL_leYNP;KKUKB zX_rQNY7zcGD3ctyPPNCxNC7Sg*b~}_Ph3WSUoE&QNH{Q71FtUf;^A=^%lfeGmvb~D zoVq+f10e#Us>>_hV|)n&sWyfKHD+oaw0k8Q(r1jEuT|0=f;c)|aNW|sKl%~2l(TM$ zLx9}%{w#^lu$7BT#~;d5@Gj20yuRanbl(9yj^5(%*FA;QIXFP=$e|y?)l9dcvAU%x z0LTaatiM^gSLj85$AjOi+u-B$uWU}FW>5js`n!!#3yK28HPl&$tAlstBGSnaSXt@t zJGQug6(@b~Pj}l*Ru`^S@%>5|byy}V)f6Vuf2W*ZM(33iFY0ok0IKdZtgsAG8AUbX zcl(R{wRSVu41&!~e&N>!UpM%Z(_7Iyg8XnmdoYi$BzY{$3Z?0@~r^Ec((FjoCh%!`3#TR%ovaJ8{>RyoIe`p zIiE-Qcq<>@$j3E?M{QYz&goAW8U)lEFqYSWoH)nqtI zhF6k0!=pAcf*?K@#JwP13*wm1S{(G^;9Oj1c+_S_uNPnJ#cREI(2HX}gSfXA_b$eD phDU8?^!xEzKfctDFZbh^&*iv(DekYub%sZ6W|aSx@L#Y${{htgh$8?1 literal 6792 zcmeHMJ#!OB7`{76HVq#TPB$3`=< z6%j?dQoUsS9YPlRwSML?*fT&UU|dhMiZUEyW(826Wuxt66a(>1!!sDm!&lbg>~};7 zZ4@;nMWx0++CuYpt`D8qQYUz7_FKB!6&;Y&-e9VwyepI|Xt_<#zJ>lpKZg_tr-k;$ zt&rU%*7JeI?U%T^SBN_hjDKqjK#s~gA;n!ew3SnQTgwf6nR}Qad+a?g^ed((ypV+z z=moYaED%Q1CGY?|@G6p<3189fgSH@8>Ocj4%$NblAykFgt1y|A3(0k743Vw`lzHo; znAZs26%2JT%O@JuMAFsgVOD~t%r+mx9L15OsyH}hkyhZyCI_cs0N;Ypka$sd553%P|AyL{G7U zfh8=18;_|V%whxXH-gDQDIq}A45+tSg?aoMcby6QiFhA=KSH={BQ~szJbtNjki6G# zz+W&hNH7l(5;1E?0a?c+0n}(s#WqkZ*;K1`9zPYOIaqUsgs5MVs`wDh5D=yIBVQCpAGtko2Qz%mHEQXIF zbf7TCHG=y9DA!M(LBUl2gnDdQm@GIonD%uR6=)PH;>Thhp3G2cA6uK2Ih5X0jGpLq?t@O8{ zKAyF1;c8U8i&LN*%PEc|Zl8-IaoO-0-09em3k=xp*0!9$DwS^Z0&L=Ide-qNajMG; zw9F?EJEKTp-xZsgc^M*zX(J7%6u>D$$^wAV#$LlmBxMPLDq9ZD8j(hpUmPR~3%!CyxDzeK2QSUuWZd)R;-u6 zA?7ON1bbJu82*ZUuVjJ414)6WaviK*uE8;2iJ9Qf{W=`?Uw#dDhy~Pk%0n%ioY65# zLWR|mSRQ~8mcw)pB)5ooC(xdZa#_P!49oa-2PN1>FJr}rg1Cc`+JjPXCQw!qpO$Ik zBFe%k7ljkheA&t>!n%R*Lbm#188NDZob$Vy4H5z?TKYJTyoN>Z=MouK zoS|ZG&zAG`-0hCY(6T{kAyT)ONZi!Y@e3rya>w@RZP|C>7|+}91kwp@c3PPw%!NCF z4Is)=t(xCS1um)V&O8UpmL4M~^!W}al{5#R4P8?Tr-hMg>B&>7s{HCt52W^k5iZmo zA(ND%tk7@c$7dWr6o;|14Il7CNT#z*9mvu!W-gy~+z`e}-$$m5x5zo@apWJFE~JmN z6jFbQ-x)e1f;4@ng3F6V$# zG(-Xn0Jix9VzVAM5@Gq??foa41)jsK_^(&@U3H?hn7T`=?M{XE4FBG-g!@=H6XENq z-vf(bbSe7d09*yjc}>LX-Ye_qJYEaO<<@t02zKVw+nIy@A2ZbF()GrhD`r|Icy!u7 zD(IfZ14H?O;=bR8f#9euPoh6N3TqXneg#O^_plpf=~x`Erk%5z@%e#6SOFF<$d#yV10+DYmjOf6f@ZjASBkz78F zki@ax!g|Gech5Uu6N`k_zFKr$fox4jDZODu;63M30P0Ppw;fxxD~GE*R{6A&;1zu* z1p+Ch;oOyLD}mO7_dw2fudWYJk|&tbny!Upt3gv)cu)TzFT^*4%^=v^FO+Gdc z#%6D93_p8j{J@NJQ%88vW<(IA7lO1Oq#Hq+^0Sc!{WQ3c))5}G8PV^j>-}`2pAP$J z%Fi(EZ>0V8w2ttg&4|Guy)sCLgY@bkP5HT+4uDOuCKtQOS~ux;lZ2ms(p^is7n3@|gEk|2y=1MIT8&Moga>U#RR8hgFA$&q0E-GrA^-pY diff --git a/libretrogd/tests/ref/rotozoom_transparent_offset_blits.pcx b/libretrogd/tests/ref/rotozoom_transparent_offset_blits.pcx index f1c119e9038144c999915445e37e3d091bfc80cf..c1dc59eeb8a3a570986159d8047d55aa54c31b79 100644 GIT binary patch literal 6437 zcmeHMy>c5j5I)(WNS=5Ke@Ek(1(*~b|%R<X&JC;|Xm@iabT;O?i^S8ow(S!0fH2 zK-rM4+E~z2GG;V8T$d!hs${-^sTrFBbQxW_g4n<-7_fO8ay_N7feFc4YQoHGh(Dwb zoPeKl*niCyW&txVb(-hOC=2>NCy9;6U@cv>S)p+;g*;xium$n=3`Vn6>rh^D-vA?; z^OT7;LE4@g*rOB%mG&{;kJPgTeQQ9uDv7l*@e9IxIr>KTYpS!uYE=71bs=aAI%Ii1 z_ktejg&R`%|GSRBhmS)#TwGQvMMQ?a-YH`WNpm4HmfLG(UHwrTNts(2z!*sCluLumQ&Q8 zkf4H{JuDL$lPFLW%9*KsrlguMmupkAb_=Jtf;x6Vq1uvKs)SYN3RqH&(1^<_o8TZ5 z!&D}nvlN1kEak#u_|kyrid2ZnHG4M$C?#F-M1*EM6J4LEr<=O zvb<3~A>mF-$~_}bRD}12Btp)J3EL`T!wn`X36kz92~Sp}cS0>$SJZk-e*lCvGsulf zQ(%tC3pGU6v4b0}m@%swbBxKNQbZN$9+5Y#z@#@P1XMP;G^EJsjq>k6f?cYXW)xP0 zdqCa{L6_bU!*Mo@-4JI+uHBFXf;&SnLz-3?JgvYL?|{M6CKhDiWw|?h)EhrH1*|!H^#z378V|xzUMp z2pUB^Q_50U185_|5adohNvuN>;As!%AZu?se}Fktfw? zz0t`h>rkv}H^IXxE|t+T+-aGKh8NY;xBu8D@LPq~EZ`Au-Ft zcC{KUkdUNILgwm~DWt2WNz9~ZT2(Oc8+on*4^FJ(^q~xBR+iZarMd}kOXXKMB(gjs zQRTcB?yuxY^zMgPAUtJilKVPg6JAM`UxPQpGC>fYavvg+K)*!PQ4l6Ny#+g{ZsIVN z{{kAmt!ewAiv=P#nz)tpWtP$+FhQIkbhTY8PNi@I58NUzPIv|9_} zC!5$5$nrjftze<~MzuFe9-d>5EiHyk>n?+Oi&~wTx-`V@ zVXjR7m)_1C&tnAG(hH&o$tjR;zEWe*xevX2SJb|6AbMqw941809@f|6{8Mx{1`$-e0H5qnF^~#?D-` zwHFER?CB_40)_>9a#c5f!#HF!JA6eSC(ghm>2Z^}+{5`ZPnN^S+#vJbjcSSFY0{Zh zcGv#X(s5r+y0tAnWoxEOBhgI}{)3_1XOZii+vkkYEjbb3OqfRe)&RKm{+C*CUXa4P z6!K>ct8f(t*vpQk!?0u78Wx_qeXZkMgL7Uwljn05y;LfYMIL4dZ zA5ws&xurGu>z;uWHPg-R6H*3}&-4u}hPQC1xxFbV0RZ{bp7m?OwY@8E&2B4(}DKIti_z z&vSCfR6T8CCSUu-;Agvok3nW`bsOa*Gk~{8X&kH-W?Pi)qH*Fos_DPh-goXh&i(uR z1HX3ob%(!VeDB0D|Ioki3VgfW?)7^8et$3+Y;SK5hr`|7-5?0=+_`i2?%jL$?v2Oe zD2kpxf3ESd@5Cb~e&B$N=Rxd@V#kLeTVY9;D@LREt5N)56#qPmV}5>)M-SrBSMjKz zu%ycs<8iFU@w0LKVjRc(yokro;<1X03QM|N;W$Cp2?kEE;{*XeJApF@oNiE5SkmQ+ z!64Wk1UrLZcMt^p>;{9KV6Yt&6_#|lVmJ)G7zVq;;LBkU@bhIb+zp0b1Vx1G@9GFDfkQaz(G_f70`}dj6p2`}_?2-j?6{#4jo=>2gKC?{D?} gPy7C7ec$KjGr#|--{10!3QM|Nk^QfP{etuPH&U>&rT_o{ literal 6792 zcmeHMy>i<|5Qc10ARvO4_%pIC7A#Y+P?3T~W`aTwz(Pezl_~K6Ec^(502eOx04&|; z?m$q996LXrI5RF>B#NR$mTWmb|G;Lxy#o#hg^{C~ByM6e60p19-roMx@v93p2Y)_Y zICjE?DLr27&qBDc0OZNVP)7PlvW8d^a^kVZ9-iYRCPl#Ub{B60Ah&@_YN((ROEi-NdZaW z@=J=9fb~kUnh0JoaftuJwI$Uy}O78DgV;?#K0g>kjF>k$-FM*G9foT;f;iHEH zag7J3BqDh$l~OWSRFEiI&$K$BWBlJBm_Qer!7^gs;H_7dD07c-CKOcRj)|Pqn13iC z4?h6Gn>!c!i0XmfY3MDaX;2ua#U0j4k2BA!t zi)SC6kb9oU&|KDd9gbN7I=&(Z-I56Ivd~os7rDq+WiFv98haj4%>S7pf#C`RmV)1K zHiJ_Lf2t~$FYAIe=T~vOpSWMbHO$|a9N?smxeI5;E39>*3Yw$-IMsO$i>of2;qSV; zP?sM%1ul377*M+2a~$)?Vx-+=s`7-u0*6ASg6}Jvr*(4Ia}*C9TPQGXfesg!5w|LH zpUBwfNHJ6kc>$Kw?Ej+9VfjoR0TL&5lRetCiWqDMc z`v&ry`yZQ6sk6(TMS)~T^8;Ptpzt*^nZkK;wN=@AKFQn`USNY>N`i!O9Nf_ z@T-!Y_)Nx`YU~3@6Y6bQGfHt;r4kY3s<8Yp)2JQr5=TBr1bE> ze+mtGt7?@R6`X-}CH6=QDqHm>Eugv*ay?KQ5UZS*cdX~&VdVo*Gj}jg^0tNa4;Y>N zJK2uj1?MP*Z&ACnZ_(_~eF+H*3bh*jPCs+rfAfrRm=^pNc3J~5l^xHTc~TXYML>pO zhL*;q@?w)KVlfP;hYL{8j6EW&>@CfxOt>vdR8q{F98`PKso{XJiGFL%Kcb>>f@+O8 zQh+#sXw5f`#|*2X2^PbR$doaxU8TWMTtp`c5sl9%#$v@9Sk(N)N`Xn~;1$*>0xY1s zc>8a|ZJ(=?Qd5wK#dbs0ZRNpCcC0syPx}>fmjY8MF8o?iY880Ucqyadl=DcWi#1EJ zBp^^&KFUa<@gib`#CdkW35nb+WHiAJC!vZ+zNMp#d1`yGf`$aM-mt&eo}`82|MeqZ zDgMFrgLzoSF1CCi5)}enZ0kUkRy@@5h1U%QH0Ql%%Xoz;fpc>4_iPu`Nm!Giw8$`| z_ut(rG*A^F9rgEi3DqEJgHq-x%wOyfH1S}UkvwO6!0{b4etm;;76+2s!CI$up0_h# zmnOli^s$}(XI9P+i`BAC{gcn2=NhZH^Y{RB)F zYUjonLY#fK+BznWHNxTC@*B5C^DNu3a)%d2f4BjAQ5#;$xSL}V>0!U>^40DShI$~q z6TD!mt#%)1rWOYSt9BRP`c}JN=MtK=Pv5k$=yt_5?8B>F{BxX^o6Bb1y+ZV%Ie0f$ z5?{mKzqX2yS%R$loo$Q}7T1zBvf?)Hml0`$S!1tO$L97*Iy7aNy zE#`72VkV|W1FGvPU&HsGLJJgz5Y zD2i^}xN-C5&D*zckH_Qbbo%)5VV!~aGc4+S#%Pq@8l`te z>7!Aa(&te+x|5D>rK1wVqRwZG$Eg^n569`jI8Etukd7awW095_7Ii+uaiXph4V-Ax zi6Z)JM$RB|x>1>7QRg!TgJ@$AZ4RQXK@`zvD;jJ@gN>-nu&DDH!(sHQSAL;4KD w-g?;kC@eE9>U>7OAFlVqkNe>#{V=4@Ct?5Nu)iLb85VUu!~BmQ`vLj+2aHR2qyPW_