color-thief-0.2.2/.cargo_vcs_info.json0000644000000001360000000000100132500ustar { "git": { "sha1": "e88c7a79b35615cb21fb4e2b63884a9ef835cda7" }, "path_in_vcs": "" }color-thief-0.2.2/Cargo.toml0000644000000021650000000000100112520ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] name = "color-thief" version = "0.2.2" authors = ["Reizner Evgeniy "] description = "Grabs the dominant color or a representative color palette from an image." documentation = "https://docs.rs/color-thief/" readme = "README.md" keywords = [ "graphics", "color", ] categories = [ "algorithms", "multimedia", ] license = "MIT" repository = "https://github.com/RazrFalcon/color-thief-rs" [[bench]] name = "find_palette" harness = false [dependencies.rgb] version = "0.8" [dev-dependencies.bencher] version = "0.1" [dev-dependencies.image] version = "0.22" features = [ "jpeg", "png_codec", ] default-features = false color-thief-0.2.2/Cargo.toml.orig0000644000000011610000000000100122040ustar [package] name = "color-thief" version = "0.2.2" authors = ["Reizner Evgeniy "] keywords = ["graphics", "color"] categories = ["algorithms", "multimedia"] description = "Grabs the dominant color or a representative color palette from an image." repository = "https://github.com/RazrFalcon/color-thief-rs" documentation = "https://docs.rs/color-thief/" readme = "README.md" license = "MIT" [dependencies] rgb = "0.8" [dev-dependencies] bencher = "0.1" [dev-dependencies.image] version = "0.22" default-features = false features = ["jpeg", "png_codec"] [[bench]] name = "find_palette" harness = false color-thief-0.2.2/Cargo.toml.orig000064400000000000000000000011610072674642500147560ustar 00000000000000[package] name = "color-thief" version = "0.2.2" authors = ["Reizner Evgeniy "] keywords = ["graphics", "color"] categories = ["algorithms", "multimedia"] description = "Grabs the dominant color or a representative color palette from an image." repository = "https://github.com/RazrFalcon/color-thief-rs" documentation = "https://docs.rs/color-thief/" readme = "README.md" license = "MIT" [dependencies] rgb = "0.8" [dev-dependencies] bencher = "0.1" [dev-dependencies.image] version = "0.22" default-features = false features = ["jpeg", "png_codec"] [[bench]] name = "find_palette" harness = false color-thief-0.2.2/LICENSE000064400000000000000000000020720072674642500130760ustar 00000000000000The MIT License (MIT) Copyright (c) 2017 Reizner Evgeniy Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. color-thief-0.2.2/README.md000064400000000000000000000017210072674642500133500ustar 00000000000000## color-thief-rs *color-thief-rs* is a [color-thief](https://github.com/lokesh/color-thief) algorithm reimplementation in Rust. The implementation itself is a heavily modified [Swift version](https://github.com/yamoridon/ColorThiefSwift) of the same algorithm. ### Differences - There is no `getColor` method, since it's [just a shorthand][color-thief_L76] for `getPalette`. - Output colors are a bit different from JS version. See [tests](tests/test.rs) for details. [color-thief_L76]: https://github.com/lokesh/color-thief/blob/b0115131476149500828b01db43ca701b099a315/src/color-thief.js#L76 ### Performance About 150x faster that JS version. ```text test q1 ... bench: 1,429,800 ns/iter (+/- 21,987) test q10 ... bench: 854,297 ns/iter (+/- 25,468) ``` ### Usage Dependency: [Rust](https://www.rust-lang.org/) >= 1.13 Add this to your `Cargo.toml`: ```toml [dependencies] color-thief = "0.2" ``` ### License *color-thief-rs* is licensed under the MIT. color-thief-0.2.2/benches/find_palette.rs000064400000000000000000000012220072674642500165000ustar 00000000000000#[macro_use] extern crate bencher; extern crate image; extern crate color_thief; use std::path::Path; use bencher::Bencher; use color_thief::ColorFormat; fn q1(bencher: &mut Bencher) { let img = image::open(&Path::new("images/photo1.jpg")).unwrap(); let pixels = img.raw_pixels(); bencher.iter(|| color_thief::get_palette(&pixels, ColorFormat::Rgb, 1, 10)) } fn q10(bencher: &mut Bencher) { let img = image::open(&Path::new("images/photo1.jpg")).unwrap(); let pixels = img.raw_pixels(); bencher.iter(|| color_thief::get_palette(&pixels, ColorFormat::Rgb, 10, 10)) } benchmark_group!(benches, q1, q10); benchmark_main!(benches); color-thief-0.2.2/images/README.md000064400000000000000000000002460072674642500146160ustar 00000000000000- photo1.jpg is from https://github.com/lokesh/color-thief/tree/master/examples/img - iguana.png is from https://pixabay.com/en/iguana-reptile-lizard-dragon-2427495/ color-thief-0.2.2/images/photo1.jpg000064400000000000000000002223140072674642500152550ustar 00000000000000JFIFC      C XL!1A"2Qaq#B3Rb$r4CSc%5DsEt;!1AQa"2q#B3RbCr ?kۧͭQk6Ɍ2e,UۘXOj# [we3beZds;⬐IυQ@OZ[ $)(G6",Wņa=3:[ w";ĸ,+j犦(rEHFxpd8xFsBaKr]"F&FcF4.=3Wb>8:O[Zڮh y[Kdqrrdw)6=D1P" ~T5{?AP 3@4Evֳlْ1ֹ^!Tko:uv׻s;T1SIc5?i4{Q+61Oܩ(]nHkF/MҞ͓;~(%]J#ǞZ#O<{N.?4zzEh:ET% G46?{V}L0nçYA\pq/S4?x@cʧ_73O䏦v81(G/׹AoڅV|>XDY3.-ԇBc租1Y# {6:1S'8KNok[[ ɖe8 bIG^*}ksyFW -]1v2$8P#$ssR)'ZRĉh WBAϭUZ5RGݔQ4DKeܕ xȒt+8N=P^lfI˩>K.˻+NE "g:mNjjJ%6yIKl!YW.r# N9.T*7 G(1g{bUs5 {7Rӄ731&< 1^3@oEw Ao.7#B" !R*tQ@PdA, H ք 3(7җ鿃 *n\U- })-88>.-Ϥ1X紓ث$O7x-͍YcXeq͍,c9|vfT$ƴtBEnl{|/'.Hsy{~bB zc EEtr0jɕp߬vs+`m|wk'GAeӤg~eB*ıN1ElJUNLb!T@e[&uF$ .q er˥H*#R^kq{WNl)ц|f.1/js [X2?wܗpԊV1* +*0w^)Q='Vm7ƽ24W"ICA#b}sZZ{=e%IiG gBCIμLx⿤<Gܼ^O'P={$(շm2WzvʻXqsã{); v%5yz ~mt;qcR$r EmJ9\_\*gUfƝ>%n+2tc>ǝDň} IV\`@ 1~%bs$R4n ?3^w]?|q#;k$cS6u \-\yW?W\yDr\.l"&enhՓ!AƤV^WӺ䡦01BBBRʊ+n7 :HC:#sQ^E~Q40îG{QqR2 r:uGY ڷ2@a)F֢daӾAGyQ Ɂul9a3={nb@t,H*hsZ;YOIsB|>m/ . dVI[oiߙʒ6'#ε>Wik/ daZ }qv;f>>9bB۟< ׎)~˝ԢgB< 9LjlC-B6]Oj'GVB\ۈec %`Č1FYbYЬrs]5*jn¢BDZ,·Ye qz%qv5@hauZ F!*9scH91cR}n1i":bV8bͶrEnZ}HbX85[' |7`w]Nғ뭆ʙl$%-1 •"| ,Kx6$~H6]$[/j֕`ms .Iӻq $#$ȁw>][BJ y56t9o?v?x,[/z~}兵n~5ÂEQ~#?\-B@е(XOmCf9'P{BT%hY(zua# ,,./!\g,*.ޮV]fWknyIm Hس9xM~Z)j[#~;mX=$F/.F\$Jױe({ Ic.m2=Yd%|9&smE$&_qku E ${T32ͷHt1j"MN!Kָ߭$ t}2M+(RXDmb#,@W.EV?g?{,{%p>QQ¹RvC9F»:;p&I_ӇwĖ){[SumWs^V)}>gY*4+En$=pެJ#Xs.9,=zVZf߼ιܬx,]n~I gN2i]wfwO1ۘ-Aݣ1yVKсV>-t0ĕ`07!YV$.6T!. ~>\8ݏ[k;hF "P3\O]MvۓZNRDɀwZ\m@sFL[=^)t \?"޽1LPB'f\Tz @ K>Y@˰LJ;&U$0Ƭ` MH؃5"2>iGrZ8$>O3G(v<>GlIi/s)?<;T?ז,ٟQ6 27ud 20Ж֑Pb|r],"8..t@s7B&yiTf#rqUq ZN'6FH=0F\rDv<ÖTX' Xhrsղ! I2Yԧ?AIGdT'Q٘P[#dA q^m΃̯\!][8/fJ+-s34& `j[$3%vy"\ܩWwlavSsWm2NNnefID t c7sTK`6@2LHw5IY_15{w-ె#5]Xb+3[O<7xnbg]J_Õ'ϴYKOL(iMk@Υw!FҹVS>sJKC#+)w,T)ȏ5y/'$K3_՚v_ER>Bh2:K8϶Fdߑw=x"5hc' )BY)V-#EG;agI4Q|s9;6 %ȜGV6lVll40}MXS#ۅY⽻/ޑw!> ocϕEko%KIbv=J"H9 `3>.TaA89KbO Ӑ@?{uуb?垫=):_^ז8{Iu%ӭDT tƾ-fFPϯ~L, %ߋ!%c5+-Z,8ltgLU;ǦTN%aAsi+}0R&s'V]rHuhXV#jEb<=mɦ5c(UwՖ{L: |5eTʋqp4G2+lb%=iq.VQ4mQ)n1,]\{S$#{^:#Q+\jf8>L8٪|'aU')a'9UA8_Ɵc$6tVN*p~nDw\m&ꄣ|ldr;ΝCǕB>Y 1nzISf]RJ! GYoz\O`jn3J61 Bv#'\J:[FT:2!է]l P* VE=:ձY /F ^ D!mgb3?G_ pВ͠H'ubG⊮+OnQX l#8= lhclxA S֌;&XZ Y/U֑l T/p(V!2>bbZ~T|&3F|-)`B߮RKF;ɠ`ҳ.q|֬`dx$kʈ#$ 9t![IV] x5b7~G{bT( uGܛm{s*CT C( r@>nLSY =Ff+QWD+$e+z6ߙ:E{KwA~5yeI17 m8ߥYK v5nj0Orr($ҋIYW[Ι[58/|{%ۖàl=#E hu F1qnwnpS_bTF ˾Yvcp|g4|;^ xrp ,Ik5A!Fqʼ{7l{UkO ; q<ŖlnN#՟UkoPc7wS>f^F)IÝg9:[q\"h-8wqr!inV4Rd2Lҽ$ԩcQO}u CX]w<1‡*M[ݘ>}畮6̥M$ZG1Hb FAuW_F{ǣkr.bͰ꺂[Ӣ*1eFgq ZbB4`5`u qWPXukTpv r9ޥXd@\KzZx#۾ Zg0Mk‘!x܅SΤM:=+z'faNq\eu3VD9m>lF:cG92_wNvt4oics,W1m ͖ oÑ؛sípeW,.UJ|kEYլV> x"~g2w&yF^,ߥlf'l=F+c uԡ1#$cK(OΒ@!i]qs3i5| AcN#x@ rqӮr;Rr795d#*tz{UlIp2rQB.#v \1f Nđ:(IJ#~)[,FDQaG: FV p[8By(hd UYWL\>6kn$KqݙZaTˋ;\Y\WaTyxg)O4D$ 5g ¾eҰkm7!ɯkָg&Vƪ"ˈݳ*=YֲE<݅FR҃kkCW^g"ǓFqBc_H#L~U,#,huDfI%p$JpjX(9>9 ׬+W[=NxkJ\ȒW,n@RvƂv^+<]O=,qT˂D|F;{)g 6@ϘYu_w{ĊkA1D =2)etojwT~p5>Bcu\quoV:`WGKn'=v;[h4m=I;'ssuK}`G8@ZC.KR'GoO^&3+N r7RQv|sbO(:H ]jV|cKSKmf;dv[ݐ]J_Sa|v?YIg((U/Bpv~HۼaFãx ];rMQ A%ż@%ۇy$*0]H_ j2\i?4> D,mFp&lhʙWKFvHmY2R̦, AT}j̒FS[Pmxn%f\JMs TiadVأTb7Xvɨ[j\Fu1$m ^o) 'ZzԷ UHӏ},Çe[$nTĭAZj[hcΏ V@Du. #j*2W]Ud[ȯaV=8%uc#W Ț{(Wކe.݉vWE,3K0SΦOV+YtmǗut~$ᴷGm$>L/{\ܘzw\9=tbvbq .mjg#5d6=ϚQۉNx 8#zY՗4tN}Z) '/Ǟ'wC{]Ibǧ?-f 7`I5/ 2 <[ؔvϝ6\UژN9<NΎ 7=h2Nf*pXʝ2JVÞI +t&kr<` }#-ka=]Cu!FjTy3K$fDzi))ɌqQH4NM3{мaCE=dFC#]oZ[.48#d  ~u&۸2[KZ?ysA1$+H`v Ic  dpvբb0zy\rKTy|50.t簯B*GͨYuM{Q C ( ӝFޑȱ!pTEс J$ljNB7׉M-=l~h#+61ּvudz*%YcӔo5Jil̟ʯQ89 ,Nĕmq[ yIYrw/\{cT|ORM\} II#O/Q:v+hK;($n}*&6yApYi1;*WNz׈yg%Gi K{,xæ⁈̺u1bryt;|PM,4X(R.j٪..}IrRM@,ISQs0$WIGɡ"TLkdT) WTeljsPs֌1UɑA v[6ʩq 6ђjS6Z%>HݨI8sΗR7tXN}i:wWa=>o㴷B Yt a*x!v'Js֓ Ʒa浱8EB,"$eȤr3>.K\pt4,aK,_0HT HSҫźj6v1!(dAA7j*n+e:f_ jAqʌ"64b=.ulF %,2,L"G~BWe8kTF]fc 78)oovC/dsknVX"ē6Gqm=1qd镦mQ#R = ?Cwmxg5vNCATPlj*a{i<7/g˅=&6xymkև\:oMU]2GNuI3&H{`Ìi#%@ޤCgϿI-dCݖ$]-#ҟ"fϴlҀD//]cʻeV ]|$!fp2\0塽r7˨ﲩ.-1k9 Q̇*@;líi-F<&H/[߉ږˬpXi~h͍]+,W!.ϓaPpۓ AS? ٢W'2QpMmgxy#N ᐂM,Ezn9%oD< w,='i++~pXa -;C[Q*kS$)yzʢąu3; lK$kaigݲQ2+c5Sx@99t@$!ˮ ! `O'IlzԖ 22d&O ΕlvzHrOǥ+`d~ב"\ 09Zxwq;^+']ߗg#eJN\'y_OŻcĘ4 ]\OO4=\&52'Rj}"Iilໍ9].#lƦux@ 0XY"Hlհ. y,]U/PE_lQꓕco޻x]LJשl~7%k]H W3Hq=*[bb_0U̽<$4r$}3SbMHNXgڅ dncNWl}WW"[{A||TEfӱ|iHd%U_ ˣߥru.,irZp2;ᨆ ܂$2obZWiqw<1*(Ȥ 1y.Վy$e>CS8cX $P9->bae =;m[#!Ze65n˸_xi,'? 6y@ވ?P늁FP,例R{:ՁcREزYgJmͣe_Ұdoߟ=._$T:g=FjK|@i/JH1xTC2j p7#V7h&v9Iq,xSU$[qۤR! 0~4yLݣE#WIVX`w1:Q]Kqgi8 xvT9(,Q$~_خk{[q^(uG :u1qX+fύZuAO1R*e  '.Y"WY'L0Q ;a Y +lcH6IbC = ڪ?D8> w~>τw jD|7zjy><^r+xaʠA֡(Lo ii*Y ăϐӌvs6kw+Дa(m=9׊AU^~ݜQbQt:gvKDu̞ p~{ȓ,4.-uԇ$c늵=f7,"=rpsjfc+}HFTTiU3w>MY0,N=kNQ8ҩ'J&\eX~ KoOC޻J&uI./m*a=s.7ŷ;/׾"-EcR hU:y Fe҈4KRݯĪ9=mo.b[#4/@2퀾V6X|u.IR[鿵Oh 6{?TNjV0Y_u2 1ImD lmo7NކgF;hZ$ Gw.N'ES1+tcea9W\0;G ]-{{NFNŧQ]G|uZܜYEK\7V uyfu3C##2\Gr7Ý6{l,$V #:uAޡb4ۍY_VXae~MaX9ck #]VvHe .'Q#^/p3nGA\lF t *Ovev ;rj j̥Ҫ\]m;wlPc`E2LLH;-=Ľ2kp3pYY^ӼceoL2gҎhu&~5Eokb_quk 5qhk*8ԘEh{p_p>;涒;/y*+,CUƣ* "\ g>, 'yi",j>&*1E] A;8]$}iJŁ ,U9#dpUϮ:ԄEI-&vu0^gQI$XV=Aes#N5F Ic$@N{UqٌN4#DPv…T[& .8UujHb nx_d#x{%oMP`ǍJ@D8ozP4 c!FGdQYvFV|Ս6-qkuts1f!ӹWG7Mz@e޼EDL6:r29lp~5.KqC:.1JF@Z'oCO줷L6,ln{IPI5ɤe gnVԿ0BCV׼Jڲ+%:l0"@lRT]oXGw܋ykn%g5&x-.+ɀ%B~4e,=I?JCr(GXߛ[!4RI;gdE} ):ϟ{k-;鷷Lib(3c-W_i;٫>bK{ n"~˺ɟCbnRd>L |=K|MWk88U))>3V|Tn^kE"]?yo28䵆DukyV9 x}@դ;y71yT<ݮQh8͕\uMlY>.Xsbp7~BPUq ÀRT1:!^D`늩 :ArHs -!ΡOJ Wd]:@{o~(a>1W'w]:JN-~NFFcJ(9#I]lprdD7n C*ًXzu%ŔGv]C;==P߽(!LkΣ˭Q.~b5gp;opeAP c6ǥnŕMZ0g;",1G<TX0eU oXzpm-H@!?:VJYNfHՍlB7zcD^y%@*84b%ԃ)(?yUF*ّC:%fLOc / g? 6=U ysQOgw<LjeO%.${dvByr5SR{:n׎΅eȱ3|(Ϫ.Hc˵}:gN0F\4ѡ[/@ 0g($=:UPdfv;^XYs0IƠtO%(&)σT$|~5{3~K1P|o>^\G$'N~ةee!Hdy,3 Pe>NDj݂"S3_%SF<>3&CJPOYMb=A9ߥsr&{~&7 ],jS]DfU b#b1jXhO<KoyrU1ɧ ?o(RJQ#1J+Ylv_VNϔBPPDBPQ`➆Զ҉sⶐMl&qo!+(ٽJg۵~gkVE9g\Lgm͛?fq jks oNe4Q̡yFyJ\$éJ`_P/?H?w{U.)=Y: %Aݲڻah0-;/A9#ғW"!" 9@3GrRByLtM4o-pyu`x}%oJ L$qEHWCv#s" w<=%(J#CMI:sȫ Xv[qi-·ẞa#VK*= p[Weƶ'L:ltܟ=!Qk\#r$T*bjB@!T j)GBljօFXsHˢ˱Y3V(~@@;vy>8J!H5|~ y#IDIQ'V#pk>=+Lmp2,}يg)&Vx2+犑C Xe ok$,7FPvN^n#뮱 Vq[p`Mb^LK2u l{Bl2%989r7QKyq*#A L K"'; E݇hﭯ +{ՏYI\;Svypڶl$e,⏴Kt_8E[wM8T!q[tsn1k g cptzd9RE 1!H<JZ2Uޠ] r9y6E}$̯h%VAC+dx`pN*NbpO|'FnIR\PNns\=O'8gKn)gHJ񫞑J7r$j̿6J VnQ)M_{Q:|Dx6dc*_'Z$clf\9u0%cUumn9j""̱:Z"ьfw7!tPTKi$X$K\bRKr\] Ւ"}D)b; 6ڬÅ嚊'6Eí{ιf?WÊ8C[-FG'oa ]T4wAp(0$cALmHCNIQkI P1|rK]ܫxεaz\GondPw3.ԑZқ}>;Q-̅J`j/Cf:*$ې ۗ]f,; î@YДniTXD1˨|u\[3ݽ/e]A;ɠVf\|+F |2}l4cVWEJ.I+n8qY[|HMI)z  0i䳕&įeK.[pz]W(=*i)*hxlOy `J'3׫…tmE?{ط\7=!oxd֡ciQ$D40|uk^-n79kICI! TZTcW%͑ɕ)\sS+^X5wSLИpp=) ua+69-&oJ>~ Iic%DcaucŸ:h$1 L4zBU=zUg[b"*Dcyݜ 4D̹%Wn.0/VcoqX\޼D9ƲJ4:E=⫼sGJ5յQ&s-դp!0MxDrA8S>ًtGI+.Y љUBjղcudk/\XwRE"R>"EzoxSxNڽJXcB;''^׹GC5(@T JÕXy@huO_”6X_FFLf.㝢$<rgDsz9W6.ŕdRG<5XX59``\aHԾ`NO:vA7E| jՖ !g@qT ,ecqQ3%0^0 \V j6>nRve偰ޮ\PZe`I>0NHڇ`ܬ9yQby?w4"+vK, otSƲf@K۱RrJ odSW'G'&R38Q$*w+Uauuv'/B'Y/n"wer~~,zSME1{DBHɵbo6J[rXo> }Ԉ :t3Ϟ`pغ\꧹8yKJI"0F{jK;qps#⾒ $dy)ɟ{Iy(!KXm`0zTQu}A٭Kuc0~rQj䤲BKm^ht%;N'N>%'֢LQ U\yo̠y2ƿEI 1XPfcqD58<uJɖ׭B&?^ey&v|7W.!۹g\lw.;3Ү i`ARޛUovB6t<?R I¨+C\,@Ξ\rU Г2]c>n*wqH Lg#q1=+? +1ҁ" =Cec؍,_Qo"hJ#OOaΙ:{p^D@0RrX?XIY:dVOv?=w|WdtEU *(^?m @90(!1J7@"VA2ďbe 65TtQTif=FFgʼǏk7?ܿkϓKݽ,[Nͣ 2C ;ƌ%#[l$QtU#f T3}_@7UE&&{mHq'MF GMlu+ 03?zҠ]e@G{H \ Hc;|f#5$ߪ1O(b<9{'ht <(ll [땷XpF *cvKRQd3=26e ek}jr2NEh1,13q)0\>f,9^f?xWQIT3oC{9b€ TtYjNH&$w-iaOtwqmNBQ|ƝmG+%g I}3Hn5t>Lj e0 Z\1O5m9WKMYLW JA+:8w/*$tll;yS 3I)Y4H@[8 ~V%YZ(¶H7u#,aqS^x^'x>Y 2*F;%]M*e͒2<Ҽt;f|6dC&4Q.|*HV_q\'I5i@z殄(țF~dg5mBc8#6#PV*kԇIރ#>&q(ŕqʥ@mqjqMQ=je %ϩƹzpm v[EX !&r0B¸峷.|5VmU¿v7@ rM3;RTsLY !9'VFwTTpTZ["?RÄn^hkq+unQxI/;K_Y"V@9b9Nߕ3@@LtWcj K":2܌VD6Ipq*ɍ.\^-(T[x:3g޴!4ܥֶ)gt듶U%.IAr*H_14[W{q@i֘-qxl]΀IQZMQ|3],Xl}Hw[*pT#|qqYO1sڭ7`14A[Lią_I&Lszr4֍xl=hŵ2!*ƹ݃ܒwq5zlnKj%Dd4Oo f[/?Gܮ hiFITaVgIciI5 tx*pU\!ќ7HZsi]sq c]1尸= iaVFQެ ث$*?F OElH֬bc"19QFD~ @p=sX'e; d'iF.*ͱ3 dgb5Elq5{&fxс:dRblRgor%Kƶ@.c wlF*c^3dE==my~X V+#:B>90m`%RF:Ҙ[GaLr%vǯ< d]ümQӎ4-5=S,*F46yɩYڻ3.d|B@bbS f}^s$$y6K#L,nmcB;k͑!*Lw_)[Y%d+us e9͡\xWD'Cݖ jc0Β}j(vuxSl8I_2XOt3L20 bvi$(ϔY$u=6qƮ֓+fA+EzVa'm(HGV$.HN* 3DP'Jۡl!Ι; 3Rh YY:Jm!H0磊FeIheLD$I| pCM}Dz5(xʢDU+r8CObF6:ӀbG`drInyϮ!Hcd*d<|%j6")+2:OqvԪNJ^|Iq+Y&A֌Dkל]K8.uM'>g5J^ z GA`oP! V(LtL}KyyH7|l)ٯ Z|]s_ԟ>&Zɜ`^gy2DP?nXrVF%Xp|lyR=' irPn Lb%v9;6UG:y.WŸx#FK)VWWl8mR#rWR47bԺ1s,{tU|;L{{58/I}Ww`8 vfwmَډt79|R鏔vEgȃnF9Dy;0ET{KW4@ZTQnp.$wn u+}y>*!5-So"GMib2pμ@w8$ceOy~%i1v&^/bBq~zMF|sq_|WXx|--JɗD5c$^%gy5r1[\E0m\ܸIdRV;q4}BBBt# k&l6lx_hӳ++Fr#\]G۴tjS5.[{{5#L#Ydӧ[;6B}1U({tP^݉`779P 3]q2坳%%ċ(@t$cBN]%dq@qXo"H40m,y⫾.8#xa5uTB0}yi$6IAb,2;:vKM6qDb̻e=yձሥe]҉f5M&Q(lv\c2`[\YLm);SBMpW8F25-p s*JN~Qij呔ܳ) $u*}y -r[pn8fD%}'P#sʄ0{ߩSWH7Xrt Ԇ]bV*QME\".K.S&[>%ML|+SXNw2M>h{F``jzm_P𹧧qGWi]9QX Wju C;nPyocЙI wAk.]O-YeH}Y0־o,R<ƪ UmKX~IHŦ`M,*I!ɗiC"+s5Xn.GUGFp̤u`l5 WJD䐃u?*}N|@uOh DAmc(جiwu(!BzzFGE2X_- \\F(p}jwDK9|!d&2HOIr{;RÆ}zOe ōC>⺿odZU4!ʠBZAP!  V2 2GH?K A(I2Ԉw2BzTU.oc!^޿1;rxUExՊSrVǯ&m2ri>)ZJTc/md;ulGv\#doKI--'~yYWR쾷1S8QOEj:e0l-1HOp؊*rKr~ f#xWӥ){<_n8yd'3^DM3hn3A-+A]V =dh$>/4$ib? Zh47vWvkg^j1 ϕ/xg\K{QMSG,?86{w,zqioe͢R^ߗ뷩/47oi,-VTaD`FҸZRTv1dR;6pgd"䗑cJ.vumLq{nC!"t_ =ty[؅c w eybԪHn7TY j HF4 (>.#RD AUu!">t;<#Hp4OZxLs0|֎(Ƃ05R*ZH*%ܢ$D:kW> BZDMb6TcKCrWJХL<0"bVW[<##a7WK{}ww2Fӣ+39@C<'omT$ЉP6ZM W&- ESd凨["C]#dt9| lG ~OFK$Q2 pcEUΦ$8Ꝇ{>VPI F47!^w>*&vykRF}D$JI`;oCgްOfǒki[ TFBFc,Cv=ጩ\M; 6<1Y;gVT`ګ5S2]uf@0]E)cֽ٬N>I)%Wȶ{V"8uzWOy.}FDYWىH=V 'R6GEؤWwMYq//8?A]=+SK<ݏ%Q͍Q  09˥c=՚k>!=VN=.(LG и!=k$e3#.I}FN[ 9+~߰w=uAl+j dOx5߲h#;s^/DP\r:Cm.9Gœ)Ѳ"4TZ7.$*x]H (01+ ׯS Xjȸ5zmB&`ABHYRD/ h7J݊;0^[D{"Wcue|Dxa~rk½< { `|%Z@,pޕ*+!aN]j]AZχ,3~Ѿ?=qK0W_ͫ&jRE 6k<=kz͝}1o\Ix> :[uMs \atO6=knt}S \ކ%d# jb:M;{pqic-E 5 ,_C7&OQ_^^EkV?ƣpUWIyB 'Fe9护0V=I|ر¯Zp ȞOFsyVKϭO6jߎA}#ü7=0[}\\?^ޖʮ]xFbt#I\1CYoT"$c1N5 4d?"c1=&'k> SWx7MqrB?$>1K2% Zb BxB>8qjGB>5(鹉$^L'b7Vu+f(Ss7z.]V297$Rث,H's8ċBɍCqąMV( jcB%y4 Ғ>nO&WܑC4/qg  \!ӷyzFTYozKΙY7gD4nXQ: iͤwYPW!X9dWaU9y}Say &-aw{j5sr(?g-Hwh@TF1'$wm]#Q$F52(cAA; mL/wqzZVHsLswT/{m{be0UQuFW͑VكJ14[NXl1l\mk QgYCEH8hHYFsnS|OZQ~q^bK W&8V-4R,8+Y0N\6l8l>g`$WJ (ҩT8ں>W#xz[h A[ˡ[XƑ24P7kfw|/,0Agmt!rwVE.!gt(N.ps4﷧W?zÅcw]= ''Nw#})AF6Qu`INy95]%QR#fͿPEj58;ၤ,L|%S Q?]Ubmr#xFK#P#el 8<rErR;cOdi9 |j:z;=kVl Am99\f}dd'$k5w5_كGmg$gA,ݐSg2iH-f9w*荽#M潆—w)8i+T?+N-P-,by'rke{(tl!E A-S\4͑*?JI-#$%#YgU0 g)\2=hp, l0OWbm%M+oORJdٍvqVҮWRObWDIqr"4=7ExNM\ zGqqKy>#=NO" uҼz^G1vMDD[vN0IryՂ-FJC/BqҒC!]N敐RCLhJ* ⸒#4 ګF]2lG)I{YQD)ғR)'N^1FkJػx'f2x ypTqU&-ks\UG&Yq^<\*5m F:\>zy'Y5 m9,rH8C6A:b1\g^fa$N [vMɲ]I0&`s;zoUj,_ RJ\FPGtuTY&9HwDP@Dž'>C5cN{|S]4/23~FvBդ͚C9pC)\((Q[VXj5ũP$0NwkhҘܦ@%`TmͲOLR:x[d`R'v޵p)JQ֠P2 QRb =[ =Ž}ċgO@9ǪbQF&.]"E:-a3?I$Ukq]Oz^ytGo/honHd+QP ^7[͙d${}ON(lb{hw ̜BI]~$]q9YP^xJ2,6^cn`aWGJ'ik-ucBȟ$dtSݽ]i%# JJ#D z2Se37!j,̝]q˳Fs$˭v#嬕8/_͎g4~6VV<&-GcaԞǫ (֫UWOHcZnܼBg59Whoŵ0#Zi%_JX_s, $X܉Sƞz6saO6|kYTB['#'޼Vn{ Ö{Ea#6 ci\bwh6Bp; Ӡ6HHYhڨ8r,N wtwUg ;[-rD8/`gY x͗[$6K%<0n< @ݲxBrrmd.ͬW LC ~5ϫGbyEU5n2´和Lך @Z2oz(xv9e`IaCcB3^4z)$ќ=tc}:&7סcɗz*Fޛ<3W^;[1G42jΘ`g<Ϡ *k)Ue;UYl]Gvz>MkaMK: iU@fGy f4"Ehl~~b6rd,(0O; i#\ȋ;f`{GP9(Ua| \|NGۣJ}ݎu~[чGO~.8u=ؘ#Ҋ{/v7~j+nI=}\xPO!ѝ*c>9ܐPt%p>q/t Ö{J4rj_UXft "JF[ajy ~QvIP1k}I_IÆ0E8j'Nsw&V^^F&R20}E>T02( 6Wށ#@ 1?ҡ.F֠K>mVW9(k[vw/#^Z1 qqB Og0r3辻uq:ۅ=k֟Զ>x#h"L[;#nۍ9?J'f =7qQR8XѰ5F2NJ'inIc.^u{$q5QM O mX~߼Ǚ?X1(E$xNwnLXIIci)ed<ՔA[bi7ڞ# R-#K5OJV{Ey1&Upg+ :{l}/)|#Vr+ |XS^M!҉#C;:VE;4?<q8YFK#8޲p~#.#M_+|2N } sqCo{ kWMW,1r$γ|~%RvFٔL2Z,o4 Vm!XW?{mcK"}y[e!]pyvQsf\T4(:ƾҫ,w(Ɂ5MYW:Fwq]"Ý{/yXPU\N3 jALaB!)$[mډlF;BJ}Qyl72(]y W D@ޱL^l #6LrZRz6~+=W^G, /g 6h>,=.Sy?\װ}בG&V@!UA1J0 JPm&_?|_].ӕnv?uZ7*EȶD:^B] ^?W$b/i5tqL۫Ĕ'cLS;kvYFwe| Ǧǭ4"CzTo̵gxD;8Ԡlc~\+-\%ϯNWxt.: PGc:S_JŊ1E9Ϟy$7re]l()Y`b 7: '(dS>λss٬ !?3Xrkã~9v vi1+,uqb1[l~d^(&U_o[3JŤI.&!1;hn%"#AᏑ=4[<1BEĞ})e:J? kJ_L2 m~b)y,:Tr! "I[%4e0 Z @QOց Wg_wdOh]i KȜ+uO" Apcm]<E:\>^mn]+lWn ,dd>R {熯xkgFz"wג9M0G]'~/ ]y' u(")~tقg'nM cw Εubo|Uz?:q_ɑ'4@^H1 <7]wvMNuF@YV|ZʻnNy+= 8I!ǥ [=캽뒶(WtrI1ɿI%Dxqճ0G֙ؕ'pG΋tf8Yv G.T:W>C'[mx#߁xGo>Au8栌gZйݔȣ z&c޽U*cm!dΏ;Cilo03GnҔWd|2.GÝC@1A1Jv{+$/Co־i_ǨxԚJ>;AɧԝT\c ){(mf܄F]? meR=bec!8mE383m#vw+ ?|+RKk8[ xJkKUqΜ/O6LUs˭> ,Ó#n,[UQQ]hcTg,h<f˕9ak^JXl16ZHb}=*tHN^Tf*1)pݾM)hAlWzO}Uo}H{@`#'ו2 9h)ld27L3.VI2HryW[ShuLq, 4LYeq(9Q]D <3QZ}YJ'l8%G {8>)EsaR:PkJbE7iH^Ȱ JN5 hLocsN1ȢS!s/#=sʼJ= uj҈MX,6Q1y/빦^H'c0eZm/pe u'c t7T\r57ǦinbM$Vˮid($L:TI<b%KUf(OEǥM?ݎ}  KXt_Go*ɮτ?d.O6b?Ǥ;g \;ج[{hF#}z<ُRkpPjSϑODKۦpdB3NօE@@x< 8 8 G?ZQ%o˻-ņY%Q>umY8D Nc]+',v_bJϙ`\u%\>Z ,HP)J؄9~3NBڊ~&02Ƅ+2 ǩAQ<$7'aLj4D˾ .pVHs϶ȪnԟĬm0qN}nugUN?^r/:VZQ^[wk0Pq&_46Wx J% -ǷCPk79HSBvdm)Y+=fT2R٭3za;S~EH#*I  [(9^`UƚQV*u! >1/o\ ˓ J(#N]LbK=^GU啳(BwtEEyd7I6RܱhBnuEkݖY y=n拻31F=K5ru__$vsj CW9av#.ki#ċnrGq ʤ|TDqPrIe1x|~' -oJyv?}/I k5Y~&+?엒흶 se+!n~5h╰xDPOj{O;36%sKw7%ڍJļE0gw?7UTYzko[،cǏ]YD>ڙ"E4^l /Ao/?6#PҸY_x]k$qztshF.. de8ؑ\|o'+0mA2'/`ѻn1zBݱp#F@w<;Oa.kѥl(\@t(Icqlt-$F_Ś)wb(ojDԽ#fgیp cכRg6C8 umťoj60{U77Tsi|ZuUEݲn#,g-2jHМQ-=5b M#$m#(nIfI[t;lm8$!'$⑛eqٳ:_Lxye/sݓŷ<.dWiqG͟7_/[+^K%ݖ8_/ +q; ''JM h  e7jS{/ODbV$}QƲjkOYR>nWj8h>+&f#E PG#^hk 'j!O\T@B Hl/,./ ët84HʌNءa###sT@0NJȨAA#ڃAQ.H'ŃqUtcڻ?> /p3G{!ZqfSۉy~yq+*NHcu`~4@>.,D},c$=٤Ec7ir}G cQ@~|iq?ooizZt`.ըny~52:9CpUĶO^k]}13|#:W"Fa:Ļ.qXI m}tu)*[vYG!Ñ&Dӧ%2 ƦNP^=d}^sY]M% %;[j a y? y0$K3ϭby{culbErʧ:V(![-reT)UQֽ2co\re %][Aij^zOpu36%v <{>muŔȃ%'v=*H^"\Њ`s&*bdQ qh˂AqN*Ky.LoV!Q:F[fI|| ǫeġU6XάZlz)1O=,cFRX%y,+4}ɿ|z^^XN!i!V2),t67l(=qn %@XMhO<5~+ض:MI/fr 3Ź ZVI5fA"i>eE-CÃq ȃ}GaZc_g8#ȨH`wbt3WCTn?ڛ+x.#Km5?SJƹ?.=ҩe-\Hʑ ̎9 ՙ ۋfrO>%9 )xGFNC9Ffx4m|R;^yR#MedWP)l{jXRNj>vvSvS'qo°7_MW>m/j8hOͮb4HD  HHKI%fP?CqNӿPp!QNڰц34y2qFKLqnq%wqy3dGI4"; P =@O: $f( `+#9'f /qvv3jx3:exeb9<^Xo7$k$nD)"A"FJJӴP[3=)JSqP<}8ab|+Azj,\=/ina]pX>8" R\i|Zersq=˓elG&g5FGD˃Y2.W]Q;djRp2G_Ys"wen'[x\qf歝eR4as]]NTVh|`5.XKzWlXU,h0.OaǼ9tXcԎޫnA$ <[qp4a:U+R)8R~+̷ jƑR05 65ebU8WKtN@8t!,n2wEP''JohBcZɮޓSiRإ1ǖD, 4+ˋ NWruy!9F1L/Zw\ pɎqyl:*Vd7l!1Az?y{$>Nɥ)dߢwGw΃°5%N_4ybu4t{U#eItD| $ t#eCƟ U8G}ƨdW\&p:3D.U_l26A'{tͧQo!Ia`7$07ɤp.b ˋËTm '7)%]ʛjb߿dgoe.|iosW6I AzQj P7kY{+O\JYY'Abk$r~#NJ(Y/i;Ix){O$h9GU\Xڶj1E+\RWq wHe||BzI1 ,$a.Wr1yP{:΢G};puΏ@E|Җoފ?#dE5JtmѺIC,JˑNG^[v{~μNetf,*$Cp&1TB@'b}(6DԵbq;`ܞdHsEt!Nui9+`9`O $6IиlT kHĿ]YRHE&Ѹpkl&1 tȦ 9lQ־cN!fo Hjoߓ f" ĸ6nWs$ٖ-k`a8ŏD uG2c Kyn4v,1@" yg`l(rD YHs#ru3TS.R ]LxQ;PI6=sB@I+3gpk.\؏r<Ǩ=b`r ;%A>֩:EWa*T$'[D$}:/:+).nUK3>H܅#䩲KȷP+G5Փ#}emibrw$̝z|ZxTU#cz$H 􄳷9V(J*r *P%h0Ė:ymR>MZ_x(e `j`:b>xӣC3cģJ[҄C2聙8Lw0)^ŸGvy5R sΐt7$+)X7G$|)d %byeC L! }*"'rGJŀ`(-ӽa呂چ8uJԨ?=gx\'0[? V;{q $ϖcg@hM4,S '%{Gyc5/$p2Ǡ&EKdۥ_ҧvN bJXDv'ƒygey/ݞ%G?0NTTIc*nd)B6ylx.`C`siQ)@l*!;`cRQvt|imKB]"͙0*,weX7R4^Gl)5\:Dߦ.-/E;]&6?ynzi- Vc,ѱCdA&qcO1҈# uJ(P܎j*\]t4Epqm@SA *5 Վ,]wkvq_E+ege+ranBZ ŅvpX\\h [?J $bժƁe<<*:#V2I8R"%Žaۈv{`>_Õ&Ƿ( jgs3NFbA繨c5B#0M3 <{ک_bjeh~xU02ʓrې\"2U9RAV8H #to f$ +III-7D _,'`+rI"H.N^ ĝҿa{!%#?n49'IPU$bf|U f[ 0,]MzaVͨy6,;WY"շޙ TBI.(Эc\0jQ,^H-@)Xl.~5S5܏ Ό2C+H,;:oǗ?ƌ2V䁀 _#I5</M'f.ccqpVHA=gk]#yYw锴QPE@PjY( G t趈SĨw+t/ϫ;: s!8rCl{"WMV5::֫W9U9EG#lw5 `r:juw an7y8$bUEҾ-)uGr['ۭ4y'q9JD?G(dNMmyenxOq?^T+X}#2FЍ ǭHp.nLneY{e CraUD<\sPnEޜZ m劁wZ$۩FRŠ'1ozlȏʘP0pqw@xh03PGɲ_c3e!"K7"g,A9ޚ-ZX1PgR,T0 QE^}3gwRipQcJ"m:.ai⵸f6/F SZh@u{?;پsW9îdxuUcr6ZB̨%'Qܻ=pfi[xZV*I6 :v`8miOќkjud(E"qs>Hcy͈ϛܟXe}}8@^x䃶sNT@Er=`#P0Pl4|n8$U&z'+H>'&8&6ѯTQq+ GV`,ɛ&ElNQ裒A^G oWCD8# JP,~4 QD)¡`FiXD_G o_J4JzFl8 F=7' "8r41qF 2DOvēY+?*<לN??F|8j fF=ihtȲ6e֒ tqA!UmTȺ$I9,C8[w#w< OjA/CVٶdqdfJʀ)Ma4TRgy{'ٹzN!#\ԸEF?_ף{YVFkiviX䳱$̓TQ;{pD-:mWʱF zP%+> t)fD35'N?5 Bu<6eNPlFdoP9!'2`bi;l tRxT?؜ʌ.8sI:s>(e87kXDz )@#' 5S-H,rXf$cϧí Hu#ElLrtdF1˘)*O/oX 6J<. eq~&o]3s+)C Fux;+77ԉX[+oT+JB nRI>rC%WPmc,13A "0FңAS X),5Oj cʴHxxT/:F:xdm*(=ک~GcyWە^`lpkw=8(\taX<-{I'_Grlƺ*]:ua>F34FHޕӨ;Ryc!;փ,@kΫec2 >SEc ľ2;ѐ+bG>;U: -U4eLZ5Bh`B1 8lM]\k'Uդ\c+r#s _-s6N~u6!#: 㾐#=MT (7Q#j >4Ϯ1PlO€h1 ov;s"J͏JA:S@HZNvgCû9quwE5naxLJIiTh;R|%]Kt䆸w aƨeBcoCzcdhB}hXO ?/PtEA |jXgSMj]'DPR`3{'Y*4nugrTE\ ,)JJdXNN #awRsLT߁;qyio>^v"1L1{=Gks$ftfޞ"G٩C21ӊ=im{;690 ~˛e;ߩ KsuQ.$`TnGrpȡ,271hcp6Qۡ8ÀAUd'2:5vQvȌdvXm^c.J!iijFw=H ߹ 17~) 3Fb<2{ ݐjYF뚻4݋&G@e0X޶='wic#]}(g'<9c9%77ʜ̇> צ/i.{fkQW}#}BQ!84 PHQD T%T!GqE#,g#ō6gM[Nǂ| geRV,6W=I,^XʬD}_OEl3*nܤGI=0ڇjզ5/&U8k?]- S,j3^*Hɢ[23 t QR:{Җ˝#1Y"ȋ!?.FGU#v|NM=ֿR~#Tֆh 5Bi`\lqN.ZqlMvg M^]2(If=OgÍB*+OL@Urқ;1u4aEN`789^cA2ŋPJ vyҡIc^:[ga" ?@Cخhr\l~׾*fᓤn] ;/6 |Mx!v4+8ZFnG@ddcҪ-U__z ~[9#=܁yY3N5aw\Tdf'Բ1>TS *<{;@ = BⶰZ)eTd+78 >D+[q4doNLFBiqwtj]9c#]zTϑGpc{`71 KL'㰪7]⣆ɏH{uʱA(d<؞srnOF}cQ=N Tdu2EcPsf;B6cajb/k`})[]Lx~LԫLRFyI Ǚ(Mr%?Q_r eDtgңSM'D =S/B@5 G*AR1МOK#R V$m!g`^s_{ ks}o:^䏛'|*䇗#C'L)MĦW7}'zNH&ʕHrQ)s f*9t(U;]^I]R8ID)2ctm]+ alJ&ݸ# UM\D-Ro@]%̲r "3O›$kF x}yR{c>ʠQ g;zHYIr7.(FW=ƛ#v9Pym}=o8ۙ$`N' q|N)Bh{jq8fVƍ>=j鍆1EN5$,hH+KniqOrGzE@[ıD])X s;uss~XWMe;9ĸ7A%_8YA!~[nܸ\uR$U_qBm?Eh^ENM:g,ggBy?r0|Gy 7FcXehD#B" Vc**OCqF|OyuKq̥yx}Q>QMya,P/#ZD<@>uΫ+ `1,>ҨFm R1FCdEy%`bpkdUWyیq#zA[)#Ԋ3$YcQ]R,d;;~t]Ot_gs?3^dpuY:]ꙙfi9DCBsLAB މ&/Z=޵H\ȹlx]lcR{Ӂ?;)fYV@lD|Lm1|I;o֠Y]:+J}`⭋ة>"R52/Fz/ Չ/<׎3HFOεy JLPcWHoB+ $Ͻ(lQn!m\>eMEM?z8W@0||EeύG_WrKE X =U[;3SRG\]_#绶F~8GxUSZS^x܏̓Ue ӟ@0x: #1`dnXzc@]eǠ4l%Bc@ ܘwN +G6R Ù:a8\ {ZmIA2Iƀ4SIuMx/(\g7JDsv|t 6 Y(UXi'O<zÅU 5Ioݡdb|coz(E(%HO1PA$@Pc4A@#;oZ G!ΈDFMa[HT!)WƟ垵E+,e{KPX run=*Yq\3I*Ϝډ POJK;ofx''g-,fYwtʗc/ۜ=ߍ_q S#I%Ҳ\dǟ!O).pv`MVYAL3gZԱʡQsaB ƈ L9!>Nlcâ%1W*k;Ʒl~teL$.u>݋)gӎ(眒6'5WYٵp+c°tyR]rPܺ#}Ϣ8xK#r}O@ݎ¯ogs;x!z6 ?9{׆kϑݾ_D/P휜XBw80 z=[`WןΨn:yc֢\wjK-/ H@QzVltS9?czpd ;rmArEߗt`'.~f4,ٝ:=5y~KCzr3]`="hx(*`hBQꄣXPWE&cj)3}H,fD !0+6Gq╆9ٱLI%E< s>٥ExTƭ ~'sըScJkF)b+}Êp;cрzJKuXGؼU&9c H@J=h a IQ_ONUDglVY4Ay|j#N986݄ܶ%+3zz5S%{[Cy" ±N2R@yOQnᖜcpk^oP$\d{bq->&|kx= DbG_]' \0Z|pߞgIˆd%VyNGVpyx}=hSօ g5,4+BHc fe\1p(podmJ~".wmcƩ.َ%gP: -c*[61*d[I3 ֫lst&P!#PH(Ь ƈ(IlcB8ܟP(EMlFp0NOJ5qu=[ę Vy97EYdjrmq kiKa.pA7վwC)g,5^e RJ)4#lC݉{kKZ(n'r&8[>D 3MI#p+k 8M¡#z_:َf/߿o9YdSI9{DJ@.9iJ\[Vvd#9G՞N #Sh" \\]_V\JRJƣqNsP:rg=⒴;(7]ʍ#eCKF(:PkOh#]6!s\Pƌ^s^K^>6{1L!DơDjC{AԖc/=*nNwa|œpђ ENz Cý}Q( X O-ƨwS$á8Uh%ٰU=ǽ\S)8[yUbH} q s&h2z1 ^?oG]>5y=ұ<Ԭd d!h 4*)c"3@tDҲClZV\n:9_#t^}"22ZHv8v#:IMT7Ia"a0ϳ Ǔ/gQoGpqA}\]]Z[efݚ!ru13_/Gu4y˗  ӡ2sJJ5)0îC9j`@ڃ08 bZ6F2ޔP 2s#AiZ,C!y,DJ UU|$aHA6?h,'g҈xݣbnǓzSK({ڎ9Gca]Ǡ_WFj+駹y[YNH%$#fv]hCݕc+v"78L8I3xK8JΟ~}rwQCk1.r^~.S޷VOjNc\9<ʟJk|?+تqB%߮:8퀛 s_!U.DCʅyo}iD_9{fH?mZ2(++l)imeUfؙJJ1I|O6f57o#*}dtS\#:*znUUz CzxvW6ŰH8WhN+͘ue>+dJtA(D!&hJD&jC~4>DұA(E[|ʓ3ՏZ&Fu,QvuKY@K)e$i |]܋8%qȥC.BSQ0HW04xey.Su?ųkr=5lYLg^!gz 8oÝtt9DfdOxuWV (e#޻#L-SH5X Tt&Pd#el)aE|`փ,D r-+..sK_4t b_^6^^Ԣrqv{cX6׼~#p%i-ՕƲ>&ym|tm^Q+/*&ʁ*7HfܑM<^`UM,* ܟ(R%W8$@u'*Y-;CȌn }RziRk^nL)\9j%>dJ^M_QՓuâۧOGv.a]y * z L/*IT+̫a`b!*bE]~8 9D4B O Cب0&EvF2]Ip9dxܻeQXL6zkog (+'JJ`UYjCh "Ո:G\u;ԠL]̫7#%xo Ւ:]EtePcNUNK& #-ǝ\IrٞWŴ::6r( 6h sHh1WZYSu62:Pe3UɗĮ]'H[@bB)'5<$NRD>(e&v&kiRv̄@#1.4VN/($tSz|M|R_gfOe坎I#Vna]Lp0TboqW?|__tG/( o 01:B<$,FvzURBq_TuHVqQ WW Fn`5+k,OaH y&mnuM)+zYd7s9ĦV9$G&/!g.OXKu9ݞELzil 4]=] &tcc \DH7X~?{@awQ2 &Djj jCP$B!GcAvBnQ9 n ?r|K?TW=}++ }7)c;SErբz[ݍQ䒭8| dᙣU ~~d8Tldaz8S ̑8*1Z ^ѯrs#\{tE2+UFW>B1eFs#2zzL߆ei7,/#ImJ#޻Q⤚tllxF`d<ˁ?z%N.^\[D`tt[)0v*EdTӌj~윒 Ffր{{e|jqǨ>6H\jƞjqU-`Igט>[a$ [lzH%}6fң8c6ٯg\ [v ƗS}O@Db1vm\΢XC4aʫnnIΕL@9'S+̖pjqF:+\ τm%E?q?:2~GDA#TMhj!TA0j )XK¬*Nij}_mGV3$>͵+E?*vogN컒~6 5crJh4̌t0^J9\Y(38h٨у> ؈DJ90P|IX O#Bf%qRتEW_%v@7?U@fcFڊVYRW2fHbIW?ʽ/_xƗ}qGIjr-4(Nʕ6Ɣ%MA)[s %t5]Ő֖eޖLmhaP ?BpFZLv_w&r=~bw~>W%i A PjOr+\wnb1*Vkg`A9q' L/{'JB]=x&K֝K¼1G.kI>oQKig$Æޯ|fPH=l֊L(o,絔89 :oK@#dzų^Ra) <~T&A$<TO*<nXLdbFw P0@r鞿 dRXcqXBcEuu9?6 -Ly-IPJ m& $o7NL$81幵<3p]mė!F" "W;cm슊[VoFLyL~>y63˹>G_R폋fi|\ mב׆qK%OQ[f$RO~U<1M9H ✂Q 8 !4@7B!+TdRmmvS4u9)b~'Ȣ=i=+ q!fV:lAcTuR)Ub|w1x|s*wljbfP C\6Ԭ$⼍Bޤ辰PdW9C?ļ\ERKB ʭ[&:vUl J6[(#-0?Ϳ+^ud96"iSR(͉d;g8WsX C5eMu.b9ȳs!X˽,i{CTϓYBmJ:"f!N+C4HbclaP  0BÃq:h΀w_tT|ٚ1fqەv}(qK8UNycOqɴjI?zZ Y0~5&' 8A~5nL-I%oc\=mmpr9F,MCDuC(6yc89QDhm`F՟> B!|Dz`/!P!yҥoL#:FU`qDVhm[[ 73a:s*l2{aǭ{Ѭale2 ~[]>F|hA{oCqq{=[B H.zWws ץ,q|ߛ99˩G2}?k˚8qO2̞9Bw,$PqP~ʱ=M2ne+!:, `? 1HW\r™ o/0L} lf,mEJG<]~ P][J9>ZMNKtBSqrNru#ҳ(JokL>;U'v^d(Zus^br5o r |j ysw؅ ?zi|OW`}] ^:%g _ʷ,/ҽ?/}O5}dWDi4Q4@ H A @Z$ IJȇgfeW6j=/u+Gu?yfvcC\M%:/FU܀_OaPYRb7"r̥:ޫjR2FV#:Ԍq{ -Ƥ`Jg56Oʕl!.h:x@4{ ܩ68V#5d^h f. =9 V)kv凓#~_"ﰝqwptLJjG|HLt:TF2Ty`\ҰE9c&U]o'}]eN1* w@TOPʔf$7LTGgmLrO3֠9<4|T!vڡ8';T,,'\ppFAVz([voWaxW ddrJttٺi%r|/2x]R(|3<&kXDq$cWzѤ{EvPdix٤WMTS0Ng=2Qg^3<_RGvɔD#9$ǥ%4.;UdadՎ.UwH)/.(7ܜYw\ I4 3hSjcDAX(5 r*<|Q:Fu$$Cι9[ݿ;WJW)ڴiq'ɉc!FyX5s)MXr琗oo^O zy;vo{mwχZGSI#pspG01CĞrcs]G_7W/|UV'jD@O2-DP@O:$:<M 7W oF;U$[%Lp-5f3Լij1ȼ>wG(:kRyk]U7.cmu;yF08hDeV*_o Nb ,d;ɤ<`:* jY#q^tGmՁRe0 mʫF(Rь$$F,$YW,H\& X胜QgVOq$VA2wl(R°hؒAOFkU1 )=Fp^OYcď1z' uf5cG"ȓQQRtYYC<aңAeҖáT?ږLeh{dHa@)WZ2`J)H&oqTwN}#r+) *vn Ls(xSЩȬ&f%wpo6(v+12pV;Gs&-~bF{d^:[+Y, !gJHԫrGXZe15o2ЩS(OˆP( g ;rDqnϘB z% Ojݗ?p7(SK#U2Q̟ /4T>5<=1jdQ1H?kFJܥ/u/#"_XZIzq:9KrHy_?j!/ġsl Ϡx` d# 56rVn.7Jԣ@ly%m${!y v'>اwo GIXʔ|>[ѾI~m,fF*:f#*1eFI FK=MX>gEWAG@U@h2w!zrcIj}E'v#l1ۭu&6cq"!{O]#vf^Taʢe]Ʒ/~T^#6u4o:w<7U҉j7uUEOLO`+8>/z/Ю}kq &ҡ;DBzPDH29¨4mkY)'5w8='oq^I$%bZ1uio])c=ٲ$C+o2rMܣ)!&F>G4y!L(^cL; J{a k$mLMgR@#HhYq|%V~W!={7yrg .O[(TR:z4Q OI'4ρ͹+LNrf]b%N L'ę3$끽[CMk$WV͉г`l0:cZqepR+54ᝫYs>U߆E=H,λh,("JPb$rh4:dy#)҃%2R2QdJZHmԆzFLl=eAZPWᬡ sݓzPqF];띌P1 e20/^<'<;ۡ2d~FM;dV=x호!J8p1gOcn}2>d_u3>qCj8}8B`O1.3?3Rɇշ_/cɏ/켟r9`d8s2#5|f.LN.7`7g)òyE1Ej:7̿H#㲷ơxDmOe;W<E05!@I@y h3P7Pf_'f_1_vǫ~ Ϭ Rja>G#z\vӤN47w_q$*$K[ؔHuD IOSʮ-ocnB})EH8:NQhZpBK3I`c/a3 *črw8쀐:~NK<`zQZ] L\}*%>8RYۯv6U!(r`ϵY`-,x QԠoZtW۹SYcέxͯI`r2EwmIu.N,*@Έl6L?_0)4PAJ5j JGԆ?ޅ `y{Ԡc0=$DŊI9W-y2i6=ՌA)?hEJ TgvS&$7,"!42<*Pp;G;YeGfVxGD_ |Y~)On0q˩;[0"Ez9eV\V! R4NjVkIajhrL6s*GZ%H `۱Y V_EnƵ\n3$9jYq{X6˸oUOn8#u tre 8U.-^UٴL  {˩ѬHevnlxп(ONJQkI,R-BEMo~Ӧ<>lP%VowU%̖F *V/mI@vKp8zM!xXPeR@'Um!c ʑt-퀓V)z7ĿO5PlLX ]'+y _ /:x,Ls5LAg 9_Wc4DpwW81̺0T9ׯ>xplըNAD  P(r+ v 9N? Nq4z9j';9c8rH- 7/)K4xኄxD9$asA~/$42p-lHWE5Rʬ b<oS"/,ey"ie@ҖYZBPXjWӾEP-;gKcKD|3F(S!@p*׸'$F"_"Sot7&Tfے=ˆ=DY ܉@{_{LeHun8g4썅MyuW] 7w~@-*1$w 9ƅ!"D[hċ*s`:NFWC'v1"!`䀶0$[*~{oYb"#c<Ƞ*o¯su1道8lLy霒uk7O04qC;n#lB]Ou̺2AĦ9H{8iYTN:n[!g>e++;Mu $b%OS,r;D&^>1'(Gho/Xd*#Q/B(N|b 5 1M3Uf,694pp*(Mal?;V ~zHH(N&)n?67–[v-mqKkE%2nB8lQI"]+ lI!|du]Y,K'vQy}E1uvN1HܵgS,x~aEPYNV($ +m+\\>fhג0_tX[=!vH#ǙA%?tu4nɰՏy,*yRvju$lbBi4ZZs- VQH,l73Ola@BMDa$D,qˣ8](t#)#i\}LG3Jt!UTA?w耕8$Vͳ,7շKxv2jq̷\Y;tx HMIuG4*`P(t:$Omaӝ * c, ُBɷCѠRH*!t(=G ފ@rGMBV:בenEO~uK$kT_i֞ LG3B,29#V,46c y:Ҵ;4FxY(8vc4()PY*[T!2;z=$(*]!A@# V$r;KKD69TFܳ>#*/"cxve̶:m}<(p7^Y5],NO6q>UՔL{/Ox1f]/ ё'Č>#c@^Tx`T}xt2h_3V\7aז꧚NR= 41AB YPܬ 'Jx0}k?r$hD4Wܪ] ,c[@*ĩ>ԛV`JE;T`.L͖V|F7ʚ/[XOogO-h3%žmwK`UʵH<,OvҢ"%9 6ʒ>T\>ru,\^[Y†0ᙄ̄95$/JVbGnI+g<1ڎ (CP|f4qQd͡@ =US;&9hmihK\B(n rk"N HIt΀Á_$ 36D1Adk@QHѱ̨݄ܔci fcy! $ 2lD ¸ imĘ2Ü)ǧZ}LE9~M*\qreR!K9P b&4 MQ 1u cC ҡ,,R*QbAӚVP+C"$W$YeOªh72+6DSh)FLh# R-cJ4K~h(PL+c-ж:m^tDdí?@RlPBlQPtY.8*9 &.5AQP:zluV1(\ z‘@6)HChuUt9rEfoE8TJ; XOa&pl/bE~6Q\FKsg rv|yxI,ZF?ƥR/w$pFklX $RkX-)Ն8`3H'I{w' H"Q+Ymھb "<3wJG#b"&Ʀ.a3`J&LK$ь}\E Fyid8=s|jU2r U#PRB$+JLoԫoƮ\&VGJ)lHfT=0}0)C5yD *9Sw\ KddGzS6D+Q_~bH\=M"&)mF銄nt*K#)% 6Wљ!ArJБ12^X< V@ࢠV+΂[E-(SՅ{KmW%/1KV^[w9#FɑR[Uw9{ɕ w `,G'wj#S"Ȋ@2 YKZYT($_425RVloQ GS??tggU-o ͥg|un'PqtY3PB46%?5Y@ UhH(NtP|hش8*mʅyc;|j#֠ Q.CD(Pl)`{Aʄ E@ ^ PkP(ޠ8?6C٠AE@(@ 5P5! #IyP d+:FU]Tt9JF "ZtHќ.jQ$ZQGoxt%ᤸ*c7ڱdv$z-'ͯ:^]ʅ[&,s,*1%mXs%=;8 L$=0̧|D_bĦ=`R(waK1#[1 I\-W K(pEy"[{TLi?v9уsiᢸ) ( /H j\Y JH;~^孴c(Nt?R% ׷Ks9ϠSM9hg;e5t#'N[C 6YQef8WU$mmAzH̲g qG|V@X RF.Ƅw\S0!9;tO;Ui5URUR+ ֈ;ٕI;yn SK_d_j~|VTpr'9~׭_y?$#[r?do˗3ރ7b?XM=[rE /?'΢Pyrj?M?G:Byt N_~|cMzycPioqŽ n9P _s%ϥd_7>tQ bS+k|_S✿PG?V|#ʂו0# 'Jd>* yt(KLcP$@$FK2_3J2_(<.uV#^tXA1P(ptQ@//@ҁuP 0O#Ped~EF]@sr" ?,)on5_>B=9yW'ER7ZCV7_S˗/+#|3|`A߉GdUYx'9t˘U@$/oqQ&~3'!W#+/f<ԝ o?֭BKB?>g\. // See the COPYRIGHT file at the top-level directory of this distribution. // Licensed under the MIT license, see the LICENSE file or /*! *color-thief-rs* is a [color-thief](https://github.com/lokesh/color-thief) algorithm reimplementation in Rust. The implementation itself is a heavily modified [Swift version](https://github.com/yamoridon/ColorThiefSwift) of the same algorithm. */ #![forbid(unsafe_code)] #![warn(missing_docs)] extern crate rgb; use std::cmp; use std::fmt; use std::error; use std::u8; pub use rgb::RGB8 as Color; const SIGNAL_BITS: i32 = 5; // Use only upper 5 bits of 8 bits. const RIGHT_SHIFT: i32 = 8 - SIGNAL_BITS; const MULTIPLIER: i32 = 1 << RIGHT_SHIFT; const MULTIPLIER_64: f64 = MULTIPLIER as f64; const HISTOGRAM_SIZE: usize = 1 << (3 * SIGNAL_BITS); const VBOX_LENGTH: usize = 1 << SIGNAL_BITS; const FRACTION_BY_POPULATION: f64 = 0.75; const MAX_ITERATIONS: i32 = 1000; /// Represent a color format of an underlying image data. #[allow(missing_docs)] #[derive(Clone,Copy,PartialEq,Debug)] pub enum ColorFormat { Rgb, Rgba, Argb, Bgr, Bgra, } /// List of all errors. #[allow(missing_docs)] #[derive(Clone,Copy,PartialEq,Debug)] pub enum Error { InvalidVBox, VBoxCutFailed, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let msg = match *self { Error::InvalidVBox => "an invalid VBox", Error::VBoxCutFailed => "failed to cut a VBox", }; write!(f, "{}", msg) } } impl error::Error for Error {} /// Returns a representative color palette of an image. /// /// * `pixels` - A raw image data. /// /// We do not use any existing image representing crate for a better portability. /// * `color_format` - Represent a color format of an underlying image data. /// * `quality` - Quality of an output colors. /// /// Basically, a step in pixels to improve performance. /// /// Range: 1..10. /// * `max_colors` - A number of colors in the output palette. /// Actual colors count can be lower depending on the image. /// /// Range: 2..255. pub fn get_palette( pixels: &[u8], color_format: ColorFormat, quality: u8, max_colors: u8, ) -> Result, Error> { assert!(quality > 0 && quality <= 10); assert!(max_colors > 1); quantize(&pixels, color_format, quality, max_colors) } enum ColorChannel { Red, Green, Blue, } #[derive(Clone)] struct VBox { r_min: u8, r_max: u8, g_min: u8, g_max: u8, b_min: u8, b_max: u8, average: Color, volume: i32, count: i32, } impl VBox { fn new( r_min: u8, r_max: u8, g_min: u8, g_max: u8, b_min: u8, b_max: u8, ) -> VBox { VBox { r_min: r_min, r_max: r_max, g_min: g_min, g_max: g_max, b_min: b_min, b_max: b_max, average: Color::new(0, 0, 0), volume: 0, count: 0, } // `recalc()` should be called right after `new()`. } fn recalc(&mut self, histogram: &[i32]) { self.average = self.calc_average(histogram); self.count = self.calc_count(histogram); self.volume = self.calc_volume(); } /// Get 3 dimensional volume of the color space. fn calc_volume(&self) -> i32 { (self.r_max as i32 - self.r_min as i32 + 1) * (self.g_max as i32 - self.g_min as i32 + 1) * (self.b_max as i32 - self.b_min as i32 + 1) } /// Get total count of histogram samples. fn calc_count(&self, histogram: &[i32]) -> i32 { let mut count = 0; for i in self.r_min..(self.r_max + 1) { for j in self.g_min..(self.g_max + 1) { for k in self.b_min..(self.b_max + 1) { let index = make_color_index_of(i, j, k); count += histogram[index]; } } } count } fn calc_average(&self, histogram: &[i32]) -> Color { let mut ntot = 0; let mut r_sum = 0; let mut g_sum = 0; let mut b_sum = 0; for i in self.r_min..(self.r_max + 1) { for j in self.g_min..(self.g_max + 1) { for k in self.b_min..(self.b_max + 1) { let index = make_color_index_of(i, j, k); let hval = histogram[index] as f64; ntot += hval as i32; r_sum += (hval * (i as f64 + 0.5) * MULTIPLIER_64) as i32; g_sum += (hval * (j as f64 + 0.5) * MULTIPLIER_64) as i32; b_sum += (hval * (k as f64 + 0.5) * MULTIPLIER_64) as i32; } } } if ntot > 0 { let r = r_sum / ntot; let g = g_sum / ntot; let b = b_sum / ntot; Color::new(r as u8, g as u8, b as u8) } else { let r = MULTIPLIER * (self.r_min as i32 + self.r_max as i32 + 1) / 2; let g = MULTIPLIER * (self.g_min as i32 + self.g_max as i32 + 1) / 2; let b = MULTIPLIER * (self.b_min as i32 + self.b_max as i32 + 1) / 2; Color::new(cmp::min(r, 255) as u8, cmp::min(g, 255) as u8, cmp::min(b, 255) as u8) } } fn widest_color_channel(&self) -> ColorChannel { let r_width = self.r_max - self.r_min; let g_width = self.g_max - self.g_min; let b_width = self.b_max - self.b_min; let max = cmp::max(cmp::max(r_width, g_width), b_width); if max == r_width { ColorChannel::Red } else if max == g_width { ColorChannel::Green } else { ColorChannel::Blue } } } fn make_histogram_and_vbox( pixels: &[u8], color_format: ColorFormat, step: u8, ) -> (VBox, Vec) { let mut histogram: Vec = (0..HISTOGRAM_SIZE).map(|_| 0).collect(); let mut r_min = u8::MAX; let mut r_max = u8::MIN; let mut g_min = u8::MAX; let mut g_max = u8::MIN; let mut b_min = u8::MAX; let mut b_max = u8::MIN; let colors_count = match color_format { ColorFormat::Rgb => 3, ColorFormat::Rgba => 4, ColorFormat::Argb => 4, ColorFormat::Bgr => 3, ColorFormat::Bgra => 4, }; let pixel_count = pixels.len() / colors_count; let mut i = 0; while i < pixel_count { let pos = i * colors_count; let (r, g, b, a) = color_parts(pixels, color_format, pos); i += colors_count * step as usize; // If pixel is mostly opaque or white. if a < 125 || (r > 250 && g > 250 && b > 250) { continue; } let shifted_r = r >> RIGHT_SHIFT as u8; let shifted_b = b >> RIGHT_SHIFT as u8; let shifted_g = g >> RIGHT_SHIFT as u8; r_min = cmp::min(r_min, shifted_r); r_max = cmp::max(r_max, shifted_r); g_min = cmp::min(g_min, shifted_g); g_max = cmp::max(g_max, shifted_g); b_min = cmp::min(b_min, shifted_b); b_max = cmp::max(b_max, shifted_b); // Increment histogram. let index = make_color_index_of(shifted_r, shifted_g, shifted_b); histogram[index] += 1; } let mut vbox = VBox::new(r_min, r_max, g_min, g_max, b_min, b_max); vbox.recalc(&histogram); (vbox, histogram) } /// Extracts r, g, b, a color parts. fn color_parts( pixels: &[u8], color_format: ColorFormat, pos: usize, ) -> (u8, u8, u8, u8) { match color_format { ColorFormat::Rgb => { (pixels[pos + 0], pixels[pos + 1], pixels[pos + 2], 255) } ColorFormat::Rgba => { (pixels[pos + 0], pixels[pos + 1], pixels[pos + 2], pixels[pos + 3]) } ColorFormat::Argb => { (pixels[pos + 1], pixels[pos + 2], pixels[pos + 3], pixels[pos + 0]) }, ColorFormat::Bgr => { (pixels[pos + 2], pixels[pos + 1], pixels[pos + 0], 255) } ColorFormat::Bgra => { (pixels[pos + 2], pixels[pos + 1], pixels[pos + 0], pixels[pos + 3]) } } } fn apply_median_cut( histogram: &[i32], vbox: &mut VBox, ) -> Result<(VBox, Option), Error> { if vbox.count == 0 { return Err(Error::InvalidVBox); } // Only one pixel, no split. if vbox.count == 1 { return Ok((vbox.clone(), None)); } // Find the partial sum arrays along the selected axis. let mut total = 0; let mut partial_sum: Vec = (0..VBOX_LENGTH).map(|_| -1).collect(); let axis = vbox.widest_color_channel(); match axis { ColorChannel::Red => { for i in vbox.r_min..(vbox.r_max + 1) { let mut sum = 0; for j in vbox.g_min..(vbox.g_max + 1) { for k in vbox.b_min..(vbox.b_max + 1) { let index = make_color_index_of(i, j, k); sum += histogram[index]; } } total += sum; partial_sum[i as usize] = total; } } ColorChannel::Green => { for i in vbox.g_min..(vbox.g_max + 1) { let mut sum = 0; for j in vbox.r_min..(vbox.r_max + 1) { for k in vbox.b_min..(vbox.b_max + 1) { let index = make_color_index_of(j, i, k); sum += histogram[index]; } } total += sum; partial_sum[i as usize] = total; } } ColorChannel::Blue => { for i in vbox.b_min..(vbox.b_max + 1) { let mut sum = 0; for j in vbox.r_min..(vbox.r_max + 1) { for k in vbox.g_min..(vbox.g_max + 1) { let index = make_color_index_of(j, k, i); sum += histogram[index]; } } total += sum; partial_sum[i as usize] = total; } } } let mut look_ahead_sum: Vec = (0..VBOX_LENGTH).map(|_| -1).collect(); for (i, sum) in partial_sum.iter().enumerate().filter(|&(_, sum)| *sum != -1) { look_ahead_sum[i] = total - sum; } cut(axis, vbox, histogram, &partial_sum, &look_ahead_sum, total) } fn cut( axis: ColorChannel, vbox: &VBox, histogram: &[i32], partial_sum: &[i32], look_ahead_sum: &[i32], total: i32, ) -> Result<(VBox, Option), Error> { let (vbox_min, vbox_max) = match axis { ColorChannel::Red => (vbox.r_min as i32, vbox.r_max as i32), ColorChannel::Green => (vbox.g_min as i32, vbox.g_max as i32), ColorChannel::Blue => (vbox.b_min as i32, vbox.b_max as i32), }; for i in vbox_min..vbox_max + 1 { if partial_sum[i as usize] <= total / 2 { continue; } let mut vbox1 = vbox.clone(); let mut vbox2 = vbox.clone(); let left = i - vbox_min; let right = vbox_max - i; let mut d2 = if left <= right { cmp::min(vbox_max - 1, i + right / 2) } else { // 2.0 and cast to int is necessary to have the same // behavior as in JavaScript. cmp::max(vbox_min, ((i - 1) as f64 - left as f64 / 2.0) as i32) }; // Avoid 0-count. while d2 < 0 || partial_sum[d2 as usize] <= 0 { d2 += 1; } let mut count2 = look_ahead_sum[d2 as usize]; while count2 == 0 && d2 > 0 && partial_sum[d2 as usize - 1] > 0 { d2 -= 1; count2 = look_ahead_sum[d2 as usize]; } // Set dimensions. match axis { ColorChannel::Red => { vbox1.r_max = d2 as u8; vbox2.r_min = (d2 + 1) as u8; } ColorChannel::Green => { vbox1.g_max = d2 as u8; vbox2.g_min = (d2 + 1) as u8; } ColorChannel::Blue => { vbox1.b_max = d2 as u8; vbox2.b_min = (d2 + 1) as u8; } } vbox1.recalc(histogram); vbox2.recalc(histogram); return Ok((vbox1, Some(vbox2))); } Err(Error::VBoxCutFailed) } fn quantize( pixels: &[u8], color_format: ColorFormat, quality: u8, max_colors: u8, ) -> Result, Error> { // Get the histogram and the beginning vbox from the colors. let (vbox, histogram) = make_histogram_and_vbox(pixels, color_format, quality); // Priority queue. let mut pq = vec![vbox.clone()]; // Round up to have the same behavior as in JavaScript let target = (FRACTION_BY_POPULATION * max_colors as f64).ceil() as u8; // First set of colors, sorted by population. iterate(&mut pq, compare_by_count, target, &histogram)?; // Re-sort by the product of pixel occupancy times the size in color space. pq.sort_by(compare_by_product); // next set - generate the median cuts using the (npix * vol) sorting. let len = pq.len() as u8; iterate(&mut pq, compare_by_product, max_colors - len, &histogram)?; // Reverse to put the highest elements first into the color map. pq.reverse(); // Keep at most `max_colors` in the resulting vector. let mut colors: Vec = pq.iter().map(|v| v.average).collect(); colors.truncate(max_colors as usize); Ok(colors) } // Inner function to do the iteration. fn iterate

