From 85d1f9402b4080a8012669cb90b004e06bab9fcd Mon Sep 17 00:00:00 2001 From: George Thomas Date: Tue, 31 Oct 2023 18:38:29 +0000 Subject: [PATCH] feat: Use transparent background on animations Note that we use `DisposalRestoreBackground` in encoding the GIF, whereas previously we indirectly used `diagrams-rasterific`'s default, `DisposalAny`. While that worked with the default black background, retaining it now would mean that we would see previous frames remaining in the background rather than disappearing. --- primer/primer.cabal | 1 + primer/src/Primer/Primitives.hs | 28 +++++++++++++++-------- primer/test/outputs/eval/animation/1.gif | Bin 3515 -> 3641 bytes primer/test/outputs/eval/animation/2.gif | Bin 53740 -> 54301 bytes 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/primer/primer.cabal b/primer/primer.cabal index 49fd7e851..c84ac52af 100644 --- a/primer/primer.cabal +++ b/primer/primer.cabal @@ -117,6 +117,7 @@ library , exceptions >=0.10.4 && <0.11.0 , extra >=1.7.10 && <1.8.0 , generic-optics >=2.0 && <2.3.0 + , JuicyPixels ^>=3.3.8 , list-t >=1.0 && <1.1.0 , logging-effect ^>=1.4 , mmorph ^>=1.2.0 diff --git a/primer/src/Primer/Primitives.hs b/primer/src/Primer/Primitives.hs index 0a54ba03d..6d592227d 100644 --- a/primer/src/Primer/Primitives.hs +++ b/primer/src/Primer/Primitives.hs @@ -28,17 +28,24 @@ module Primer.Primitives ( import Foreword hiding (rotate) +import Codec.Picture.ColorQuant (palettizeWithAlpha) +import Codec.Picture.Gif ( + GifDisposalMethod (DisposalRestoreBackground), + GifEncode (GifEncode), + GifLooping (LoopingForever), + encodeComplexGifImage, + ) import Control.Monad.Fresh (MonadFresh) import Data.Aeson (FromJSON (..), ToJSON (..)) import Data.ByteString.Base64 qualified as B64 import Data.Data (Data) import Data.Map qualified as M import Diagrams.Backend.Rasterific ( - GifLooping (LoopingForever), - defaultPaletteOptions, - rasterGif, + Options (RasterificOptions), + Rasterific (Rasterific), ) import Diagrams.Prelude ( + Diagram, V2 (..), circle, deg, @@ -48,6 +55,7 @@ import Diagrams.Prelude ( mkSizeSpec, rect, rectEnvelope, + renderDia, rotate, sRGB24, translate, @@ -343,7 +351,7 @@ primFunDef def args = case def of -- Since we only support translating a `Picture` expression to an image once it is in normal form, -- this guard will only pass when `picture` has no free variables other than `time`. [PrimCon () (PrimInt duration), Lam () time picture] - | Just frames <- traverse diagramAtTime [0 .. (duration * 100) `div` frameLength - 1] -> + | Just (frames :: [Diagram Rasterific]) <- traverse diagramAtTime [0 .. (duration * 100) `div` frameLength - 1] -> Right $ prim $ PrimAnimation @@ -354,12 +362,14 @@ primFunDef def args = case def of -- for unrelated reasons (getting the `Bytestring` without dumping it to a file). mempty (decodeUtf8 . B64.encode . toS) - $ rasterGif @Double - (mkSizeSpec $ Just . fromInteger <$> V2 width height) - gifLooping - defaultPaletteOptions + $ encodeComplexGifImage + $ GifEncode (fromInteger width) (fromInteger height) Nothing Nothing gifLooping + $ flip palettizeWithAlpha DisposalRestoreBackground $ map - ( (,fromInteger frameLength) + ( (fromInteger frameLength,) + . renderDia + Rasterific + (RasterificOptions (mkSizeSpec $ Just . fromInteger <$> V2 width height)) . rectEnvelope (fromInteger <$> mkP2 (-width `div` 2) (-height `div` 2)) (fromInteger <$> V2 width height) diff --git a/primer/test/outputs/eval/animation/1.gif b/primer/test/outputs/eval/animation/1.gif index 4631f47d8de04be5d22517077af7aa2580627e56..6819aa58a8b7eb5ff67ea060e0d140006d416e6d 100644 GIT binary patch literal 3641 zcmZ?wbhEHbT)+^;P|m=h_@CR)H6+;CF~HSG&w!bc0Vwp7g_Dbci9rX*W&kN`1Tp@D z2q0kkKc#==>9_og=WMyvz4_jr-~4TlJf=PCT=weJws+jeKlxn$*8BX|x$pn@dAQn- zEc)2t^Ns1uvd@wMUJI{eg?MerS{d=++v&Hj4m{bgBh1hKyz$oRvt@gqpS9@j+kead z=bw95u77tIdSNaRq*3u=qQs6H7gzk4A^6gwGf=bAb7JOB%Zn?2{uFp+`6@uGYS)CU z9j7j=`jsW{+NwLCyL|No@9ncMSby)9eq&Y>kW-^GVa>KPcVfQD=I==>Ry>(FdF6x!H50E)c(&x!RGsKuHJrxF&dfB)zO|>* wc=@@xI$WdSI2w+l>2Wk)kCu<4<>P2QGg@DdwtGg~kE89!(RTRI?ax>P0QZk5S^xk5 delta 343 zcmdlfvs>EQ-P6s&GI0Sz6vGDwF!;})_@CR)H6+;CF~HSG&w!Z`D6IICg@Fr5>wuJi c6f-dWZ<*-0l96ZP>jt4wDOhMs-pyMF0F}K^tN;K2 diff --git a/primer/test/outputs/eval/animation/2.gif b/primer/test/outputs/eval/animation/2.gif index 85aee4f2e96a3c902c9d1516079e541691a0835a..c1aa2ee9dcf930acae28ea4c81d9d8a3161cfb91 100644 GIT binary patch delta 4482 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 KyW~l@AN~XXO%R;` delta 4353 zcmZ8kc|25m8$ajF7?WiT3du0`C1pY~lEzLXB70tIMpDvZX_>JzsU}AfV#ZdIE>5N7 z-aEEL=?|#nrYn%JoGzteK6m*3$Gs#+9c{OY@1ej8CvC&9 zJw5ywQiCqlZQIMC?!hk24Q>e;Ib45hBh8Sj9<5E|z3ZP?jqn3M?UkqdM$-9XPrBM_ zHYDVxvP^J)-Yo3@jn4+wZ`>KP-Q(t3y~qGr=u5LNbCZEvQ}SFkV~8gA9%=U7Xng3Q z(?PdG_`P3G0kbds7!t;SYgLzlJz>@FCsud6J{^B>)hLJg<8&#rp`a;`zK>uPzCT-f zJ@K^Kr%iu<8|&97DhW!_|E4#TD_Uu5Eha*8Ho3s0iuX5nnjhzX^2WZ?C!DnA1M1K~ zBfEtu50sIOw0?nqu}Zp6q%dPU_({ysTr zXmLQ$meWOV{+teBt2`_YPYrQfL>(n9=j>)_b>->sxqxc-+0e2F*%u|qWqreYoE#NH zuUl)kX?Hj%nSXs%*;?^{z%tBc-<`sVduY!LHttMtqkmf485y_|!nZJdfiDzh8B2ci`liIF;qcQh_R^1D z{4*5T)}_iHH?SH>kNvTM574fgv?x(MzJCt0vG%F8?tSEHHM8jq^feb3(p*QyU%nG% zcbmD9)i;>&?$zDicvhNp-DDNnU%5<9Qg`KHUY|eoM*q0@+UY6Fz7T0XV){kW5|f3` zn$Bhrthef~P$&zd#WqsNfaDJ)^+XnQghUI}6QA0Y%*Z7Y^N!Wi$S)+osU)7r8K0>l zm8!Rt5)xiwst+U8|Nd{nM<3{AIAA)Kf&DN(RDhN1(MbaDZK&ZANOdiov+CrUU6Az$ z*8nR}&9NC%Xc;|3w0#;=oTF*V=Ed7-OPGKcv_06R#5EcAXsd3Gj}%1T(;Y25{4^q5 zT6Ia-Xy2kxAtL`uodY3|2cMJvwxm7Ds{nuiz>TG@jxqE|7I9xV@2%5qW^jA^?$>JA zOJ!o`I^7jCOFJ&8h-?g<9WL*ZVg*!}_N5GOKYdPuJbY*PvZJly;p^6-ZS)QsC6BM_ ztk%)(If;T6S5&wcCYbJf-CwRKdtqxxXE!BxpFG{^T7GMTNtxd!c{o(#j$5@-dF)-6 z#wHWWWk%CRV-1S)>6O89!K+Uhe#=V*I%?!huluX%jXW%LkFNPP+c(mwn16m`RYQu$ zA$-c$Q_)|8f&}^muAMdvUexEiKbwti@6Yh&z3dBgW}Ap|P3j-x<;U!#zx#bW!Ajr1 zqnl!tdtz15gXG60s;Nl?j{+x3k;sP-Mwqe%{g}Sh2|3Xd$uvx|DurY)*c44J(3c6E z$^$3fH!}g_kIBVQA&D5IC$3@Il9Ws2_n)k%4G9}vof!x+P7dF~WE~|_Xs6W8Yq+Gz zw?vMn{!7!Ov@;G<#e^A0atT{QfkMJ)Pq5WCD;WAfP>3c`Q|g1J`7r0amN`ceyOOXW zY#lZ!9@Qv6+$L1^%0(*?d$HQ4V=*I@qeaI*kB=5Jae8B%l#&0DXt3lj|JTx&jEM26 zrj~S;iW$Bx%tdXLl$-$zt2;J8TdQh2clDZ%1G;cs&C2}ayOZzHB!kSj9n;n&UT+Q? zUhL~lR>R;^^~N2I&fz$3_*;S+b`nJ|{F|e!+Sz-hNaD3(exKv6GG#(g+tJEfHd?z1 z;*HC8{Uwqwb6xM(d)q7+6iF$4DFU0bY&CAH~$@ z1vPgZGWx^S>TCTUklqZc=9^Uf?{~UTHJh?t58i%PTKM?*yqXzRoZUh>)Bf^@X7s(a zomWe3naZO`1NWP+S`L!=v_1H^_EQNI2mC3lLf#(Qi6uot^&}qJXIInY)Lde80UkF1 zC9;gLJaI_A(KJa!RI!93x#f`&^NfahbO~wZEsvu~m2phUMEe{mJ!>l~y9|xZ1>%C*}5z zaWk~8j+JDYOpKMXM`V7kVzNXeET$L?(U>q-bpRex7KPo{Jccj;tg94@zGk^fXBIS0 zmI#K=lOlftUqd(mZdch0i85g$a=`+MEfWcEQ5A$kxT^O+X`tq5Y+)mj;4t@Nb+&u{ z2>^~$_d~-zR=>0umJa`|5w;Mv4<>0%s~-azwxA87-Le>+k!%7z=Du%e-O+>9Ns0X! z=88Rz)5W6+wLKv8k*O`rD`3ac5wh!rJ9XzEfQ&rQBY+EOC{B^EgTC;Z50SWa(jdx$ zwcZO-1>jff>wltFP=u&9#Bjq}0V=@I5mE!-O2Y?>lmp>sMo|ltHzLQ4yFduw!A>Sd zD41dLauF2_w{H?Msz=_Kx}iH0KrGDG2q_6(g7r)5vXYUcLLw;-y%?j~{q3 z`F6kqY#Kc(vR8TV?d;^B$MngLpsDdSI{UXVMgDzW)@xN0v{?@?dlYnN z*bcR&k*?&yw~3wYu(HJ58E5aFO#2*+^=J55`RnWpln_rYwL8q^lwsww{C4S%^kVsr zUETLTXzgThd_BJ+KG;$BSid+Ij@j_lHp(v*YVRjpv%n(%M0S*dowYaP6YHAh?bKwC z?3mGs-fYVvPa~U;|5P_Oy&GKrB*}aC8V?q(CH*e#TW#LH5FN!lI}`VucDJS%Iosmz zuBNa%cNlDr_s{Q*;Lj*&)3=|_Av~t;GSYSS^9WXE)Rf}KBqI}}ly2{s@C?2X_g>{t zX*7TGdG7!??WVIw2!oC)3I9C6 zSJ!>#OS-&r?tUJ>I)^gwh5GacnYV8=F$g*`N@E8k1N+*0p$}Z%AXO&VAqhv!ejFs` zYs*e$WT<<%mTYC;;H(nS&mIC|r!}J0#Kpzj?M`c&PZd$5C+|Zh(_}ut)*{nhtW#~C z(lAt@#3z9U1A;}aG@OMM^UiDreOPt093$s(JQ!R-9$rV}P_2PRa*_*{2PEYwWRvY6_luLBmGstW{;NZhZ)T$)Sqi4eytmW zU)(SS#1f;a*vmKdFHVRPG&rTwjoT@a602y6tvKU<^d@Us_Hh$>fBj0WH7VYQhaJIeB zUX0{9>=W2UwrMZP>k%keAQzE5=e_fDj&)h|6=aV)C;@wBd5NOm1-LnMH-N5+0x>A< zG!zC9fCkw(_*eJ&-J$9s2ceomsGihC2B^TFc?so|fpmEn&8La-wOAHmrN8i!iO8IP z`8=Q^fF=YG<-*_{fkHq6VieSX%9e?|3tj{WIOK&20Y{M7uquJB5sh%OMGj~cI_bhH z96~RNW;P?bDkco6BSP81Q^2Bm?K^6Z#IbTIfhMT|+Ck6=J1zXZ9 zSy;raFeYUiYIaFM3xPhx(p;8hX-Z$X#U@D~b+r`LlytSU6!k;^K9J#rV((_O35X@K zb}w_W&q7_0+AJyr;d$`n88V8HJR7kLG00vBs*fBwXMyH~Z@^m!x`3N6`07Zh);T|H zI)Kc@xUYMMAg+5>f0bfHiQHAsi3x=h{FJ45D(ulpz%7^8F0Orl*yTzLT8O4A9)fUkRcdG*s8uJI!g#?l z)v@zop%sV?k5Ciu$=q|#;;K;jz zjy<^>B_L}3TM2rO{MP&PBCC0Vi)8U;K@~uDCw(q8wKvRRJDrs9<*edNW(FD1vdg}g zsuzHz!DY=mQSX`N8nnc4WeaaHh!cFfH5mew%rz7kEzKBz=#q| z$xf?+zr3`1=;p4RWl!KKSPXL02C_vhK5WvwD~(