From 4a41376fefab5aed5875e58a481b0cbb68604048 Mon Sep 17 00:00:00 2001 From: James South Date: Tue, 4 Nov 2014 22:16:28 +0000 Subject: [PATCH] Applying optimisations Former-commit-id: d202401f6555c117e25d4132d91a76d65aab0e7e Former-commit-id: e9ee3a8064f0e9db131fd0b136ab327b0d7d29ff --- src/ImageProcessor.Playground/Program.cs | 2 +- .../images/input/000.png | Bin 0 -> 155 bytes .../images/input/effect_24bit.png | Bin 0 -> 15467 bytes .../images/input/h9ghTMB.png.REMOVED.git-id | 1 + .../input/monster-whitebg.png2.REMOVED.git-id | 1 - .../images/input/pixel.png3 | Bin 166 -> 0 bytes .../input/tower - Copy.jpg2.REMOVED.git-id | 1 - src/ImageProcessor/ImageProcessor.csproj | 4 +- .../Imaging/Formats/PngFormat.cs | 3 + .../Imaging/Quantizers/Quantizer.cs | 2 + .../Quantizers/WuQuantizer/ColorData.cs | 61 +------ .../Quantizers/WuQuantizer/ColorMoment.cs | 31 ++-- .../Quantizers/WuQuantizer/ImageBuffer.cs | 81 +++++++++ .../Quantizers/WuQuantizer/PaletteBuffer.cs | 52 ++++++ .../Quantizers/WuQuantizer/PaletteLookup.cs | 164 +++++++++++++++++ .../Imaging/Quantizers/WuQuantizer/Pixel.cs | 2 +- .../WuQuantizer/QuantizedPalette.cs | 17 -- .../Quantizers/WuQuantizer/WuQuantizer.cs | 89 +++------ .../Quantizers/WuQuantizer/WuQuantizerBase.cs | 172 ++++-------------- 19 files changed, 388 insertions(+), 295 deletions(-) create mode 100644 src/ImageProcessor.Playground/images/input/000.png create mode 100644 src/ImageProcessor.Playground/images/input/effect_24bit.png create mode 100644 src/ImageProcessor.Playground/images/input/h9ghTMB.png.REMOVED.git-id delete mode 100644 src/ImageProcessor.Playground/images/input/monster-whitebg.png2.REMOVED.git-id delete mode 100644 src/ImageProcessor.Playground/images/input/pixel.png3 delete mode 100644 src/ImageProcessor.Playground/images/input/tower - Copy.jpg2.REMOVED.git-id create mode 100644 src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ImageBuffer.cs create mode 100644 src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteBuffer.cs create mode 100644 src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteLookup.cs delete mode 100644 src/ImageProcessor/Imaging/Quantizers/WuQuantizer/QuantizedPalette.cs diff --git a/src/ImageProcessor.Playground/Program.cs b/src/ImageProcessor.Playground/Program.cs index abafa234a..a8362a2bd 100644 --- a/src/ImageProcessor.Playground/Program.cs +++ b/src/ImageProcessor.Playground/Program.cs @@ -49,7 +49,7 @@ namespace ImageProcessor.PlayGround di.Create(); } - Image mask = Image.FromFile(Path.Combine(resolvedPath, "mask2.png")); + // Image mask = Image.FromFile(Path.Combine(resolvedPath, "mask2.png")); IEnumerable files = GetFilesByExtensions(di, ".png"); //IEnumerable files = GetFilesByExtensions(di, ".gif", ".webp", ".bmp", ".jpg", ".png", ".tif"); diff --git a/src/ImageProcessor.Playground/images/input/000.png b/src/ImageProcessor.Playground/images/input/000.png new file mode 100644 index 0000000000000000000000000000000000000000..892574db169ee45a83301aebbcc636a21f825fcd GIT binary patch literal 155 zcmeAS@N?(olHy`uVBq!ia0vp^OhC-Y!3HG1DAjj?IK@HkP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucL5ULAh?3y^w370~qEv>0#LT=By}Z;C1rt33 sJ>#Bd(*uBNcsyMkLnOkJ8yMRD1MOpEcq>=Q2NY-UboFyt=akR{0H8@GuK)l5 literal 0 HcmV?d00001 diff --git a/src/ImageProcessor.Playground/images/input/effect_24bit.png b/src/ImageProcessor.Playground/images/input/effect_24bit.png new file mode 100644 index 0000000000000000000000000000000000000000..bd594381d5e3969366228ad57061dc9d0826f38e GIT binary patch literal 15467 zcmdrzgL5W6)3t5mSKGF6wVv9xZM(hNwr$(Cac!@BYU91{-}o}K*~v_@n{0MAo6ROl zNkIw`4i63l1O!n=T3qEHF8Bu%Fi`($2g?h*e+a@wR7MTvU-5-8jr`YUb(PR`6?3q& zGqZOEQB+b>veG26vNLmWH6r06A+dCIb>wAY^6>Cr^e{7aH8ZhfG;v_Gc4Qjw97tDe_aEPAjbM#bOhP6EOnAfxrq9G5)P7 zE?ezvpS*q2W{$sp#W^l>4R+VJyqsZEKT59?uHV9Rp|eg}RwfLi1UH4Yb*+g7CVGHa zs~W7);Lh-Qpb?AV6dQEyn;i2ZX5flyehd|>qz`ca51M9^uCDJr{wcY1bsl5Rjegi?S|K<@hkP0#yHM!>@qjtOZ3|h7=}s7VdG#0coR6#d z9AKGg=VOUQwq0k&tzzD&cSVZAthz6;)Whbb$s3E-U4yOL>yf|S(J)yVpC6#}9&Q_e z3OY~80MVcgCO)fWav-M{0PXVuzi(atFSHg5A`tESO)||EmcH|zpl^>LSc9()61wd& zW5-$*HI+_G8M+iH^%}Ic>$Lq)r+xZMRc~G(N#%K-W}Yn&F6(aWhJc>8Cf_sbXh*Ra zSpcEc8gnQE$Ni@R_QpG>6gzLE)Wn3D2U4>CE!@nIZ_N}b^JzhvSg8prS-fV(oH`T1ZfnXoPVDCy@-Z6oZ@penmLDQx zXIAMr@lXJxK1B6*%n|yu77RFu2S>7ule1^5fc0vqYHZ2ED(N!4xo{p*k_S)G= zW_k13Hg|B+rgN5z5q0PgYGSPxWj4H)>)(jiKVyK~gqqXFZlDMlfhS4N5BNqz{vY8; zDK_wJ4mr3mvhSnb8PZ0mn7<+N5*0m7EUD_96His{0_(q)0sGJm6(BD%%H0n#4=yk> z-w`e&r~7vVS!IbE3q>Ofp8J`f*WHR{Z$K(f-}{$OV7UBi=`UqEfFz8NHyl2|KcV*s zxmQ)_A!t<&4s+d2)I>rcr)(iJ=u-5I+S|}Ypux+GM$LLz75HA@dhV?<$(Yy$xBXvb z(aI}|(r6yNb^|m#SM4zc$A~45*d^gFO>ZDNKL)SsP519h{WpPr)Np011(-*#Gg1IO zG}}D9D*h`tg6M6XDQlQ6*)bFE_mCZ2DT^;}s#ul0SB&Z+o34URjx$TW@g7aYI*$S9 z7MlWM-_2J^oB<4OKa2*R?q*Og(gDdedthkr@{ixoa(+_5`JCUf1RtDAA!(UjU((K1d}WAM~*>o66V<~N&r@2O|A zz1$Bs+51v$r+NR0QnCANRNqNc_V8BV%swqsCghKoFKA6O4)nm0YA?(94HqE{y&!TB z^c<6|)dRc7$3-{~|B;`AV0)Hk4&Q z-x*sj&m{`D{ZttqaIc%R->4Qf4j-V!7qcPeSMY(mzT*wim|x$rcdNax9jqDZQct~i z7h}cZ;*+xI11~{gV6Q_@bVq*ny;8XGbBo>_R{S&K9F1#c6^eCotKV~io_?3w-8V1q zlI{6C*X=l+PDVxb)5B#2)#k?#VMvGARwmfi=f6xdvu5Z_8N3lT-Jxc1&`q@c6#Un5 z4B*GMvaKss_68G{hUGE9=k@W&W(F>SiU?Dyuo0AfICjS!y*=+1RbiBs95WoP@sFHf z=P8!G)(d?C?RbY%WVVB(UVwmT#{@M;oBID$x|FU+;KSW$MG^h6K~46 zU+l6@eG^d-1I1|=Y@S53PdWHC5>!;*BkSXJdk70Sj7QmI59tJ{Qa6Uao_wz^q+C=A(K zAU|*`EWUtsW}eaLKCoRZFc!_v0vhsz}lrLv|gs`%k1!W+064*Y~gaz#KvRh zom5Fh^r!GJj9w1Nxb^MYCn1B+i)KfE1FZkEL@}-Te&nEa=3(;k%)Xq9tB=UQOKR96 z0siiiFE%?fB0p&;&~Yrkw&v?qd*J1oa(cf1eUD+S|=1jQ8 zd+kIz3{Cd@S){tni=y(nX(Mhreb#y1B=(=J4VR6<1<4y~r3*I+u41wnI3(|^Zf6wfocV$aZI|);!%F2m zgCczai5=H}F*WH^m)Iy_498NdUQgL{n^hC~`=`y(5TA+tQ;hmoe!$m_R-TJV?7TeN z*G59e_1fjz3sb=F2Zye->9ey8&`^|$H!NA)6?H(Nzc=*I)}XheRJIz=${31mvnXx^ z2P2I>q>>tsL1iw3W1q+&p6B{NHy98fqfYunvR${**bYJGAVmb3$?y}NPSbfFoS%l> z+?CYCi>82HveP;w<#0M1LrC;t5q3@QgK4KI8)FovxKx-%N{qqKC^4gn{Bzu{#_Ld- zNF9>QOt_?9MmezWH0yOn6L}AJr;xI>b}(J|YUs>y&FEcihN7a!K?OrTI)&4EPqnW7 z0d}hH9kT-~5XpvmGw)q~b1%x}? zc9DW$VVT5aJKlYrQALp7`i`qGYULrUL~cKR74!NyVs{6D-s4&j3*#L5bwl+W9DjAy zsu*4Sln4Ld@3I>J<>{E(R`)SIts0@PMmKWNYxY?u~a|m9t@5CN))h!{k?-S!b z72j*ObjV_3n>oKo+@g+Q`ZXdLqt)^(v9{lx1#QW!J%;VX0Tw3SzD-SjvAr)TFNctq zbS0f!7CcWr+N?JkbJCT6*c^=9_iCfRkoRBES?p*;;DO>y9d`s9QBS^<4pTEvZwD=E z1%|&m2nA+zAn!4P>34Of2=(4KW$UPAjEvEG224>a5ir_*B6b0j#q=Ev7y|=X9Kek0 z_-)DVC{f2`yt%%G5m!~u})$HNlj zdpr1gh@j^9k-Y+CVte0mK5@uNSozB9DrnEE`x``-JN(Y zzd{}Kzr5ypp4^GFUl8WIDThhHEKw{?0gx16G^T1J^?ImWYfj%eLp_|X8tOd&m zb;`a=3jzNlz-J-H_~*T%(>q_n>BM4Ktk5U3qJb~&I^i=*-{WevR%N(l8W=^}Cb>y-;jXJJaMHxE2993&yvd<1fm*L`frxxigab{vZo$IQw;Rq>2*{w2(yX7>dE z47RXL(sp=r+@A?01PHMz4PINYhUq&9aOS^t{@F9iy%e?Vtp_z<+$-{fP+Yy=DqZQ` zC~K(5ao}!Q`rE&e+=%=`@iF5Hqf?eiOcZ@N=nMRAmdX}uyrwPGz}vNFdD(y}>+^ws zx$Z^oMvi0CPYW$^WF2YE#h?YtSW%3Tg){>|9tS+2Xh8z%OdH^K<|w-pmI%f=E8OQy zbm{SdWJ-I5xz}Cy>Q%a-%4SS*sr3dPJU}I~;S38-)>e1$0BelDzWsvp2GMXsHK9()F_KRh;?9QH`$08)9BJgo(w850l+I&CLl_Z--X`cHjT_q;+gjuR4eNeDQj|C3G6?UTd(%r}pF0`_ah0B;T_q+pCH)OuCk zlBGJJ7qf?E4L9X_fu<MO|J=Y#VfWI_#yo^?Si`0GZ$5;v=f{Lkeqg_E z<^zj!Z}uWxNMpRc*O1YiI2oK}i=A5i3}`CRSy0(n?YpvYEG*>T{%;KH)-AyjM~$*v zgeC^7jV=YXN|%hf3T*&rWkV*%ff`_HFTQSkqrf}l#Ktq@TKY+caVhu&hSoGi=we%D?BX zEN}|L`QjB*!XOsJVM=@gss0RafqA}{!TTOz9oGm0j6q9;&k&|EQQMTicp0{HB;D|M z!8Jtcr6AQL#p)McfQIp;v$mB6LGG3oi^nW08%DLGa~7`a30i*VTK2kq5#VObWAY$) zF&t1P2-?^ubakuYa8*_}>$Lg7Nx>q83oKJ+ne)C_y_yrm%y!GS=UMvT4tSoR^&<+! zJK^_n%(Q>mUGda|E-`2kDl(MtQl_ZW7actQo&icgW@=l`;X3AQA2$h9&zRSY&DnPT zL$h$3rrA;p!Ao6q2n4-5jcL#=^<>$HQz(xXh2TvY{B&ifMDZ zMcjfT=%RDPXvIsNqj9VXtbh~;7|!74h~aj|6v&Cra$I;*+Q9QcnNh9kUY0-G#1SO4 zTIwt{G~$#iL-Rdq-$2HIWw9V$nhzSajA&c6*fR~LxORi;@j<6}kx<(yOGQ-le?7Vr zd16t5+fu$>{kto9yXJsa8~N%j*@2=0R2<5cB0yJlI=UlV1ibc=TPc9tGSg)}Kb+UJ z7E10agU_$AtvI=dehz0OG+I5e55lUae(q{wwc@a5q^#$8l`)Ay79^kk!C~{ z`ra~v4Ge+hHe1o&6C{_Y!e(@V<|;hZ${L2M3X}ZvG-~qwY;oRJpnO=2+zoIXY(@UP zP}4BgO|k_pm|bQ4M_Vb!EKPxn%|stlsKdj+qq+#91H$@xY1!f&IxEct`{;{R_MI|q zb(Q+6gUT%TY<0&c@|U31{;zK8gMZ8HDszKWoSs_w%W0Sx8VzD- zC)15r`UI0F8uuX7n&vIidQf@ZTJ@1M1OE1Tro3L)t2O+%#u22jB9;kOxC)lD7#DLy zBbLmH=?WL0R`6dA@Zf^+-oiXtRHYCfbj^g&48!)3>}z_mpjFaJX3(0$(1YN)k;?0C zp6Cp^|5cyy>%5r%kuTvk`HPW{3mXCV9wt8x8s2rWJ;~dq=3)p!y-!0tZmN9xE5RFE z9C&5hzjI12habnude9|WK=sOuCn+u7DCtNKwqy_OgLKmbmZ^*$$l#my^itod^S*TX zRqZWGt#mfuy#3UXDf0Ee<#qS+6aVv>&Gaw>P5dV};12q8$>EEYcw^TBTOFgJN)OUb z6_n~=O|9OLs)K`Ovy;mxbnmEa92Bs8fL-2E6Dz026IAT(= zQ*XajFuH=^ zRXF;-YT$m`1_CUBwutr?@z?ZyjPz9&M)|*991ouWJlgb{I5BpYv2*M{ zHdv$_vu#$ml$a&AW$%3u_#^h$KlZFQkPTL%?Eahi*2ZeeBTi*?7viw?b%y)wvNGVh zPi4q@TRJxV&+FU;Iin{NO6oSli+CsC7q?ImYsM3QS|vuVRe&vkAuQU}n@nr_RN$k$ zLkSrt=mvyKbG&*30!lgiWKp$9_=l?C|auO?LSCu)#;@!+ZEPs5sUFQo>=u2(9A5E>as`+bV%@H-p$7 zo#H$@P`OLlu6A+J<-ToNR|!rmPoZL4yBmmP-QG$ZKbcRoXP*FePmWM4YCASLyp5Dt zJt*5|M=RTXtzfsS5UvvLLO6p+sqo=4mxRh@TDm|rPH#^5MhNI&N9egL2A>7CV(s5i zx4CPSO|rIE2HJL5p9!>IF~uZ=2xQDd4nT~kwE0Iom$zNAw2UWF`1SXZFcvrLu36zr zXI^2`3*M3dME`5|dI})Dh;!e+)7{)U&zLKsWRGS=@@G)Ny-jg|qz2U!3c^N3Av-`H z;R5;Oygn^+U@gFWJ7u~18(OjInL`%A6O(R0x$nc}BU_y9UF3YN*xj3K$6ofXCJ8QzHUe6=|$KFfq;snC{9C$5471XUSfZy*;cItPV#a&YE&${ z_Gk|t!d4USm{!7N{AqQ@7+D(I>eo7INy~{zrIFnegmrl@OCCs5O+w~2a}FZs8u&8; zp@pAd7RMG!U#X?Ttvf_p7t)2xM5#FV#CGl z^{gJRZlB^ri=L@=Xp_>6X=z#Cw(=!`diKCZobcmOCiUukoD8;R2lgvGW5Q;EbO3Sy zyWUBIncDM9^Jqaddu`k9qiQ^vjfXO+sp6sA72D&S>4vS_=bzu~ck0tU#ET?FF;bgYqnfoT?B)Y7dXrOy7B3}JoMptIZof~_x}C$ z%cD8qkpBZ4cvwdV=y*g2o_$B`&3D)L&Rp^&JO6hgHy5>1Ww^UeLS<936WESq1=f(( z)BG?@l-J&Ojw1uvQ%SEBs#}nfl!$k`q-g<`@Z>4I;MSn#$Ub5DE?y^#LpkA+a@|y4 z(U3r%e^Ab}AoR>H74XE(B>3#s_fzU{JA|%y*IV8<`k*K<+N{Vw*WUMO)^|r0FnQMZ zjF@*x$O(0X7aEmLXk1>OhSCBsC@kiK2-#0|g9k|at#O6`FkoYFb)TkKBr;8t}q zaiXtmpZ{%P1a>2H&?W=~02ph{P0SXgRHVxD{4?2qPj=PsDE_)`(Ya`r6l9yqKeoEp zOqt9lzZRVXXN%cKi3u>1D}I-E8SX;SQWYsJ>G|>B*!E$IxTv)%3jD1}BKMCgo&dD6 zpf6r3vccdqT0NSV^d*Xh0Y!$6vr5BMF{^BMY44cJOvxw+aHsHNp{3!0zBY&xTFsob4M`N(Ys8H z1Z(w|Al6^7O>9gAL0ASPrm<7cbr2yw|lFK{Z6J+3w9a0K3j35rI4J4fv{&d&s3K1Q3 zrME37PS7dpdOt2D8wi@dU8mznmkgr!q@$Pieq+N&vT)v(76mpc4|IU;c3qtG)cGcZ z!Npny=BZ~uNBx;vXMga`Mmo(|}FHyei(iYu#@gXgMZM@Wl z#U_ z%r#y(8li*mi42i+K@@ee(Tpflx{W>dsScQCAOA&m={~$V?2q&V1Lw?Fo+t*Phwh?1 zjq*3Exp*eQjWioRKd8J)vy%ny*mOHx2j-r{6p|_mUg&&f21ylYaf+z-vj8V_M9u`N z4pYlurnJ*wC1v{tKO9S8@b`uzoL_l;Rze36sjpSFd={6J`ultv+G~ zo}Gd(Q?S+bdM0-Z6sN$wVxN=H*S?sm3*ip`2^pA?(P+oM;T(_KPKSpJhaY-imrugm zAxq4`-QKYn^({xt@0Bv+AVrW^6WwJ8>gxRo{CUXZ2to3Tc0aUJTe%NV12wbTqVVAc z<9`w`QyyQH%@fx`3@!a3exBnWtGnns&iC2Rz5~mcg^Fb{%pQe86SET~rCGqLCKp8n zrIjETPSq&NMi6n+Udv2G!6X-!QNmE6u_gUa3AHp{bE|=KAjPN>7UC+fvGJ}i&SH=6 zL*VN;#|7xe1};P1K(+KCBeeUqQ=K{g@tx)a^x>`?cGf*JJqcsxHX2g1j(+#I!=zoE zC7T#uYx?*TlHockL*S~(^mYksp4Q1pv?Q=!g=+gUC2Ok_0|CL`_`h6$#MY((p-)!e z*J?S>8eb2ZG~gs~EDmvyGu%K2ezYvKQDt}qx~AP|%EBra9*)~V|4B)7UC?{FRh%YD z1C+g%mSRQLs3iKLys^~_8*@>Tf&kq7(b`8%rLciJx^_5{xOeh7jm*ahd|0%ob&NI) z6)jh5C%S#}bq%krw;Swu3FU zaNy|I6a1~0E*ZRauPN;4d3nTHbJyGcxJjo5HG{HtQLA`$mlR6r2sQ3NiU?u|U5=V2 zYPrg>>6Vnyk@H60^ZG{Hkar9|UZLLy2(W%J4cxnM3Mt2EVmgP({%q@9d>mWclf!?F^j$450}Q!x<_T$(6Iq!iVztp_qK+C_2V*+4Qd{8lke5tc#4 z(4uR>m>4n)_h_VOu8;~C{K@qNlX_ye#0hhUx^xX?uF$XaHT&B-7*jL_stPFw(Wno4 zhC7G56Aalw@e3?)mx<#2m0s~JKTR6*`)#rnqoPA$T7hdaY(!dlHo+aYef{)TMHmc8 zyN>0i6nWLsPr1WM= zJZT0kL^nVDMY}Do|5Azl!`pG2EGT#=D0k2jswA$uOxv(p3wXwVHR~z`5}`6H!YOEE z5RxO;P6n7~%iP*+zf}S)k5hy%3VIIl$YsZmq~Tl@6*!p(E~$ve4?lq8Y7~hICOKmm zPYYz`$o*c=Yf*bF6>(COFK`Yly{yz$j><>c01)@xULb7|*n+@_2F zopL$j$rdcNY7q`SMU68*ta51~9!=qUd4wcBb6OLS}4%4POHc-J`pFRpkChCwN;7=`T0{}@w) zJ0_PW`TW{QTLVBGo(DCJz#)bAE+oE)-RF%*7#`Jb=y zmn*KfUgpcHSiQm_*L;J=6YK85weF92?#)fO&ka4(sLpKLn0V{0{K9TVs^oPt=b3z?=doyUrwXhvUsaMxp zkVXp6K?bV}mdd{aCiq@3z5Aa~_G>k_b5j5IFXx0Yu-urjB3678MtqhIMdG{*LN#ExC_QE;VX^e^9{sW5hW=k{%%z<15UQIDv=&vJ^-(RD=i&>f)(YRnHIlT<; z7)Bi?^>2#{9z#XsHR_#ora?V#n!Su$J`%lcmo%L?XS`J57T(ifjwgJM)z)o~O66LW zAI6~4YJGi6#+Zu?*KxC+yJN3yF`Z3I%({k*HI(jCg87|65sg~kxOQ(mQ;lNdeWsH@ z-o3X4nXjS?T>iC>ri)fj+q}cr67CDj9mMsBh3#7Jm!1=e3D8y!b(}WdAx-3IHwg@r zv{I}Iq*OUW*pjGi!M~mi)7D&mFtO@a&Ul6Yk_DQOKTZX%nvb;fGj@J0)0QAF&=>>( z2G17MzWN}F6>iN&ln|cxd>Y1ywh?=Mrk%?d6-aYw)s?iPTB;C6mnLw^j-5m0Bo|8R z2C7fT8K-f)ONlt_33Rqv=!XRiIfj@MX9c2?HTs7N+AA_%D!cCAV2mLg`Cn%&pXli8 z>XAK8GYb!Q{*fMcwn$_QHNq8Zq!&>`6<4Rps1-4hsx*{e{R3S~4BUA4su4e8uGK>T z&2a$@X#8HUvAi~=gIu#RHB$N)2_1Mqk}nP9ZPlYTK0Ivf6*#=BE&b*6^;LMX^pE)b zeF-_^NiZpSI!|G>Xk!bU6<93msm)TjmlmxlTxXFOPe|1oXe~&(M%qI4zo|Nye|$qX z08;@E>kf$b_g$$q)|?Z$qHEs%O=X^yQyhYU7Sj=kRSP!WKFr$2*bGDZ9(JqF=bSk& zc_%D6rO_%parCq}m$Dn6v9$-PutRv3&W~MLOX?r4RW-B-+O4Sh?-*2jPJF-e{ZcFd z{KEq)laJa_k#<-oh^!%d6&noQ*|`d&ti;5;(mVzXz)LIR){$9Wu$Sh1m8QZalm$1eQa#CuEjVvFkU1`Cq(Cb6 zX!^?X_ji13oXK>B`KZPsQ+`srda0aIjnu2z?nz32&@apLVhngr4LE^w?(wMDvg2f{ z%U49Vtc)^U|1pH>B5-}cm@qm_p>gIGFOp2B$;Mw`KtrZDtgdA`8WBCd}# z+Nb`rGRdB{Kdcz=#_Et4fconZtL;aB2Ingx(s~!_lZrFb3!$68U z)BWewaVBzyWccVmBdYHV`y6&gV?G0O>z3+ZtaVfE9!_75Dw;3cVssJwA0^DFWnwgh zUQRr;y|M^(4$T9vR-Pgm9*@5%A@J)_-``n62!OT(fxAtQ7ZJZ&caJ2a&}agFDJf}r zBA=xZ#&!5FTXLrN<;Y?l2~rBXFUo?aj;&>MO)}HrO!G0?Kaz+r`3Hvb#y|ocbY~?&z^aosB8S=7=#V!^CeoSz1S zch*?+Q$wn@-ddR!W{I3TbKCQMgQ@!c=(wKuC;is<<_Mt)hVG0bQ{fX11NwKzRe$wQ zyqwLLUIa_L?uLs+CpAvdJWpe1(DtMyYk^>)$) z^NjX(Lah_~xZOzcxgF) znrApHI`bdxx81_H7e0GFGU%`&wtjP&H|q11@${cNzXel*H6c3X3w$iLe+FD9!YOe{ zu8!RR`G|0ET;^OE_KX<6(QZ}&N`5~R4^#|?z?*;^p}s>-ETM05=+njt>ge0!NCWN& zDt}Mn{kQC2ukC!qe_Z(5!m#9CB#NV~-f%4j{9aTLkc>Y*8e^~`sAP`-`l02&{v!~O zmm`A@G&N)$Q)gm>YWREC4j-~*?$m?w?Z#j3#_NlN#elTV#vR%FBh`_t7*w6c+GI&o z-`kVbtZy(nxV|%C%OI9bF(0x-$O3OpJfZH;KkdNy&OiP0MT#YgJrHHx=^`S4#T4Er z`1vcOBM9`$EZu017hb^2*|TxmD)zT>>X04ZHDXzLkUgi;EHlR}!LDt=@d~@mcJJWp zE86enLqgN?;n!rUhzC7)TysN^k0p|Sr)IyWQy|reGeCdlo-fVmVR--e4$PS{Cl0+%Q<}A**&)o#)h@*@f)=?z-}aCc{Mtf2{irYvzE&{NKQ3 zMb#ZYuJ4xRA-58nsiwqfFJ#qc832-pFEUa-E|zLP6V0xd#lr>o^jm#M`K|Qc-_s!U zRaret_FxOGR|`yu3`Sj?l{=g1+jZTLfJfAwF1HO2!110Z;lmR?i=SV{_xF<9tbBmb z>H8&1`(E_hEbR;*@L;Vs!tY;aj`f57Hu{R4p|ISu*`|xEBr~nEdRYTc(9ssFTtyBl zkbSRwiM6Ji1O;PoS4_vEt&X3xlIS=|kRobP7&*Vz^W|YCskuyWF5LtmaQx-TuVLSl z?7OP^Hi8&wxh(9MG}gk-E&kn0XksZY?okByOWMy`7#UZA-IoAdDM<-6TN z>jZXYfAyi~;Wc4P<(1HC_#KfQDQ{|<_)DwnS$T_C+&o3}IzmfDJrEUgn5+d!fVyXE zvo*>F9d7_aO^d*9qJ4FpxO=N3teDcmme<-{bME1WYgbV%y8)z3_O&qLpzG2SjQt+u z(9^oZiL6Es8CV}dEHu+MP7v|^^E}61RppF0Fwa8(wWuT`FVD>J8{rI>K(v(IMf$f~ zX9hS@3ZQmoDu;$)R96~LKsX4>#ZQq%2pO6>-=W z!mAZMIUT&Wn~|M7T?jIbcR!bKZJ+aX=1s7*@Mv-$ZThPl&QaYi4Ji5VABB~3+kPdC z<_JFcGi7G>6#e=D;epj^Lt5~V);~>x(Wl8{s5hnqhoW%sN5@(|x6}qBU&05!dl71t z@%wk9#7C))D%vT9(URz(Fg1i5WGp#{Cs21{cV*-j8FtR>jK7u;V|R&T&W@zDg;rx6 z=FAekXwZLn>YL}wX+GvG9F5l0{4+oB_FL;=Z!^BqbFrKRhr)hs~=({yp< zmFkIn1@rB|x9&y$#f==UB-TollB_!)MwpyFuS-c{LELtos%|7NZNjGbc-;dti{B~~ z;mf{qMpxR?*hr6aY7g`TJC#4?ek%QGKA+S)LDdnE+8THU@>ANL?)2JX8xjP~#^x+P zC6P%r{MIIehBy@$C=LN9AE~&7UHFfeqvx9|(1rp~Yc;8oZ93c+Nf=H-DpUhthNH{aCkzdC6vxK!=q$#O` zCh~@d+!~7hX(#&x7DvH|p}?(}vpD>MbrF3c-!6i5t>((R3w-{bTfYnP(p#l-I|(&6 z4lL9kJlfPiL&V9o-ogkfc1z8=Rtro9OJ)lB-8;G&GrxPx|4wWFdo^xbdZ(iUKf^;g zka{ml&p17m-kRUE{W_;#yex+GW(l=kROZf~EigaqufW7lI9u_0Me+_iu!_M+Oo|Q> z$t6iz_zX}zhfq^S$8f_b^tG8E1et$TMZ=qzI@H6l@A(z=!~Dr*Ckp&)N%+7jc1+1^ zhLa7}gX>8fwc?#;i{O{bP`$45xN6Q=A45)N&PUa6FuoUg?hEKAWrS}-LG8@_rc$J) zD^e)0iF`p{H2vP^<$0DZ#YFk;D7R$|ngh}6Hblx7T?sEGxO-A%EM?bu@uNiN<_-z) zxDWYYxin{t>&aSXVT_ws%vatsVHbVETbPT$cZfG1AF+t!4#=GYUXwVV`Sb43YCM-; zxW>zF>M6aXTT_h(PrUljwgv`-W*uhD%5FS8ji$JP`WXi^U{ufJ$S+KQhYO9bUb!Fu zPo`f5oMvVwm6K51bZ1P%_jV_dk&V6!=4u2orXDAD>GwO+$HLyGK4gKkz}V=*^ZRSL z-IkcP#eI{G9B|$kdC6vSc415`rP9v|fH`H?-q)?1+>i|7sCjc!aAk9R!U{R#8-|o< zNK_=RFbK$+N@55Hs8by%>AcH-S60D@l1J!u4JXZnp?cHc7E~t!?XxWicoQlH25435 zXGh=1zHe{t;e-N`v%j$I@~4f@ax-a}NT{ph7hz1s47EFhFMFYu?AM7dD!>~IyLqZj z@H=e&Y~w*Wx|?abw=tZ9N@zTF2*W#MV9j({=qv%t!cHCP%oNPAO3Fp7X$2*?CN`VX zC(BfdF1ZSnYs2Z^X+nmp-5)mBea@4TY_}yhF0C!G!Gh6br(;ntq$fSHWpX$pIGf2V zmA&z!;ZjJ$m(U!Y!|_(R^CfuZaduM<*3LRe%ORu+U++SR|)!?-UCWc5k4NGsG*ksuMK~Lalq!#k~+P1cA zcq-Pb8Xh5upF=*)-E#AsYDW|hAR4@ugQ`V{zqR!HyN-R_@w)^9`u1)@0paPv+@~%N zlh}@&ooifUtC8wTR#=IgJ!@bU(|<=s^mW^mvr2yHpsvQZ4tdj|Cjz)2Ot3WX(&dJK zTDLX0vx+~_GZ{u9qCyPfizyY9RTq)X)3J{f*$pE>0iWC(;yfGZE2#%%y2R~ zE;fNLUht>uN$l@nIp(K8s9%USOG6zMX*TlhW~QrIzLg?f;Vvx9T3epm!~S*xorB`( zzyS!FB!uJbtG%)RYj_W^7{~3DK8DyPd>oUkdF^weOUouIqb+oy!Q*egtALRt)f{iv z@2r6Aj{nf305^%s-JpDmrN3__xq~PmUpsb;DC=UUyJj;nbv)hnjfmza!?Gm;VYm1v z3G*ZFfGgqtYZ zhI%1e--aj1tyR$}_(cVSFH)kY$NOk6lP{>qTFsu%4UN5Htk6hZnP|XTuY9VWZ~$9O zv-loxPtxcE8j!&ItuA<|^v@*KQ1K(mBEQQx{N2vg@xShPFX2*iX%&FhsI0B7`NeVj z9Gs@VbFmuM5@zIkTy*yNR9)i!Wa!D}!O65emSL3q*X6B=VY==jGVmwtUP|eScYE;F zXM`S5q?}Keplzd;fP#TNc;(ik-;OH)m@#x}N7D7>i%r%MIv^I**Y)%N4}8v`A))$2 zd_ZVY!1w~VpRA$G$H*cy5Fg^Z(EuGEIAb3G~@cRos9-is*0{cO$gt>3OcO`B}Vh| z<#rq7(;HrKL#M^wLJh<-;-2|gm$MWX#*12`lb0NIqeu}5-vLefKq7rOa!!t*Ma1me zRe+BrWT0DzI%cbrNd`{U3^On9maN1HaDyhmv5NISFhRp9#lA<|>2$AjuXZgPB)h1x zGW~LLT8hy`AJ`=YF>|EWV>C<8KRAfUV=7cExa*Iu)YO4Xd@n>fRk3tMFcqK|nWo6d zmiwU|3gwOii@71&#iR_$Sid+~zFQTwT0EhDrAx4xcwvz0AfdK+k_}VNnk-CMJpwn< z?dAO}S*91Fiy?KTCfpyA0Z zZl5nQ#l`0gtya)9uC<2OzH~O4kKvPsL?_wOl)D^=-&@#A#kDpyL{OE)5TGQ621u3C z<{x0CU*?H_NHskh|%gL|kRLO5N=$&Y`ZgRatE)C4NuS@H%HCVH=DT z>6@u!qJVNpemy;7LnZ&*@goWBAt21HExYzly8}J=YO@FdV>Xcz18~71pspvfEJ)tl zjf(0uAG-=V-0#LT=By}Z;C1rt33 zJ>#Bd(*uBN#64XcLnOkJfABLah%qzIVEOzX2;>>kj*7qZ*2>!oRKnot>gTe~DWM4f Dy + + + Code - diff --git a/src/ImageProcessor/Imaging/Formats/PngFormat.cs b/src/ImageProcessor/Imaging/Formats/PngFormat.cs index 81ef4cbdb..528740a4b 100644 --- a/src/ImageProcessor/Imaging/Formats/PngFormat.cs +++ b/src/ImageProcessor/Imaging/Formats/PngFormat.cs @@ -104,6 +104,7 @@ namespace ImageProcessor.Imaging.Formats // The Wu Quantizer expects a 32bbp image. //if (Image.GetPixelFormatSize(image.PixelFormat) != 32) //{ + Bitmap clone = new Bitmap(image.Width, image.Height, PixelFormat.Format32bppPArgb); clone.SetResolution(image.HorizontalResolution, image.VerticalResolution); @@ -116,6 +117,8 @@ namespace ImageProcessor.Imaging.Formats image.Dispose(); image = new WuQuantizer().QuantizeImage(clone); + + // image = new OctreeQuantizer(255, 8).Quantize(image); //} //else //{ diff --git a/src/ImageProcessor/Imaging/Quantizers/Quantizer.cs b/src/ImageProcessor/Imaging/Quantizers/Quantizer.cs index 04252e1fb..55bddb0a2 100644 --- a/src/ImageProcessor/Imaging/Quantizers/Quantizer.cs +++ b/src/ImageProcessor/Imaging/Quantizers/Quantizer.cs @@ -59,9 +59,11 @@ namespace ImageProcessor.Imaging.Quantizers // First off take a 32bpp copy of the image Bitmap copy = new Bitmap(width, height, PixelFormat.Format32bppArgb); + copy.SetResolution(source.HorizontalResolution, source.VerticalResolution); // And construct an 8bpp version Bitmap output = new Bitmap(width, height, PixelFormat.Format8bppIndexed); + output.SetResolution(source.HorizontalResolution, source.VerticalResolution); // Now lock the bitmap into memory using (Graphics g = Graphics.FromImage(copy)) diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorData.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorData.cs index 13bf0230e..435c53669 100644 --- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorData.cs +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorData.cs @@ -12,81 +12,22 @@ namespace nQuant /// public class ColorData { - /// - /// The pixels. - /// - private readonly Pixel[] pixels; - - /// - /// The pixels count. - /// - private readonly int pixelsCount; - - /// - /// The pixel filling counter. - /// - private int pixelFillingCounter; - /// /// Initializes a new instance of the class. /// /// /// The data granularity. /// - /// - /// The bitmap width. - /// - /// - /// The bitmap height. - /// - public ColorData(int dataGranularity, int bitmapWidth, int bitmapHeight) + public ColorData(int dataGranularity) { dataGranularity++; this.Moments = new ColorMoment[dataGranularity, dataGranularity, dataGranularity, dataGranularity]; - this.pixelsCount = bitmapWidth * bitmapHeight; - this.pixels = new Pixel[this.pixelsCount]; } /// /// Gets the moments. /// public ColorMoment[, , ,] Moments { get; private set; } - - /// - /// Gets the pixels. - /// - public Pixel[] Pixels - { - get - { - return this.pixels; - } - } - - /// - /// Gets the pixels count. - /// - public int PixelsCount - { - get - { - return this.pixelsCount; - } - } - - /// - /// The add pixel. - /// - /// - /// The pixel. - /// - /// - /// The quantized pixel. - /// - public void AddPixel(Pixel pixel, Pixel quantizedPixel) - { - this.pixels[this.pixelFillingCounter++] = pixel; - } } } \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorMoment.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorMoment.cs index 7087a48a3..1e751a73a 100644 --- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorMoment.cs +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ColorMoment.cs @@ -43,30 +43,39 @@ namespace nQuant return c1; } - public static ColorMoment operator +(ColorMoment m, Pixel p) + public void Add(Pixel p) { - m.Alpha += p.Alpha; - m.Red += p.Red; - m.Green += p.Green; - m.Blue += p.Blue; - m.Weight++; - m.Moment += p.Distance(); - return m; + Alpha += p.Alpha; + Red += p.Red; + Green += p.Green; + Blue += p.Blue; + Weight++; + Moment += p.Amplitude(); } - public long Distance() + public void AddFast(ref ColorMoment c2) + { + Alpha += c2.Alpha; + Red += c2.Red; + Green += c2.Green; + Blue += c2.Blue; + Weight += c2.Weight; + Moment += c2.Moment; + } + + public long Amplitude() { return (Alpha * Alpha) + (Red * Red) + (Green * Green) + (Blue * Blue); } public long WeightedDistance() { - return Distance() / Weight; + return this.Amplitude() / Weight; } public float Variance() { - var result = Moment - ((float)Distance() / Weight); + var result = Moment - ((float)this.Amplitude() / this.Weight); return float.IsNaN(result) ? 0.0f : result; } } diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ImageBuffer.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ImageBuffer.cs new file mode 100644 index 000000000..70a48ab71 --- /dev/null +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/ImageBuffer.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace nQuant +{ + class ImageBuffer + { + public ImageBuffer(Bitmap image) + { + this.Image = image; + } + + public Bitmap Image { get; set; } + + protected const int Alpha = 3; + protected const int Red = 2; + protected const int Green = 1; + protected const int Blue = 0; + + public IEnumerable Pixels + { + get + { + var bitDepth = System.Drawing.Image.GetPixelFormatSize(Image.PixelFormat); + if (bitDepth != 32) + throw new QuantizationException(string.Format("The image you are attempting to quantize does not contain a 32 bit ARGB palette. This image has a bit depth of {0} with {1} colors.", bitDepth, Image.Palette.Entries.Length)); + + int width = this.Image.Width; + int height = this.Image.Height; + int[] buffer = new int[width]; + for (int rowIndex = 0; rowIndex < height; rowIndex++) + { + BitmapData data = this.Image.LockBits(Rectangle.FromLTRB(0, rowIndex, width, rowIndex + 1), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + try + { + Marshal.Copy(data.Scan0, buffer, 0, width); + foreach (int pixel in buffer) + { + yield return new Pixel(pixel); + } + } + finally + { + this.Image.UnlockBits(data); + } + } + } + } + + public void UpdatePixelIndexes(IEnumerable indexes) + { + int width = this.Image.Width; + int height = this.Image.Height; + byte[] buffer = new byte[width]; + IEnumerator indexesIterator = indexes.GetEnumerator(); + for (int rowIndex = 0; rowIndex < height; rowIndex++) + { + for (int columnIndex = 0; columnIndex < buffer.Length; columnIndex++) + { + indexesIterator.MoveNext(); + buffer[columnIndex] = indexesIterator.Current; + } + + BitmapData data = this.Image.LockBits(Rectangle.FromLTRB(0, rowIndex, width, rowIndex + 1), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed); + try + { + Marshal.Copy(buffer, 0, data.Scan0, width); + } + finally + { + this.Image.UnlockBits(data); + } + } + } + } +} \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteBuffer.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteBuffer.cs new file mode 100644 index 000000000..ac7ba623c --- /dev/null +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteBuffer.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Linq; +using System.Text; + +namespace nQuant +{ + class PaletteBuffer + { + public PaletteBuffer(int colorCount) + { + Alphas = new int[colorCount + 1]; + Reds = new int[colorCount + 1]; + Greens = new int[colorCount + 1]; + Blues = new int[colorCount + 1]; + Sums = new int[colorCount + 1]; + } + + public ColorPalette BuildPalette(ColorPalette palette) + { + var alphas = this.Alphas; + var reds = this.Reds; + var greens = this.Greens; + var blues = this.Blues; + var sums = this.Sums; + + for (var paletteIndex = 0; paletteIndex < Sums.Length; paletteIndex++) + { + if (sums[paletteIndex] > 0) + { + alphas[paletteIndex] /= sums[paletteIndex]; + reds[paletteIndex] /= sums[paletteIndex]; + greens[paletteIndex] /= sums[paletteIndex]; + blues[paletteIndex] /= sums[paletteIndex]; + } + + var color = Color.FromArgb(alphas[paletteIndex], reds[paletteIndex], greens[paletteIndex], blues[paletteIndex]); + palette.Entries[paletteIndex] = color; + } + + return palette; + } + + public int[] Alphas { get; set; } + public int[] Reds { get; set; } + public int[] Greens { get; set; } + public int[] Blues { get; set; } + public int[] Sums { get; set; } + } +} \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteLookup.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteLookup.cs new file mode 100644 index 000000000..8babb206c --- /dev/null +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/PaletteLookup.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace nQuant +{ + class PaletteLookup + { + private int mMask; + private Dictionary> mLookup = new Dictionary>(255); + private List Palette { get; set; } + + public PaletteLookup(List palette) + { + Palette = new List(palette.Count); + for (int paletteIndex = 0; paletteIndex < palette.Count; paletteIndex++) + { + Palette.Add(new LookupNode { Pixel = palette[paletteIndex], PaletteIndex = (byte)paletteIndex }); + } + BuildLookup(palette); + } + + public byte GetPaletteIndex(Pixel pixel) + { + + int pixelKey = pixel.Argb & mMask; + List bucket; + if (!mLookup.TryGetValue(pixelKey, out bucket)) + { + bucket = Palette; + } + + if (bucket.Count == 1) + { + return bucket[0].PaletteIndex; + } + + int bestDistance = int.MaxValue; + byte bestMatch = 0; + for (int lookupIndex = 0; lookupIndex < bucket.Count; lookupIndex++) + { + var lookup = bucket[lookupIndex]; + var lookupPixel = lookup.Pixel; + + var deltaAlpha = pixel.Alpha - lookupPixel.Alpha; + int distance = deltaAlpha * deltaAlpha; + + var deltaRed = pixel.Red - lookupPixel.Red; + distance += deltaRed * deltaRed; + + var deltaGreen = pixel.Green - lookupPixel.Green; + distance += deltaGreen * deltaGreen; + + var deltaBlue = pixel.Blue - lookupPixel.Blue; + distance += deltaBlue * deltaBlue; + + if (distance >= bestDistance) + continue; + + bestDistance = distance; + bestMatch = lookup.PaletteIndex; + } + return bestMatch; + } + + private void BuildLookup(List palette) + { + int mask = GetMask(palette); + foreach (LookupNode lookup in Palette) + { + int pixelKey = lookup.Pixel.Argb & mask; + + List bucket; + if (!mLookup.TryGetValue(pixelKey, out bucket)) + { + bucket = new List(); + mLookup[pixelKey] = bucket; + } + bucket.Add(lookup); + } + + mMask = mask; + } + + private static int GetMask(List palette) + { + IEnumerable alphas = from pixel in palette + select pixel.Alpha; + byte maxAlpha = alphas.Max(); + int uniqueAlphas = alphas.Distinct().Count(); + + IEnumerable reds = from pixel in palette + select pixel.Red; + byte maxRed = reds.Max(); + int uniqueReds = reds.Distinct().Count(); + + IEnumerable greens = from pixel in palette + select pixel.Green; + byte maxGreen = greens.Max(); + int uniqueGreens = greens.Distinct().Count(); + + IEnumerable blues = from pixel in palette + select pixel.Blue; + byte maxBlue = blues.Max(); + int uniqueBlues = blues.Distinct().Count(); + + double totalUniques = uniqueAlphas + uniqueReds + uniqueGreens + uniqueBlues; + + const double AvailableBits = 8f; + + byte alphaMask = ComputeBitMask(maxAlpha, Convert.ToInt32(Math.Round(uniqueAlphas / totalUniques * AvailableBits))); + byte redMask = ComputeBitMask(maxRed, Convert.ToInt32(Math.Round(uniqueReds / totalUniques * AvailableBits))); + byte greenMask = ComputeBitMask(maxGreen, Convert.ToInt32(Math.Round(uniqueGreens / totalUniques * AvailableBits))); + byte blueMask = ComputeBitMask(maxAlpha, Convert.ToInt32(Math.Round(uniqueBlues / totalUniques * AvailableBits))); + + Pixel maskedPixel = new Pixel(alphaMask, redMask, greenMask, blueMask); + return maskedPixel.Argb; + } + + private static byte ComputeBitMask(byte max, int bits) + { + byte mask = 0; + + if (bits != 0) + { + byte highestSetBitIndex = HighestSetBitIndex(max); + + + for (int i = 0; i < bits; i++) + { + mask <<= 1; + mask++; + } + + for (int i = 0; i <= highestSetBitIndex - bits; i++) + { + mask <<= 1; + } + } + return mask; + } + + private static byte HighestSetBitIndex(byte value) + { + byte index = 0; + for (int i = 0; i < 8; i++) + { + if (0 != (value & 1)) + { + index = (byte)i; + } + value >>= 1; + } + return index; + } + + private struct LookupNode + { + public Pixel Pixel; + public byte PaletteIndex; + } + } +} diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Pixel.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Pixel.cs index 0101b4af3..463059351 100644 --- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Pixel.cs +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/Pixel.cs @@ -26,7 +26,7 @@ namespace nQuant this.Argb = argb; } - public long Distance() + public long Amplitude() { return (Alpha * Alpha) + (Red * Red) + (Green * Green) + (Blue * Blue); } diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/QuantizedPalette.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/QuantizedPalette.cs deleted file mode 100644 index 13fd8a651..000000000 --- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/QuantizedPalette.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Collections.Generic; -using System.Drawing; - -namespace nQuant -{ - public class QuantizedPalette - { - public QuantizedPalette(int size) - { - Colors = new List(); - PixelIndex = new byte[size]; - } - public IList Colors { get; private set; } - - public byte[] PixelIndex { get; private set; } - } -} \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizer.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizer.cs index 2354984fd..34a5b1b99 100644 --- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizer.cs +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizer.cs @@ -4,88 +4,49 @@ using System.Drawing; namespace nQuant { + using System.Drawing.Imaging; + public class WuQuantizer : WuQuantizerBase, IWuQuantizer { - protected override QuantizedPalette GetQuantizedPalette(int colorCount, ColorData data, Box[] cubes, int alphaThreshold) - { - int pixelsCount = data.Pixels.Length; - Lookup[] lookups = BuildLookups(cubes, data); - - var alphas = new int[colorCount + 1]; - var reds = new int[colorCount + 1]; - var greens = new int[colorCount + 1]; - var blues = new int[colorCount + 1]; - var sums = new int[colorCount + 1]; - var palette = new QuantizedPalette(pixelsCount); - - IList pixels = data.Pixels; + private IEnumerable indexedPixels(ImageBuffer image, List lookups, int alphaThreshold, PaletteBuffer paletteBuffer) + { + var alphas = paletteBuffer.Alphas; + var reds = paletteBuffer.Reds; + var greens = paletteBuffer.Greens; + var blues = paletteBuffer.Blues; + var sums = paletteBuffer.Sums; - Dictionary cachedMaches = new Dictionary(); + PaletteLookup lookup = new PaletteLookup(lookups); - for (int pixelIndex = 0; pixelIndex < pixelsCount; pixelIndex++) + foreach (Pixel pixel in image.Pixels) { - Pixel pixel = pixels[pixelIndex]; + byte bestMatch = 255; - if (pixel.Alpha > alphaThreshold) + if (pixel.Alpha >= alphaThreshold) { - byte bestMatch; - int argb = pixel.Argb; - - if (!cachedMaches.TryGetValue(argb, out bestMatch)) - { - int bestDistance = int.MaxValue; - - for (int lookupIndex = 0; lookupIndex < lookups.Length; lookupIndex++) - { - Lookup lookup = lookups[lookupIndex]; - var deltaAlpha = pixel.Alpha - lookup.Alpha; - var deltaRed = pixel.Red - lookup.Red; - var deltaGreen = pixel.Green - lookup.Green; - var deltaBlue = pixel.Blue - lookup.Blue; - - int distance = deltaAlpha * deltaAlpha + deltaRed * deltaRed + deltaGreen * deltaGreen + deltaBlue * deltaBlue; - - if (distance >= bestDistance) - continue; - - bestDistance = distance; - bestMatch = (byte)lookupIndex; - } - - cachedMaches[argb] = bestMatch; - } + bestMatch = lookup.GetPaletteIndex(pixel); alphas[bestMatch] += pixel.Alpha; reds[bestMatch] += pixel.Red; greens[bestMatch] += pixel.Green; blues[bestMatch] += pixel.Blue; sums[bestMatch]++; - - palette.PixelIndex[pixelIndex] = bestMatch; - } - else - { - palette.PixelIndex[pixelIndex] = AlphaColor; } - } - for (var paletteIndex = 0; paletteIndex < colorCount; paletteIndex++) - { - if (sums[paletteIndex] > 0) - { - alphas[paletteIndex] /= sums[paletteIndex]; - reds[paletteIndex] /= sums[paletteIndex]; - greens[paletteIndex] /= sums[paletteIndex]; - blues[paletteIndex] /= sums[paletteIndex]; - } - - var color = Color.FromArgb(alphas[paletteIndex], reds[paletteIndex], greens[paletteIndex], blues[paletteIndex]); - palette.Colors.Add(color); + yield return bestMatch; } + } - palette.Colors.Add(Color.FromArgb(0, 0, 0, 0)); - return palette; + internal override Image GetQuantizedImage(ImageBuffer image, int colorCount, List lookups, int alphaThreshold) + { + var result = new Bitmap(image.Image.Width, image.Image.Height, PixelFormat.Format8bppIndexed); + result.SetResolution(image.Image.HorizontalResolution, image.Image.VerticalResolution); + var resultBuffer = new ImageBuffer(result); + PaletteBuffer paletteBuffer = new PaletteBuffer(colorCount); + resultBuffer.UpdatePixelIndexes(indexedPixels(image, lookups, alphaThreshold, paletteBuffer)); + result.Palette = paletteBuffer.BuildPalette(result.Palette); + return result; } } } diff --git a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizerBase.cs b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizerBase.cs index 3baff786b..b8aeb13df 100644 --- a/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizerBase.cs +++ b/src/ImageProcessor/Imaging/Quantizers/WuQuantizer/WuQuantizerBase.cs @@ -20,151 +20,52 @@ namespace nQuant public Image QuantizeImage(Bitmap image) { - return QuantizeImage(image, 10, 70); + return QuantizeImage(image, 0, 1); } public Image QuantizeImage(Bitmap image, int alphaThreshold, int alphaFader) { var colorCount = MaxColor; - var data = BuildHistogram(image, alphaThreshold, alphaFader); + ImageBuffer buffer = new ImageBuffer(image); + var data = BuildHistogram(buffer, alphaThreshold, alphaFader); + data = CalculateMoments(data); var cubes = SplitData(ref colorCount, data); - var palette = GetQuantizedPalette(colorCount, data, cubes, alphaThreshold); - return ProcessImagePixels(image, palette); + var lookups = BuildLookups(cubes, data); + return GetQuantizedImage(buffer, colorCount, lookups, alphaThreshold); } - private static Bitmap ProcessImagePixels(Image sourceImage, QuantizedPalette palette) + private static ColorData BuildHistogram(ImageBuffer sourceImage, int alphaThreshold, int alphaFader) { - var result = new Bitmap(sourceImage.Width, sourceImage.Height, PixelFormat.Format8bppIndexed); - var newPalette = result.Palette; - for (var index = 0; index < palette.Colors.Count; index++) - newPalette.Entries[index] = palette.Colors[index]; - result.Palette = newPalette; - - BitmapData targetData = null; - try + ColorData colorData = new ColorData(MaxSideIndex); + foreach (Pixel pixel in sourceImage.Pixels) { - var resultHeight = result.Height; - var resultWidth = result.Width; - targetData = result.LockBits(Rectangle.FromLTRB(0, 0, resultWidth, resultHeight), ImageLockMode.WriteOnly, result.PixelFormat); - const byte targetBitDepth = 8; - var targetByteLength = targetData.Stride < 0 ? -targetData.Stride : targetData.Stride; - var targetByteCount = Math.Max(1, targetBitDepth >> 3); - var targetBuffer = new byte[targetByteLength]; - var targetValue = new byte[targetByteCount]; - var pixelIndex = 0; - - - - for (var y = 0; y < resultHeight; y++) + if (pixel.Alpha >= alphaThreshold) { - var targetIndex = 0; - for (var x = 0; x < resultWidth; x++) + Pixel indexedPixel = pixel; + if (indexedPixel.Alpha < 255) { - var targetIndexOffset = targetIndex >> 3; - targetValue[0] = - (byte) - (palette.PixelIndex[pixelIndex] == AlphaColor - ? palette.Colors.Count - 1 - : palette.PixelIndex[pixelIndex]); - pixelIndex++; - - for (var valueIndex = 0; valueIndex < targetByteCount; valueIndex++) - { - targetBuffer[valueIndex + targetIndexOffset] = targetValue[valueIndex]; - } - - targetIndex += targetBitDepth; + int alpha = pixel.Alpha + (pixel.Alpha % alphaFader); + indexedPixel.Alpha = (byte)(alpha > 255 ? 255 : alpha); } - Marshal.Copy(targetBuffer, 0, targetData.Scan0 + (targetByteLength * y), targetByteLength); - } - } - finally - { - if (targetData != null) - { - result.UnlockBits(targetData); + indexedPixel.Alpha = (byte)((indexedPixel.Alpha >> 3) + 1); + indexedPixel.Red = (byte)((indexedPixel.Red >> 3) + 1); + indexedPixel.Green = (byte)((indexedPixel.Green >> 3) + 1); + indexedPixel.Blue = (byte)((indexedPixel.Blue >> 3) + 1); + colorData.Moments[indexedPixel.Alpha, indexedPixel.Red, indexedPixel.Green, indexedPixel.Blue].Add(pixel); } } - return result; - } - - private static ColorData BuildHistogram(Bitmap sourceImage, int alphaThreshold, int alphaFader) - { - int bitmapWidth = sourceImage.Width; - int bitmapHeight = sourceImage.Height; - - BitmapData data = sourceImage.LockBits( - Rectangle.FromLTRB(0, 0, bitmapWidth, bitmapHeight), - ImageLockMode.ReadOnly, - sourceImage.PixelFormat); - ColorData colorData = new ColorData(MaxSideIndex, bitmapWidth, bitmapHeight); - - try - { - var bitDepth = Image.GetPixelFormatSize(sourceImage.PixelFormat); - if (bitDepth != 32) - throw new QuantizationException(string.Format("Thie image you are attempting to quantize does not contain a 32 bit ARGB palette. This image has a bit depth of {0} with {1} colors.", bitDepth, sourceImage.Palette.Entries.Length)); - var byteLength = data.Stride < 0 ? -data.Stride : data.Stride; - var byteCount = Math.Max(1, bitDepth >> 3); - var buffer = new Byte[byteLength]; - - var value = new Byte[byteCount]; - - for (int y = 0; y < bitmapHeight; y++) - { - Marshal.Copy(data.Scan0 + (byteLength * y), buffer, 0, buffer.Length); - - var index = 0; - for (int x = 0; x < bitmapWidth; x++) - { - var indexOffset = index >> 3; - - for (var valueIndex = 0; valueIndex < byteCount; valueIndex++) - { - value[valueIndex] = buffer[valueIndex + indexOffset]; - } - - Pixel pixelValue = new Pixel(value[Alpha], value[Red], value[Green], value[Blue]); - - var indexAlpha = (byte)((value[Alpha] >> 3) + 1); - var indexRed = (byte)((value[Red] >> 3) + 1); - var indexGreen = (byte)((value[Green] >> 3) + 1); - var indexBlue = (byte)((value[Blue] >> 3) + 1); - - if (value[Alpha] > alphaThreshold) - { - if (value[Alpha] < 255) - { - var alpha = value[Alpha] + (value[Alpha] % alphaFader); - value[Alpha] = (byte)(alpha > 255 ? 255 : alpha); - indexAlpha = (byte)((value[Alpha] >> 3) + 1); - } - - colorData.Moments[indexAlpha, indexRed, indexGreen, indexBlue] += pixelValue; - } - - colorData.AddPixel( - pixelValue, - new Pixel(indexAlpha, indexRed, indexGreen, indexBlue)); - index += bitDepth; - } - } - } - finally - { - sourceImage.UnlockBits(data); - } return colorData; } private static ColorData CalculateMoments(ColorData data) { var xarea = new ColorMoment[SideSize, SideSize]; - var xPreviousArea = new ColorMoment[SideSize, SideSize]; var area = new ColorMoment[SideSize]; + var moments = data.Moments; + for (var alphaIndex = 1; alphaIndex <= MaxSideIndex; ++alphaIndex) { for (var redIndex = 1; redIndex <= MaxSideIndex; ++redIndex) @@ -175,17 +76,12 @@ namespace nQuant ColorMoment line = new ColorMoment(); for (var blueIndex = 1; blueIndex <= MaxSideIndex; ++blueIndex) { - line += data.Moments[alphaIndex, redIndex, greenIndex, blueIndex]; - area[blueIndex] += line; - - xarea[greenIndex, blueIndex] = xPreviousArea[greenIndex, blueIndex] + area[blueIndex]; - data.Moments[alphaIndex, redIndex, greenIndex, blueIndex] = data.Moments[alphaIndex - 1, redIndex, greenIndex, blueIndex] + xarea[greenIndex, blueIndex]; + line.AddFast(ref moments[alphaIndex, redIndex, greenIndex, blueIndex]); + area[blueIndex].AddFast(ref line); + xarea[greenIndex, blueIndex].AddFast(ref area[blueIndex]); + moments[alphaIndex, redIndex, greenIndex, blueIndex] = moments[alphaIndex - 1, redIndex, greenIndex, blueIndex] + xarea[greenIndex, blueIndex]; } } - - var temp = xarea; - xarea = xPreviousArea; - xPreviousArea = temp; } } return data; @@ -474,9 +370,9 @@ namespace nQuant return cubes.Take(colorCount).ToArray(); } - protected Lookup[] BuildLookups(Box[] cubes, ColorData data) + private List BuildLookups(Box[] cubes, ColorData data) { - List lookups = new List(cubes.Length); + List lookups = new List(cubes.Length); foreach (var cube in cubes) { @@ -487,20 +383,20 @@ namespace nQuant continue; } - var lookup = new Lookup + var lookup = new Pixel { - Alpha = (int)(volume.Alpha / volume.Weight), - Red = (int)(volume.Red / volume.Weight), - Green = (int)(volume.Green / volume.Weight), - Blue = (int)(volume.Blue / volume.Weight) + Alpha = (byte)(volume.Alpha / volume.Weight), + Red = (byte)(volume.Red / volume.Weight), + Green = (byte)(volume.Green / volume.Weight), + Blue = (byte)(volume.Blue / volume.Weight) }; lookups.Add(lookup); } - return lookups.ToArray(); + return lookups; } - protected abstract QuantizedPalette GetQuantizedPalette(int colorCount, ColorData data, Box[] cubes, int alphaThreshold); + internal abstract Image GetQuantizedImage(ImageBuffer image, int colorCount, List lookups, int alphaThreshold); } } \ No newline at end of file