( queue: &mut Vec, comparator: P, target: u8, histogram: &[i32], ) -> Result<(), Error> where P: FnMut(&VBox, &VBox) -> cmp::Ordering + Copy { let mut color = 1; for _ in 0..MAX_ITERATIONS { if let Some(mut vbox) = queue.last().cloned() { if vbox.count == 0 { queue.sort_by(comparator); continue; } queue.pop(); // Do the cut. let vboxes = apply_median_cut(histogram, &mut vbox)?; queue.push(vboxes.0.clone()); if let Some(ref vb) = vboxes.1 { queue.push(vb.clone()); color += 1; } queue.sort_by(comparator); if color >= target { break; } } } Ok(()) } fn compare_by_count(a: &VBox, b: &VBox) -> cmp::Ordering { a.count.cmp(&b.count) } fn compare_by_product(a: &VBox, b: &VBox) -> cmp::Ordering { if a.count == b.count { // If count is 0 for both (or the same), sort by volume. a.volume.cmp(&b.volume) } else { // Otherwise sort by products. let a_product = a.count as i64 * a.volume as i64; let b_product = b.count as i64 * b.volume as i64; a_product.cmp(&b_product) } } /// Get reduced-space color index for a pixel. fn make_color_index_of(red: u8, green: u8, blue: u8) -> usize { ( ((red as i32) << (2 * SIGNAL_BITS)) + ((green as i32) << SIGNAL_BITS) + blue as i32 ) as usize } color-thief-0.2.2/tests/test.rs000064400000000000000000000035750072674642500145710ustar 00000000000000extern crate image; extern crate color_thief; use std::path; use color_thief::{Color, ColorFormat}; fn find_color(t: image::ColorType) -> ColorFormat { match t { image::ColorType::RGB(8) => ColorFormat::Rgb, image::ColorType::RGBA(8) => ColorFormat::Rgba, _ => unreachable!(), } } #[test] fn image1() { let img = image::open(&path::Path::new("images/photo1.jpg")).unwrap(); let color_type = find_color(img.color()); let colors = color_thief::get_palette(&img.raw_pixels(), color_type, 10, 10).unwrap(); assert_eq!(colors[0], Color::new( 54, 37, 28)); // 55, 37, 29 assert_eq!(colors[1], Color::new(215, 195, 134)); // 213, 193, 136 assert_eq!(colors[2], Color::new(109, 204, 223)); // 110, 204, 223 assert_eq!(colors[3], Color::new(127, 119, 58)); // 131, 122, 58 assert_eq!(colors[4], Color::new( 43, 125, 149)); // 43, 124, 148 assert_eq!(colors[5], Color::new(134, 123, 107)); // 156, 175, 121 assert_eq!(colors[6], Color::new(160, 178, 120)); // 131, 121, 110 assert_eq!(colors[7], Color::new(167, 199, 221)); // 167, 198, 220 assert_eq!(colors[8], Color::new(212, 80, 7)); // 213, 75, 8 } #[test] fn image2() { let img = image::open(&path::Path::new("images/iguana.png")).unwrap(); let color_type = find_color(img.color()); let colors = color_thief::get_palette(&img.raw_pixels(), color_type, 10, 10).unwrap(); assert_eq!(colors[0], Color::new( 71, 60, 53)); assert_eq!(colors[1], Color::new(205, 205, 202)); assert_eq!(colors[2], Color::new(165, 170, 174)); assert_eq!(colors[3], Color::new(147, 137, 129)); assert_eq!(colors[4], Color::new(146, 152, 168)); assert_eq!(colors[5], Color::new(117, 122, 128)); assert_eq!(colors[6], Color::new(100, 101, 113)); assert_eq!(colors[7], Color::new( 22, 20, 27)); assert_eq!(colors[8], Color::new(180, 148, 116)); }