From 7718986fb5f2557db1074544890479cb51b6489b Mon Sep 17 00:00:00 2001 From: Rohan Singh Date: Thu, 15 Oct 2020 13:33:23 -0400 Subject: [PATCH] Add support for decoding an animated WebP The initial implementation doesn't fully expose `WebPAnimDecoderOptions`. --- README.md | 1 - examples/images/weather-anim-frames/00.png | Bin 0 -> 684 bytes examples/images/weather-anim-frames/01.png | Bin 0 -> 688 bytes examples/images/weather-anim-frames/02.png | Bin 0 -> 698 bytes examples/images/weather-anim-frames/03.png | Bin 0 -> 698 bytes examples/images/weather-anim-frames/04.png | Bin 0 -> 699 bytes examples/images/weather-anim-frames/05.png | Bin 0 -> 696 bytes examples/images/weather-anim-frames/06.png | Bin 0 -> 690 bytes examples/images/weather-anim-frames/07.png | Bin 0 -> 670 bytes examples/images/weather-anim-frames/08.png | Bin 0 -> 676 bytes examples/images/weather-anim-frames/09.png | Bin 0 -> 680 bytes examples/images/weather-anim-frames/10.png | Bin 0 -> 694 bytes examples/images/weather-anim-frames/11.png | Bin 0 -> 694 bytes examples/images/weather-anim-frames/12.png | Bin 0 -> 693 bytes examples/images/weather-anim-frames/13.png | Bin 0 -> 694 bytes examples/images/weather-anim-frames/14.png | Bin 0 -> 688 bytes examples/images/weather-anim-frames/15.png | Bin 0 -> 690 bytes examples/images/weather-anim-frames/16.png | Bin 0 -> 690 bytes examples/images/weather-anim-frames/17.png | Bin 0 -> 689 bytes examples/images/weather-anim.webp | Bin 0 -> 5734 bytes webp/anim_decode.go | 123 +++++++++++++++++++++ webp/anim_decode_test.go | 77 +++++++++++++ 22 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 examples/images/weather-anim-frames/00.png create mode 100644 examples/images/weather-anim-frames/01.png create mode 100644 examples/images/weather-anim-frames/02.png create mode 100644 examples/images/weather-anim-frames/03.png create mode 100644 examples/images/weather-anim-frames/04.png create mode 100644 examples/images/weather-anim-frames/05.png create mode 100644 examples/images/weather-anim-frames/06.png create mode 100644 examples/images/weather-anim-frames/07.png create mode 100644 examples/images/weather-anim-frames/08.png create mode 100644 examples/images/weather-anim-frames/09.png create mode 100644 examples/images/weather-anim-frames/10.png create mode 100644 examples/images/weather-anim-frames/11.png create mode 100644 examples/images/weather-anim-frames/12.png create mode 100644 examples/images/weather-anim-frames/13.png create mode 100644 examples/images/weather-anim-frames/14.png create mode 100644 examples/images/weather-anim-frames/15.png create mode 100644 examples/images/weather-anim-frames/16.png create mode 100644 examples/images/weather-anim-frames/17.png create mode 100644 examples/images/weather-anim.webp create mode 100644 webp/anim_decode.go create mode 100644 webp/anim_decode_test.go diff --git a/README.md b/README.md index 0ab44fd..e8ef8ef 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,6 @@ func main() { ## TODO - Incremental decoding API -- Animation decoding ## License diff --git a/examples/images/weather-anim-frames/00.png b/examples/images/weather-anim-frames/00.png new file mode 100644 index 0000000000000000000000000000000000000000..9112505cf2ed2bbb5001c0e2571ac7410bab4213 GIT binary patch literal 684 zcmeAS@N?(olHy`uVBq!ia0vp^4nVBH!3-p)I`?e@QY`6?zK#qG8~eHcB(j1elRbib z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP{Jd?C&YE{-o0^gaX?;E zQ`5$c8};?|XU?1%A0NMT>C)cbUI_^a6%`e0YwN76tXZ>Wm6et8@bHwDmNqsv1_uWh zE%>iu6yWae{{R1f=kRl)KvNi#yxm>Ixv$OW2Xfd;Jbhi+pK$RDav1qpyyO8&8+f`n zhIky`I;ne7vjUH+vy1qqZLY7U{r`VC^qxl8s@>Nm)h$jcnzJd09#;A0c=+*gv3o0> z|7dMs@iw}5)%%y;<`q){1-2<2+0%cYHP1M&^_N=qd%47(O+C*J?v7JE<~T#K@KrDa z|KnFvjFXM6TD$+mu$*ZRW17Cig^S^-`icp>hj*kT*!THIzvrBkFw6Pk;;heAm)9xW zd2yWmi%g!b(gTt8V(ZTYq`z7&nphgAGeP+V_sK1*-+gK|W%@iXpY3EwsH31Ia@ISL#gD)*-sG6pG?KAW8HM^5;ALMMxb(lZ@U}(d!{R}(GZ4=V_?OrT!*--t! zZPTCF-|hV}Gj!g5SD3w$?^<8F#5aYq0PYRl674JQ2ER*47j}N8nBaXOyTLk)U+}uv zx|c=|#DkqBuge~m+zSj&)e_f;l9a@fRIB8oR3OD*WME{VYY0RpAx1`4Mg~@f=Gq1Z zRt5&I>~-8xH00)|WTsW3X)v_3GBJc`xVrRF9!x`dYEpq!ZfS*XQch}K3WH-wenCJ{ hYFTD}X|ZEYK}MnzNVu+Q?IfTE22WQ%mvv4FO#m#^0Vx0g literal 0 HcmV?d00001 diff --git a/examples/images/weather-anim-frames/01.png b/examples/images/weather-anim-frames/01.png new file mode 100644 index 0000000000000000000000000000000000000000..97bd1985906177834275f62da921525f64a96429 GIT binary patch literal 688 zcmeAS@N?(olHy`uVBq!ia0vp^4nVBH!3-p)I`?e@QY`6?zK#qG8~eHcB(j1elRbib z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP{Jd?C&YE{-o0^gaX?;E zQ`5$c8};?|XU?1%A0NMT>C)cbUI_^a6%`e0YwN76tXZ>Wm6et8@bHwDmNqsv1_uYL z7zGq9`0wuS{{R2~Ddr5vfu=AfdAqxab6=a$59F|yc>21sKjGpRGfP`fS%&_e|FijadT$K6d&pAC;-vd0;f6yTVs#~Kv-l(Sp3rYLom!?Zkgb6`l-N&5xK`e%3%KT<|deQ?Kbkoa#bTSFv1B z5x>4rQvRT+o>-}%kibojk||MB7%l`l_NL5wrpk2H=6+3`$_?iY>jGBXw7Nf4TV>x3 zTdPlIu`~W2R*u)5w#w$sP&@hD%cmDD{T+1;== z%>4U@!ejMY3(lNTY}oj0a&N5Zg7se~ywS2ynx}t2=K2Gj0}tXE4sDm-VCw!?)P%E+ zuSwtEvcULSCsX~W*SOl|A7^@($u7d6E-$RE*J#JM{1;5C(HCu|*>6S5p`ly!HM zazB4#@<80kN%F$Y8QV7k15~xdHKHUXu_V#WANKBhd*Yv3Xa?RiG9IPgg&ebxsLQ04zrTSpWb4 literal 0 HcmV?d00001 diff --git a/examples/images/weather-anim-frames/02.png b/examples/images/weather-anim-frames/02.png new file mode 100644 index 0000000000000000000000000000000000000000..9974039bb2e2d64beace18bbf58bd1a80fbc9712 GIT binary patch literal 698 zcmeAS@N?(olHy`uVBq!ia0vp^4nVBH!3-p)I`?e@QY`6?zK#qG8~eHcB(j1elRbib z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP{KRFC&YE{-o0^gaX?;E zQ`5$c8};?|XU?1%A0NMT>C)cbUI_^a6%`ddJw0n{>#VG-S+i!9m6h@E@RXL8Ha0c} z2M4Pd1-QGr7cKY?1U)DFWq}4UCV9KNNX*V`l>>6vOFVsD*`ILn3vw9wS-j){N?Urm zIEHu}UpuL{tXYAlRZ)qV?d^qikN?guIkCTKYS#MaTNa#j&*N~4l;|pr;ZfNy6S!2V zm8r%wf=66%Jwr^*_Q*w|6IG0}QZju*`Vs=%?|cg6G+1+Lce;ew>W+u)0ZTuJch1Nb z5?sYH;fmg>W=VNRQ=P8bM5kMaER5W|F;FURcAYHcyWAU?9N~3=3h|dX4s&v+*ca! z#$DhlSdda$K3}V1`iDa9=P7=SJyZS7zTIT{wWj?^8mlc!e1qlv=G6?$^BG$1=N-5w z^ELc}N=r0{ZREP^@gEzR#Z$U0HD+Z^+J0D0tayUf3zi>_IC)cbUI_^a6%`ddJw0n{>#VG-S+i!9m6h@E@RXL8Ha0c} z2M4Pd1-QGr7cKY?1U)DFWq}4UCV9KNNX*V`l>>6vOFVsD*`ILn3vw9wS-j){N?Urm zIEHu}UpuLLQnLb&t0GhPnl0UByZ`*3u9qLMAt-lFVS^{%&glwMn4CBEbvHh7?poCl zbC_YDluloFPk2LM&GyJeQztS8M+MIF>pUYV^QtGhFqDO7^|yDDZAZ0|e<=E_a(dM= z$J6D6ki(={A67%VX3Y(PwGR(5yy!TgA)?Rd;mR)hpfWCp?L=zjk6#`J?O{yQ7rfYY zZ)^64&_BmoE6$i_i3@uz{c}>_eAA58|8EOyt7aCkdATv=`np~H{x7;u_B8F3?2RZ5 zGhvvn#L&0o-TP$r_d<4h4^v(QGvpo;&;FdtF!z#k;p%3+rhJF_-ydi>99Cy|c%S>m z+{N$sZnb>iIw<^m&%aK7ksT_rXB-n(`Q75P)p@2+dV%vpbHt1lhp)d#C>IKT#JIrq zf~dlqYTp*GLtA(7{9tF?Af5WK)Y%*uuBs)j5hW>!C8<`)MX5lF!N|bKK-Un6OhSx| ztc(n-49&F-46FC)cbUI_^a6%`ddJw0n{>#VG-S+i!9m6h@E@RXL8Ha0c} z2M4Pd1-QGr7cKY?1U)DFWq}4UCV9KNNX*V`l>>6vOFVsD*`ILn3vw9wS-j){N?Uol zIEHu}UpuM0s6~OtRZ+=RYq#t7ga7L@1(}X5bqzB=XK~S!&wRQ;+rh@C4>VsM`L#LS zwSnJ9^?SkTUJ&ON8>-%W;D?#G`6IVvl16&$y<;4b*U$*}J> z!&KX>kPq^V`HZ%a>&)Xmo?}^fVxq0WvRyW}*R`kcZ)$RLh;d0;ba;6{z|A8u5v+fF z3s@QUNBg!UwG!C8<`)MX5lF!N|bKK-Un6OhSx| ztc(n-49&F-46FC)cbUI_^a6%`ddJw0n{>#VG-S+i!9m6h@E@RXL8Ha0c} z2M4Pd1-QGr7cKY?1U)DFWq}4UCV9KNNX*V`l>>6vOFVsD*`ILn3vw9wS-j){N}GGS zIEHu}UpuL>s9Axhjd3F5<+6t7H8d_i1idLIRm2e*0>1o?g}xro$L}BC5AOb-uQV+-&WH&wFn;D^7#*)egrevyNdkI6g<#)&2djzfNuxepP!O3tQ8+Q8EUc_j+Tc! zePGL&$CzdmpTFnPTt26X>hBwPt|sU2HdcAhAREH;$XUht%Z4z%E`336?uK%f{fr0p zm31UtKD2cQ&kyD=2c*LcPrpyB9jm!BP$~V zD?@W_0|P4qgID%C?kE~^^HVa@D$z6;T3VSHLNr`m`X~>mK^j8Zczopr0OE@Jb^rhX literal 0 HcmV?d00001 diff --git a/examples/images/weather-anim-frames/06.png b/examples/images/weather-anim-frames/06.png new file mode 100644 index 0000000000000000000000000000000000000000..e2a951301ce2aaca3790e1a9edebf3997b24640a GIT binary patch literal 690 zcmeAS@N?(olHy`uVBq!ia0vp^4nVBH!3-p)I`?e@QY`6?zK#qG8~eHcB(j1elRbib z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP{KRFC&YE{-o0^gaX?;E zQ`5$c8};?|XU?1%A0NMT>C)cbUI_^a6%`ddJw0n{>#VG-S+i!9m6h@E@RXL8Ha0c} z2M4Pd1-QGr7cKY?1U)DFWq}4UCV9KNNX*V`l>>6vOFVsD*`ILn3vw9wS-j){N*j8* zIEHu}-#V$ctXYAl)!C&jXN&XhQ@`zB>ewr;UF&gI?|H?9$u%m|85lTNCY_k=H|?-o z*dZN;)*peNI+t>8b;)65`+QPK^7Iql{tA{F#f0@QLv9Hze0fW#@KLy6L-Bc5wIbHo zju-ouZtC0W#c-N&(wiVXQ{`UX{fc(K7x^+~@G(r6Ezvku$Yd$8<#I`(rlU&u28MSc zr&Mh&``?>!y5iO8N`@D!nRHjih<a)N1i_Lm+pXrCI;Z6N+2H&;IpZw;M;@HUWe;>O$quhU{3HwSNQv1q6Hn^Tx zKf!0`-u#-!Z~2^ho);g8`FbVhtg{_^$U!>?8JE08{@vjLGhMo>IV*fWWII^h$W&Sv zZL#cdL;S*OX(6-ghvospQ?2OC7#SED=o$i%Nr;h=m63s!p}Dq! zft7*5D|;Px6b-rgDVb@NXc`PHtxOCd8m=yVln2zH4zi& zQ`5$c8};?|#VG-S+i!9m6h@E@RXL8Ha0c} z2M4Pd1-QGr7cKY?1X-OQ1At~RCV9KNNX*V`l>>6vOFVsD*`ILn3vw9wS-j){O3Qk> zIEHu}KRYS+dXoZAn`Bahe^|!TfB*Gsg%bOMJ}%|IJLmDtQ+nHGb23g+NeWo;F0oB!X=G74R2F)q?=Fu(`7g(v*1->`?BPPR}^=BSiFqk(<)7ojZKFpcg;51 zn!MJJS>$GFOorRdTl>}SW?3^&(6f5sVB)JCnz}IW_HOS#tB(CKZ_uCq(6e9SMs=~~ z&-rp6OoFtvuK)P+uJ^*ho~j1*-$i$~Rvj{PJ&;!`Jt2GVX~t`_zbnf<&|}zVqVhPK z^}w-f&PMAKgcCIwzE>BBG<@K7sCdS3)ACf<2A>7d9P7S_+r)fIns&eGq>MrI#9gN= zW}IOR-N0qQla!>?=KA7|$|XzV2300MhC|a!)egCEA1{&kz;CurT+Qdv-3P$1Q!R0g zC`m~yNwrEYN(E93Mg~R(x`sex5@KXzWn^GwXs&HwU}a$N%3jAEMMG|WN@iLmng&Bl zD-%PAhO0{-~s}->pOsxj&p7^l9db zBl{aNTk8Bo7rXFkG8C!K+2Qhg$;IcP2UImC49~O z@jm zT{ic2wI}i$giT;B=$7y)VK@`UkjSg}NkP7$_`r&WzD+(YKRJHiV=rKNzUO_QmvAUQh^kMk%5tct|1VagcuoF85vj^nrj;vSQ!|+ve$7((U6;; zl9^VCroqtC%ES<&;p);yc|Z;7AREe4lM1YIODlAfa#HhB7#vIT3!D;@v(t<6OY>43 ba|$vNoj?+sca>ZPYGLqn^>bP0l+XkKhi3T? literal 0 HcmV?d00001 diff --git a/examples/images/weather-anim-frames/09.png b/examples/images/weather-anim-frames/09.png new file mode 100644 index 0000000000000000000000000000000000000000..da4e5169bfb4fd4983e5cc48c833213a686d33a7 GIT binary patch literal 680 zcmeAS@N?(olHy`uVBq!ia0vp^4nVBH!3-p)I`?e@QY`6?zK#qG8~eHcB(j1elRbib z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP{KRFC&YE{-o0^gaZOE4 z8#iv$*VhLMFI~EH=FFM@|NoDVkC%{;=n zd%8G=cpN`FnX$N8fu}W)XPHHAd)d4H|1V{7JlMc=>-OEa_f=t&*X#Fdu&fkX6UDXO z?G1}{q5Tf7r-6%FZ5S?w9XdNbqx|B_ex3#KoUdaoTezP77n3>~{=IkQbcT-s`<4{( zWhq^qze(1oqKaXH-MVWZ41)N-OxB9k^O9$1Dddz|ajZ~i(`5sn%a;o>kFGqx*zm@! z;?nfp7tVjYcf`NCyeDP*(tnn#W-2PO>6D#a{JZV*rUvfbdd3sIcF!4}ANs8>`9PIH zZ`0WwHC0t}&-a}>>&~}{C7WS=`UgD*MWzR$4E4(yT&t(ZDEi-+!}NTU+(z4HF5;pI zTi9nOTs%MhjKLeme?!C8<`)MX5lF!N|bKK-Un6OhSx|tc(n-49&F-46F(^b literal 0 HcmV?d00001 diff --git a/examples/images/weather-anim-frames/10.png b/examples/images/weather-anim-frames/10.png new file mode 100644 index 0000000000000000000000000000000000000000..063a65f6b364f34a4599ebe97d3bb74f612a7c1e GIT binary patch literal 694 zcmeAS@N?(olHy`uVBq!ia0vp^4nVBH!3-p)I`?e@QY`6?zK#qG8~eHcB(j1elRbib z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP{KRFC&YE{-o0^gaZOE4 z8#iv$*VhLMFI~EH=FFM@|NoDVkC%{;=X zdb&7(&e>CLlKDrGM|r&;u6YTr z5%y=3cKdihPHTY+e=37g)7O^%2a75VUz}%3*upZqan7Q4uH#P2|Gb){vXxUHTr@`N z+yd30->Sb)9=R*qz*F?~n7Hy+m;NUwcWLZp7HAVSxp3yQ(vt;=QPXCBT-O`UTE;LX zuTA~;v(|lQr|aF9-Pt=O&;Migv(1MZ4HGUrv-;z(*YH8>h5hVPyyCqZtUmthWcwk+ z@b7Riq^gZV=Lrs`-h z!B0XA+3uBx@3H-1;+>*lzQAj$9xzN*OI#yLQW8s2t&)pUffR$0fsui(ArP5_7#Ud^ z8CV&bYa19?85q2>*KtSDkei>9nO2FW!O+sm#1Nw4>e5GfKn?038_H9Y3aoNVD|C}` uQu9(6982;GoD!3>(~I&;^HLmh3NjL%KoXmGm0SgCVeoYIb6Mw<&;$S!LjbA( literal 0 HcmV?d00001 diff --git a/examples/images/weather-anim-frames/11.png b/examples/images/weather-anim-frames/11.png new file mode 100644 index 0000000000000000000000000000000000000000..ca9252b27370e5b78543e8727364461fc9b9566c GIT binary patch literal 694 zcmeAS@N?(olHy`uVBq!ia0vp^4nVBH!3-p)I`?e@QY`6?zK#qG8~eHcB(j1elRbib z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP{KRFC&YE{-o0^gaZOE4 z8#iv$*VhLMFI~EH=FFM@|NoDVkC%{;=X zdb&7X-J$3l$o&UFy%0IJR+VbIDFd1}%<2?FShT z!XISWyf#eR($mCunnB6w(DQHX%U|qx@im>XU?WTQK?O-&d$HMOYu0ryyT-t>eOmCU z8;);1zQ&d|PrU5I-u3jAkA&OHgMUkA22Kx4XGmDo#J0L{j>A`}_ROjo6QWo{4VVkw zs#S=`7cP{4yw%fR|BqDe?zyYyPo5)b?!@ryTK(gX0tY%D?Pr_fHs8A;`O#lt-WMVb zZ|f5oG}k70O*5`X zc)B=-cpN`F^o0}+FkK4V^7d$vO_$*qI^9Dm#tArKM!I!h`m<2+1$sJ}W-hBJiU_DQfS*Wlt|!YQ2)%Fokh#o5acm zpO(A|DsSfAyptUZZn9)pRNIQTREDL=f| zo!dc{OG9t&WM|gZ${_~gpTiq64)ooX+QX&b!|=$0d>xWiTJJ8vEtzdyS*WdZDvPy2xsay1U2;3%1`*0wn-^z z7fY$lR{0~}j2?&|KCQ7?(L?bnFi2HPTq8l(>FVdQ&MBb@0D+VO$^ZZW literal 0 HcmV?d00001 diff --git a/examples/images/weather-anim-frames/13.png b/examples/images/weather-anim-frames/13.png new file mode 100644 index 0000000000000000000000000000000000000000..c5ff9ca57327664f7a228ca29c351fa4a4652d1d GIT binary patch literal 694 zcmeAS@N?(olHy`uVBq!ia0vp^4nVBH!3-p)I`?e@QY`6?zK#qG8~eHcB(j1elRbib z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP{KRFC&YE{-o0^gaZOE4 z8#iv$*VhLMFI~EH=FFM@|NoDVkC%{;=X zdb&7o4t`;ox&Mqk6COy-bx4oclEznDSjYb3|<< z*GX&e2+j%&cx>0uF=5&15{5X7BQ37U`^p&J$}>tTEc?l({WkpiiWMRT(-~(uhpbpo zu;fwLwYgL8uH}kYck5T0*X5nj;<6QWcH8NT2B z@L?M_JG0T+-zh5RUpt+o}h0{Sso#CGjLwOxj%JUb@#Sgv~{P$RN zRp*KtSDkei>9nO2FW!O+sm#1Nw4>e5GfKn?038_H9Y3aoNVD|C}` uQu9(6982;GoD!3>(~I&;^HLmh3NjL%KoXmGm0SgCVeoYIb6Mw<&;$TW#s1a+ literal 0 HcmV?d00001 diff --git a/examples/images/weather-anim-frames/14.png b/examples/images/weather-anim-frames/14.png new file mode 100644 index 0000000000000000000000000000000000000000..b3053fd7ee23356c36e401810a6747df7946805b GIT binary patch literal 688 zcmeAS@N?(olHy`uVBq!ia0vp^4nVBH!3-p)I`?e@QY`6?zK#qG8~eHcB(j1elRbib z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP{Jd?C&V=_E^hDMy-iI` z8#iv$*VhLMFI~EH=FFM@|NoDVkC%{;=_(Jbhi+pK$RDav1qpyyO8&8+p1o zhIkx5JN5OXCIg<UdXWsFoa*JiC*z)!)_PfOot3-hBhk=o0U7J z`P!ejn!)%%cb>0 zYnBH07rwkUf4*p9+uREc-}s(8G5g8=w^7h-WlQP)t}3;FErIXA|GXP}?iM#ObY499 z^A4l>j%Lfky0z-ODifC<+|9tk+9C9_VFOn}Y_MNVFaHk(jz{s6I--}?NNwexP$S^k zWAn1D?NPIZVEG2_h+1`}V_Vbq{!V09Fl)KdIfL7Q*J0)%d)6uSvwQ^|`vug`?dK2K z!~26hzeTf5=|t3ZV1TNYxJHzuB$lLFB^RXvDF!10BLiJSATkLtGO{u@l?^pF4m literal 0 HcmV?d00001 diff --git a/examples/images/weather-anim-frames/15.png b/examples/images/weather-anim-frames/15.png new file mode 100644 index 0000000000000000000000000000000000000000..f71bb7f01c36608c44296a4443cf851357e17e09 GIT binary patch literal 690 zcmeAS@N?(olHy`uVBq!ia0vp^4nVBH!3-p)I`?e@QY`6?zK#qG8~eHcB(j1elRbib z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP{Jd?C&V=_E^hDMy-iI` z8#iv$*VhLMFI~EH=FFM@|NoDVkC%{;=_(Jbhi+pK$RDav1qpyyO8&n|Qi7 zhIkx5JN0H!vjLB5uET;^lQu2+^8f#qD-n|>MxIHzwNg&T{Y3lfZl|7&id~nALd-ZV zIG95MjZc{sa5=afczRg>S+BQDZq)ZWo)>i-0*pHrJ-h7`+J0AAXeon=Geet*$h?r6 zDZch6u6nbc6_}+N`+omAv57Y2j4RGEq{*m9Z{z!wS;d!9RKWU;!CQHs!v~@HTMEyA z40IK@`YR#6_1~X0dS^Dsb1wMX6g`pQo$P<+14~6Y6|V28f5e!@@QmTl`?mJ&!JJHU zwx65q&!uvZ>)Gee*|wDes+(*oL>m?`6e>vzgfX^!%@UgYqt}w7@5gqPN9Q!>yV|m! zC>Dt9x|8*4;hBSKg4z~LYl_v?^1RMgeK%%RNapyclBCCA%CNNitLB0~SKrL!*wyO$ z=-Y3H?)FK#IZ0z{o(?5Qt1djEt;|46F>z zwG9lc3=Ce`>$sz6$jwj5OshoGU}$M&VhGW2b?Ku#payl24dtmx1y;GG6}m||sd*_3 qjwSg8PKn9c=|%aac`1%L1sRD>Ac@VpO0EL6FnGH9xvX_(Jbhi+pK$RDav1qpyyO8&n|Qi7 zhIkx5JN0H!vjLB5uET;^lQu2+^8f#qE8A4`b<&L9hVUn})J)JbchP*ic$KO9;>``s z0S=`J$h45! zD|anU-aXB?>m+~h?2B<$ohxjM88fc3WX%(g{mynQwbD;zQ#flG!)=9i4IdqAE*-LY z?yme(vAQ>Ud)fb4cOnwaiyZ#5mMgK}x%E%HL0hxPVPVC87iJ&E2&N7DrOni9QRNPggb|4g%czrWx{V34YoxJHzuB$lLFB^RXvDF!10BLiJSATkLtGO{u^!%{ZRk_ literal 0 HcmV?d00001 diff --git a/examples/images/weather-anim-frames/17.png b/examples/images/weather-anim-frames/17.png new file mode 100644 index 0000000000000000000000000000000000000000..e2d1851dffcbbe1dfc7c1ccfef5fe363cf1e5063 GIT binary patch literal 689 zcmeAS@N?(olHy`uVBq!ia0vp^4nVBH!3-p)I`?e@QY`6?zK#qG8~eHcB(j1elRbib z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP{Jd?C&V=_E^hDMy-iI` z8#iv$*VhLMFI~EH=FFM@|NoDVkC%{;=_(Jbhi+pK$RDav1qpyyO8&8+*Dq zhIkx5JN0!@ivmxpWACzvvb{NPfA7zf)njDRI_GD7mOnN5hfkk-ILFIHnX0bpn;V)1 z9%xrNon-W5=-}8f{|)02_Nf~CC9XafbYR}m%d((rULJSs`>_5MD_IdJCAWiqn!xEzr^-SM*1pdEreqze!2YIr2UCClF`PXSeBZ^RmR|AQ6Vg=65=n9-RAT&luwo$Z)0eyT6G8-vOzH%KQ_1W~p-& zm{mQgIV-%d`EAbrwcn+VbV~8wV^uJ4n6!fRN6}f~>3%66t?deC&3LbpwC(hh3BMVX z)i@U+9KcH9hY@@vg<}?1UJxhgVuU3iGMjof{pzX)ZW$c zf2xrF!2Bsiv%58|VI43;RZCnWN>UO_QmvAUQh^kMk%5tct|1VagcuoF85vj^nrj;v zSQ!|+ve$7((U6;;l9^VCroqtC%ES<&;p);yc|Z;7AREe4lM1YIODlAfa#HhB7#vIT n3!D;@v(t<6OY>43a|$vNoj?+sca>ZPYGLqn^>bP0l+XkKslfh5 literal 0 HcmV?d00001 diff --git a/examples/images/weather-anim.webp b/examples/images/weather-anim.webp new file mode 100644 index 0000000000000000000000000000000000000000..e015a54087dd31416727c9110c3ff0262eb39f09 GIT binary patch literal 5734 zcmchbcT`i^y1)-bkSZX(2_#gdNblVs1ccB*I!F-$BB2OMhXF(&0R)vM9h4%*ASlg2 z1e9I`LJ@|lNT{I+xdGqIow;k>`~JA|-pNYV+I!`k^{wCc+u#1qHr0nf99RLs>Wa3B zrHP_7H2?ri#IFVbTmS&AYx+hM#1Vmbos5hi?j*$F>Gks;XDI1lf>+Ii0X{zIUIe_Xn^-YvWwlr??O8D@EB4w{duWStk(7Gs6siBb;`t^ z+E3z8ZLiqbgeOe0mwj~pEcSoMQSylz_vXvO*Bulkamb$IR?eO2V@LB`C zb5WDj&lopvIM%QG1E~Qo7_tFP>%(yaOL22^G=P;0EBs^&TuIP~ApwcD7kTTJP83vRl6RZt#L zY=WU<4ByC@r^_B*7NY{p9mY7Vr4x*v{_b9pw$yp;=3lsRf;OdgTvj z9lcPSo1I(MWoacAox%a{x9+{w)<}{pKV%6zqO{wfI568~Ix+L^pIPw$0Gep&2`krA zR+^LyeWzy52y@_cnT{zZrUqZ?B!`H^ymg*!4*X?9`0=1a=#m>&`^{5$ zHBaxFvYp=3gWUXf%`-Z>B8#-NIgr4Ip7Umc4wMk?@u3(*HHZ`QpDy(T-j zZOdu3_oN865vI7^O5^HtU-M~gJrN5jWT(|Z;Ex=r0|3!i+yBV{)kzN2|0`ehe8Hlo zH%6osdX5Rt$du5CQIHkEZ<1L?mo!}PG4%b;Rbz*7k z=_ycN(D5m{cNNQA94^LR{{o?3YRjMhk>kRb-`F-nM{qUyiVIr@I%Q0!q$K_zrS=z6 ztS6+@{wq?}D7Sie(1mOna@Cb%E0zu3@-OQg0}*Iq{nQT{|IQ*MV>;p=2^)JUJfscd zsRN_Zzs3Pxh=k#9+)rWeO8^pTZ#=0_y3U$v6M~}ZanG&I%+*YJUx(<+svDp1av!cu zx>sn@=NkKp!%EM4vzDCon&FER-am^Hv!|vW6yeUgq0%!3$C~84)Z-g~*)HV-C25xT zl0gJ%KTfhb;ElIoW|mmTOKbFvVQs(Z!hH^pK%#*baMK`?*||z3|jsoTE(5 z=N~(VOHFehb|dx;PO1H~9;*C>+G#yh`LC#1p}-XeyPpCaV-6iz+$ppA5{{o3VLQBn z6^=-SKv1nr#>{tlYL8gYDCjs)%_C+p;ha3AADg%**c()~S*)IqPt_XHH*C$L)HdVC zX?K;KS_X8sf9S~#yB|K*5%w+99qkpam(%j`&a`1LyEb*tbkjs%L7AET6xhXmlOU{o zcHp83)1dOVZI_g`o*Y=ka_y(8rOX2pQ;CwVg4t?OYu2eMrnzOMY>9ftbNZqSL|BxI z*4Qf(e1=AjlZ)VLNHwQ`+OCLtVV+?E#{6b2HP5*u>-qO{E!u+bmns+(obKnh2MRt43 zMe*~ERLC~b*yu9W&e$HN-mPmJfwD8Z-O$C4NktiRj29}IBY>>A(XH%k2@(d@eC=uJ zHKfPRnQc85W!b&#lsj%3I22Ak87Y4qgck1{$gFFs3QQi>^(5S%d2hl-3%K;ra&T6} zOm)i)C>QffaZdW`=6_AutIiAZzG!>@M^}?fTG;3BM(kh5&Nf&~`Zn&aR)>__;S?L3 zt-GalvM1g8W0e!@A#qRoJqK1NIZ*#QbI^eqvJ557dAVOObqsw4z9qs}NT8jRH_eDD ziRtxpyv&%*4<{{-1zmS(gc^1wbf!tY?5N~3qCmFRJ3;epv;eS&=N`Q{$NaRIBu9y9 z7W4g$GEXvii`yPE^Dq4oK1%rMo4$e#L%KB+Svq~62555AH<`tvC?y8xslGJ^sFUp+ zs(1H?C-rwqae|>@;~IP=vHteTPd_>(mT%*%9Q4M z6gnYu40!y^t0bitmPX;%x=ahbC;PltV=C?oqZ%dGuN4%EyNGjI+!0XyrUm+rwqaU< z+mwO`d5$Nh3}_jD`dGLM*q)B=!{wE_I~Cn?A;rIRzn3g1ksZR|4Xom``3ltuhF^Ewt{&2&okwOWpW z0=Ja~5wx4trcq?PYXgiAZ=8=XABJNKE`HLnV0P6DEw}zbVrw9GDc~96N>sx1K*iTH zV-rjW@N8(SWIR3$snV z2A5F#mre4S5|atc=oLtKEsp&mZhG7O){f=^SDVzKitXM<+!l`+bLpHUM6&h&xx!-} z%wIJf=^YPT3R5ST1iOEyWEdb&0iIMzeU9WKG;J=4f!D7rV0%L@204MIO|3w%J0(Nz z9_{nN4+;(}%n|#Rp@o|ciG2!%gQeYOjO*MHIPdeXf~sap@Vlrb3&#*MzE0{m_KW#xS;C)PKHZPCf&Ev6srUqQ1?R7?-|6yk}ZJI(ZQZb6)7a zD3|})$c2zeMiAx@@ATAya3Kd&r61ShpU3ciT0Sd(m7PNo6h@pTfl#<1Adw!(0ZRyT zWc2v{Y_hcz0!SVcDSE|v&9cu^rpU;$UaL-!%)<>w7llL)kbnPlb&RRC-P0eR^qBQHSSYDNnYEa_4rLh& za=lS$x;S2+MT+jwR4A`<49)gX&`U1NHr$3uaJbh6>Z(QheiS^seZ-DeD!I+=&63i` z1S{uVDZKWb3?x`}SgrUu^SZ@g(Utckj@M&TJW+wwA`V|H><*pRJa`-{HLqLgi$}9gt{wV` zU7V#$SSFC)N}P9Wxw_Bb#g;26){~W#+A+>b`ugG7lHf7Ai5Nq5(>#koXNgIdm7({L zfJ^q;vn6NW^0xH(SH4Er@EYW=JU)zbjqL+RLO=C9#b!K_OqU4G+t{U7R#!3>2ur*l z%+SQ_Oka3Zqp3LKM+s{w2uZ#h9a&6)x^4fh8JBK-Z%Uq!Rs4|nfq~`HhWqQC)48_f zeL!8nsKXcpbrID8P}Bl=KB-73m`=KrhK2U_$a)WlZm!mI`k3-}XyL${c2Xuqs?{}c zaB}Me4fbk?8lR#6ISS`d@9U@s=Cy*O@JqlAa*EYg?4DB47o0v|uEb>G&nf%o#5cQ> zb0PrX`pXWRPMneCB6cg;r-+lSuB!;5Hea_t!qb{T%zFeNKL2eQ*c!fC{U8$QrYBVz-JNh-7P%dV% zLwLd=W)Te4E-@uR0RRdBlbBSPSB81-MrQgv^Wq(zxQWwd>YN(oKuE{ zWJ9&$H(YGuCrs&yBQ=QvGKZGrAdfEG&Dg-vO>!xtkvm93dR?bYz|~cixR`-GLr)0Y8QYN%(?$Jn(XMx4##}*CkKfkP8vY z@@cqq?MamSzDlo$dP?n?H*^jStB$yAv_mX;MWF4QgP-x9*vxPe?#ZF&pW;2W z`NDsW7dtWWWD#U`+x*_welbIV*5f6H7eOoj#|A33DFN2RHr#y(14H#Tkx35aRjUEH z2f}Nccc%**pui7{x6Zk^U%W-e;D#c!=_AF=s#|SeGbRh(&I(7=e4FJ2Z71JbwlQvK zd_@`ixR%T>G!$)CZRYz=#y?}`J&oA}^7FjwlriuB5;FwF?UCp5La4_EmrX4mzcDfD z_l+*c7R)dZ@zM(`qQ5j9l?A& +#include +*/ +import "C" + +import ( + "encoding/binary" + "errors" + "image" + "image/color" + "unsafe" +) + +// AnimationDecoder decodes an animated WebP. +type AnimationDecoder struct { + opts C.WebPAnimDecoderOptions + c *C.WebPAnimDecoder + cData *C.WebPData +} + +// AnimationInfo represents properties of an animation. +type AnimationInfo struct { + CanvasWidth int + CanvasHeight int + LoopCount int + FrameCount int + BackgroundColor color.RGBA +} + +// Animation represents a decoded WebP animation. +type Animation struct { + AnimationInfo + + // Image is the list of decoded frames. + Image []*image.RGBA + + // Timestamp of each frame in milliseconds. + Timestamp []int +} + +// NewAnimationDecoder initializes a new decoder. +func NewAnimationDecoder(data []byte) (*AnimationDecoder, error) { + ad := &AnimationDecoder{} + + if C.WebPAnimDecoderOptionsInit(&ad.opts) == 0 { + return nil, errors.New("failed to initialize animation decoder config") + } + ad.opts.color_mode = C.MODE_RGBA + + ad.cData = &C.WebPData{} + C.WebPDataInit(ad.cData) + + ad.cData.bytes = (*C.uint8_t)(C.CBytes(data)) + ad.cData.size = (C.size_t)(len(data)) + + ad.c = C.WebPAnimDecoderNew(ad.cData, &ad.opts) + if ad.c == nil { + C.free(unsafe.Pointer(ad.cData.bytes)) + return nil, errors.New("failed to initialize animation decoder") + } + + return ad, nil +} + +// GetInfo retrieves properties of the animation. +func (ad *AnimationDecoder) GetInfo() (*AnimationInfo, error) { + info := &C.WebPAnimInfo{} + + if C.WebPAnimDecoderGetInfo(ad.c, info) == 0 { + return nil, errors.New("error in WebPAnimDecoderGetInfo") + } + + b := make([]uint8, 4) + binary.BigEndian.PutUint32(b, uint32(info.bgcolor)) + + return &AnimationInfo{ + CanvasWidth: int(info.canvas_width), + CanvasHeight: int(info.canvas_height), + LoopCount: int(info.loop_count), + FrameCount: int(info.frame_count), + BackgroundColor: color.RGBA{b[0], b[1], b[2], b[3]}, + }, nil +} + +// Decode decodes a WebP animation. +func (ad *AnimationDecoder) Decode() (*Animation, error) { + info, err := ad.GetInfo() + if err != nil { + return nil, err + } + + anim := &Animation{ + AnimationInfo: *info, + } + + for C.WebPAnimDecoderHasMoreFrames(ad.c) != 0 { + var ts C.int + var pix *C.uint8_t + + if C.WebPAnimDecoderGetNext(ad.c, &pix, &ts) == 0 { + return nil, errors.New("error in WebPAnimDecoderGetNext") + } + + img := image.NewRGBA(image.Rect(0, 0, info.CanvasWidth, info.CanvasHeight)) + C.memcpy(unsafe.Pointer(&img.Pix[0]), unsafe.Pointer(pix), C.size_t(len(img.Pix))) + + anim.Image = append(anim.Image, img) + anim.Timestamp = append(anim.Timestamp, int(ts)) + } + + return anim, nil +} + +// Close deletes the decoder and frees resources. +func (ad *AnimationDecoder) Close() { + C.free(unsafe.Pointer(ad.cData.bytes)) + C.WebPAnimDecoderDelete(ad.c) +} diff --git a/webp/anim_decode_test.go b/webp/anim_decode_test.go new file mode 100644 index 0000000..5b68103 --- /dev/null +++ b/webp/anim_decode_test.go @@ -0,0 +1,77 @@ +package webp_test + +import ( + "fmt" + "testing" + + "github.com/harukasan/go-libwebp/test/util" + "github.com/harukasan/go-libwebp/webp" +) + +func TestDecodeAnimationInfo(t *testing.T) { + data := util.ReadFile("weather-anim.webp") + + dec, err := webp.NewAnimationDecoder(data) + if err != nil { + t.Fatalf("initializing decoder: %v", err) + } + defer dec.Close() + + info, err := dec.GetInfo() + if err != nil { + t.Fatalf("getting animatiion info: %v", err) + } + + if got := info.CanvasWidth; got != 64 { + t.Errorf("Expected CanvasWidth: %v, but got %v", 64, got) + } + if got := info.CanvasHeight; got != 32 { + t.Errorf("Expected CanvasHeight: %v, but got %v", 32, got) + } + if got := info.LoopCount; got != 0 { + t.Errorf("Expected LoopCount: %v, but got %v", 0, got) + } + if got := info.FrameCount; got != 18 { + t.Errorf("Expected FrameCount: %v, but got %v", 18, got) + } +} + +func TestDecodeAnimation(t *testing.T) { + data := util.ReadFile("weather-anim.webp") + + dec, err := webp.NewAnimationDecoder(data) + if err != nil { + t.Fatalf("initializing decoder: %v", err) + } + defer dec.Close() + + anim, err := dec.Decode() + if err != nil { + t.Fatalf("error decoding: %v", err) + } + + if got := len(anim.Image); got != 18 { + t.Errorf("Expected len(Image): %v, but got %v", 18, got) + } + if got := len(anim.Timestamp); got != 18 { + t.Errorf("Expected len(Timestamp): %v, but got %v", 18, got) + } + + for i := 0; i < 18; i++ { + frame := util.ReadPNG(fmt.Sprintf("weather-anim-frames/%02d.png", i)) + got := anim.Image[i] + + for y := 0; y < got.Bounds().Dy(); y++ { + for x := 0; x < got.Bounds().Dx(); x++ { + if got.At(x, y) != frame.At(x, y) { + t.Fatalf( + "Expected frame %d (%d,%d): %v, but got %v", + i, x, y, + frame.At(x, y), + got.At(x, y), + ) + } + } + } + } +}