From 82a76ebf057e8035b14ed74bb42489309ef5e57c Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Fri, 19 Sep 2014 19:32:53 +0800 Subject: [PATCH 01/21] adds transparent test image. updates readme. --- README.md | 50 ++++++++++++++++++++++++++++++++++++++--- tests/images/trans.png | Bin 0 -> 242357 bytes 2 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 tests/images/trans.png diff --git a/README.md b/README.md index 08c7abf8..bfb07f28 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ 0. [Installation](#installation) 0. [Usage](#usage) 0. [Supported formats](#supported-formats) + 0. [Note on transparent images](#note-on-transparent-images) 0. [API](#api) 0. [Open an image from file or buffer](#open-an-image) 0. [Image operations](#image-operations) @@ -24,9 +25,12 @@ 0. [Adjust saturation](#saturate) 0. Adjust lightness: [lighten](#lighten) / [darken](#darken) 0. [Adjust hue](#hue) + 0. [Set alpha channel](#set-alpha-channel) + 0. [Opacify](#opacify) 0. [Getters](#getters) 0. [Width](#width) 0. [Height](#height) + 0. [Transparency](#transparency) 0. [Get as a Buffer](#get-as-a-buffer) 0. [JPEG](#jpeg) 0. [PNG](#png) @@ -120,18 +124,24 @@ lwip.open('image.jpg', function(err, image){ **Decoding (reading):** - JPEG, 1 & 3 channels (grayscale & RGB). -- PNG, 1 & 3 channels (grayscale & RGB). Alpha channel (transperancy) is not - currently supported. +- PNG, 1 & 3 channels (grayscale & RGB) + alpha (transparency) channel. **Encoding (writing):** - JPEG, 3 channels (RGB). -- PNG (lossless), 3 channels (RGB). +- PNG (lossless), 3 channels (RGB) or 4 channels (RGBA). Other formats may also be supported in the future, but are probably less urgent. Check the issues to see [which formats are planned to be supported](https://github.com/EyalAr/lwip/issues?labels=format+request&page=1&state=open). Open an issue if you need support for a format which is not already listed. +### Note on transparent images + +0. Transparency is supported through an alpha channel. +0. Note that not all formats support transparency. If an image with an alpha + channel is encoded with a format which does not support transparency, the + alpha channel will be set to 100% for all pixels. + ## API All operations are done on an `image` object. An `image` object is obtained with @@ -359,6 +369,29 @@ Adjust image hue. **Note:** The hue is shifted in a circular manner in the range [0,360] for each pixel individually. +#### Set alpha channel + +Globally set the alpha channel for all pixels. + +`image.setAlpha(alpha, callback)` + +0. `alpha {Integer}`: In the range [0,100] where 100 is completely opaque and + 0 is completely transparent. +0. `callback {Function(err, image)}` + +**Note**: Previous pixel's alpha channel values will be overwritten. + +#### Opacify + +Make image completely opaque. Effectively sets the alpha channel for each pixel +to 100%. + +`image.opacify(callback)` + +0. `callback {Function(err, image)}` + +**Note**: Equivalent to `image.setAlpha(100, ...)`. + ### Getters #### Width @@ -369,6 +402,11 @@ pixel individually. `image.height()` returns the image's height in pixels. +#### Transparency + +`image.transparency()` returns `true` / `false` depending whether the image has +transparent components. + #### Get as a Buffer Get encoded binary image data as a NodeJS @@ -395,6 +433,8 @@ The `params` object should have the following fields: - `quality {Integer}`: Defaults to `100`. +Note that when encoding to JPEG the alpha channel is discarded. + ##### PNG The `params` object should have the following fields: @@ -404,6 +444,10 @@ The `params` object should have the following fields: - `"fast"` - Basic compression. Fast. - `"high"` - High compression. Slowest. - `interlaced {Boolean}`: Defaults to `false`. +- `transparency {true/false/'auto'}`: Preserve transparency? Defaults to + `'auto'`. Determines if the encoded image will have 3 or 4 channels. If + `'auto'`, the image will be encoded with 4 channels if it has transparent + components, and 3 channels otherwise. #### Write to file diff --git a/tests/images/trans.png b/tests/images/trans.png new file mode 100644 index 0000000000000000000000000000000000000000..f55636b83da24cfa1cfcd65e56e5756e2e2bd75e GIT binary patch literal 242357 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 v3YW49_rm)>hJAo+fj1;+tic`v$qn( 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�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

SPz(+3`8Eh<=K6XRmJt zERy6mY|{*T1NY$GA(IsD=ITn*Bo)g#DBbqW_<#GmHV(OlK`#bynb7yDO@9%E8hxhNPPnQ-O+Q6+!R4dVj|6gF@W029+q2zl!#HEwe z1L9zCpKL1H9Q-b)gTutb1)ru2A(vIO8s!%eEom!DZZ?J>?Zf*YzT-LQSC{9udD3b* zeEaQN`!nMnKD-Z~aCm!k<#zWg%hz4mvmW?+2YYfRD7^dURe-vrTP6VsMu`60tyRinh4+=P`(r+VWcCuUR9MNd;NG4L8gghTI45; z@!@sAp7saOs7kCuM%aF?-m7GWGdT->N`Wd3C~>G~BW-C`=M#=sX26rk{7PWO*?t?M zE#>ot3jF=HPs?FCY3nR2c(ebOtsDult`~N(EQsWVWtm$-vJWiF zFIrG|c!-|Bb`;VvqX!^xF`i2fId2*K!fe>YGRRKr%5__b1lNKZBjJC>n=9;~ct8Nu- zE%I!0dqyMJbIq>U9<;sMY|pLcksjZO^Be#<4SKzBRmk{f73PJG^(#5`v;T zJ$mc<;!db&{ky$xOl&hL4?XvgilOe&$0;56uW$B5J(dS@6?6nmAFR0V)syj^_KkX*TysR_cjb)fqa_Mt z;_xP8rYDqSJ{m7ex$0BS^n?{CW}ZRoL@(W^qAm1|Dbw@bsjfQ!Eas*cLa_FAk5mq_ zW*E(hgy)u>a~(u^%jLw|zmwkn$ewX4K(>(1EGK%vVj)UrQ(04<1#3*tkj$9TMbqh*t@;Gy>@ZFJn5U;=ZQz% z)&s*v8OxlDPkYuAwFIVGr|1-1l37MBWimRREvRYw`(OiOH6o)RhH*ztG1zsdJ@+^X zG1s1CjVVF0$2Xf0k=d3#un#5K+wUk?;n@;_M@GY~%7NTJ&y=+wgHf+AIRnWQh^y9i zqF7F%wv=SbWY9j*Id)0L6=j3tT53=5TB^tEsbpfRh|GYQQ0^+ z+*hTF_oYpSDjoO8_xO3Onp1Yd`jPP`_z-1;aSBHEAz1L~5;|!a^+dhVPa)&reA=mm z5$wsu8P7V{v(~v~eD&({m23szMf(|qCmxw**6HDPYHJ^r%y`bAtbwbc!LIps+vBQk zSTqklC})P2e(AlZELw11D?et&tTp=r&JMH;C|7WA z(#>A3lnG-8<21@7VGgTHu6eHh`9yX@c|ajPxAZ@lI%(nX{_K;rtwrPp@92HF7f}~O zO`BW0qt>~wOpJhCUw;ul1n*1Y#Au<5%z~mojkDxZsuU|9&uc+X+MXkm383rwY?Rb6 zVKDkJ_4N7k7a_(w?OsTk78w=hJW%vuFIh69_wVvR3?V}KBivxy9G zc5i?8dvTTLBRd$;5@q3{ZCuL1^qJ@%mrD6S_JyhsWmQn7y>8pR{^qH;aM=Xc*O%g= zEtT^Yor%oBKY#vA5j)O-=$%XkXNLM3n;Rx1${J*;c`>6b#g*zHH5h1k=N?E?_kMC`DdVMxq< zs|N{+dpMkU`o5_5)vA>H02l2C#_^JJH03ZJhOOZ$L}^rWq?QY2|at0m?r6w7c+h^-DM(eIBnmkK(V>xbMYh(CU&o;y<%VH=Hm>bz1QCTsR9>PM|t zQsoH|TT)SL?C$jYCPoYp#j~gS)*?hQf z!xb-M2T@N=0Y_dVOsM-E9306YM@?_UWrX$cl-ju;^85LcIcqxu02)g*l|U_Su;&Mx zm1Xkfy7cYs%N%$!fSk532=Rk>vS$mNnXoRZyY$=d2ZaBE5m<&@ggkeEAOFV*g&;n&TrKUjrzW2Rt@#DQ5ef* zEB1L4@dYr}g~TqP-sxm6QKQ5C6Np*>zS!$4nt|rt%eDrbfmzZD81%+Shr0dU!*G83 z%nX0koc-7j2Qy(ea1!(6%ut$MC{S6cC4e1Xt+6L|AN#u!(Ai46d#GTC>Lz>%pzcSO zA;NTddovOMmVM7UqJPeFyuF?2Jm`JPHT{e#ht$+;uhwc6KOPN48w*+CXw+AgZ@HZM zdaWXcNQuPQX4Dr>L8*c_fY&+y{KEC2M_6EXh%%OC$_jx{3< z2$=e~*J|DDSUGF<6k?SEa=hHH1(7qe9Xa>r^&73_`}db}`XpcgBx73?EX)WDjoASz zH5QP~(St49bKCg&41$~;v&{>g+pI5!PG{t=zjV#`mu%za{mtFbg08K`WeGBQuhBTz zSuMKblVhrMqRS=_r1}TOVYymUVXC^L=*xKiU9ja9;k(6z-RmF|<5`}uT6o#Xr-#ywziZ}EoJvW|@-9x`^*}HcK zr~7WUn!0+a>c0N&H}2s0%q;-6{_gj`{XSI@!iWHFBVq>)jKM}C^Zonx0UA4gZW(om ztUg)w_WGkvqAy+%S{Vw-sF2n3=VuoJ^oWVA*H;Oe=m=*)m<`M<4C^g$kly*R>vwrkSiO$H7 zh?vvQCHg0W;63=vv9!SAagWS(at(a( zJ~$TUigDrqU1BR?{m85TuUTo(LO_ARKdhtrk% zor5)Tj07zpJJ`wm)zyud@5o*$)2DR9?Bi71nr9q&r{h!weS5O|TrM&%qIWH>{17<+ z_*rD`$~?AhL^KpR7D5_WamHd#bE-LK+6?a%(oC6f(QtSk1c|I>@>s!x#V!u9rz83C;J@My1U!z*)PfvFPSQF0g zfA|l7M4vv<9~bgn870?3M!C=)d9a^XJiG^+%`CxCRK$^ge{W8MKau9hlP9OzYoOUv zP7L-N4x06XGlO;b`0>4}R)7E9@APv^GQ;idP!h}BD|hd=y5 z6%LXZ*0ta3YC7c7)`HKvcyb|s)E^CmH=u0;eyl$lEAgV@!`Vcwjq{XQ1kLp?;&b%b zH?LoQ|4;wTKPlR}zP<_z%X%qC?6q1F7P-5-(#Va6{Ta&65sFxSh%(%cE$Hq2WyqN^ zwx9WdWlLlR&!0YX&7G#}*v0^e`25*Z->C1P(iG6YMc{-?0sY`-&o0zI`01y&0yMpN z@m#&(`C=ZTqSaa%Di8Di{w|JSS`#+QnkTMv*S| z{yspqa^xS+hCqAw>gp2=bYkwwCRC@Ce)&7!1H5;BcItV!AmOPTaL;x>?&ADRWhxPv zxQqxX=$WLt<&1k!9v6k-bWS!y|1!YH##joatK;Jn=-TvqOQDB1D)rI(oWdSB* zM3x%^tb^GLi9$t%19QL~duC6cKXZfOB&;_;urg*$*J2GbC+4T~rANnnWDcFNp)X&q zJcqn&hXTWH$*Ftw`i&i=kt9d6X7t;gf$Le|Sa0sSr9HzrjDD6y-dSY6yLZGauo-Kw z{`~t7SD{)XILn&jz4G;)-H^9T`3tkT2z}Ul&_jeHfJ}M?*||M)_6^Q}J~zSb-Q7JI zxIPEq&BzWm(xDhO_Rs$QvC!$zU0bHc1b`6e>`4fspe+UM&!`buc5|mC6f;J3fB(=7 z#}wL8b67q9{=-LG?~>MW1bGtE2WEWuUZdH<#~?IK>J5L^pXmYNIBhP(x>L-jrf zDv<}v6j{G~!OjGXgIUIHvR+c2(}hPd(EFry0dw1`%X6V67v~8UOJpN58WCiIKEatJ-4WQC3yuZ8Z zigl0K{>8)sn&CX;eBSLgP!p_$&-Ci^rTd3J{GDU6W4RtnK9t$(v$GS+QupQP z)D{v(C!mr~x$SB1pFTadXTMMa2P0vnng{zJ%mbX` zagKbcpaH?ABXj&{A`mzrVsdnJtN@O^Ptvx#EAUwWXF|{bh>uEwBx7xV?j4+)57@e^ z%YkTY>$N?5W+N#$`V1qB6ETIh+dYJog@;%zgV6p1`x*O&mP97IH9u0q{;GYDwKGg& z@XIfsC4~(!7>=INRT0sYo8k|-y}kv@WqZFY_EM44+}>-_3VW}B*6hydSzUy{KmGVq zoc1w4^X2Nwot>Ym(u6G=TY~lT$9Jw`dwcI-Px|na6>S3xsOsgw?@(S|Fv4ev+N~vE zC0DehbcHPpH0QJ5efJym&;tgPelILPlzIzE?{hpJ&{GY7)1+E9OI3s_ib9GN!TbTU zsdRuLiD=!GHbNwhUkkVsN1U>WR(UKbqMxogcRR`SE`$*6_4MAA)Lvr|YFNy~yRAwH zktTbA@en?TwTf(C&BqCVga_Iu06)q}ny01=M%BUs5M`7L30!0oR<)_tlTt7jO&L3K z9n3*$>N=l3eUj)Ik97TUHs21H8Sy{;=}&sTFP^_tAqS4}6juOo2I~cgEa~rQZZ>Lj zS6#A?l<{kS+LNM^!2G{Z)bjysh6)nNhRx7~$Z1{BwGW0vfwG`7g%X$GA*yf1^~h!8 zxEKyuAi=1XCtxYhh(L_$%{BmP+o)Ax84c$tRx*|wRYPDw;~A5wQ7MZ2(UCiW>@jTz zkWHl%2vVh*M21A>{Uw?B+dFYFP$&AbOM9*S#5%{ax!5u0)i|tmc`$b zK~Ghl*gsJ^R{hx!c_S9;RYzc_v*6}V zg+#zm>SZpY{&m9b@1W;QdZ0REX}=Oj8#Bj>PiNVMW@0ea$GLJ5Rhhku6B9*M^w_Tv ztT|JEl%9B}(}TWN0T1PU-eVD~A)dC3n8qx4WuH^EPq5T62NBai%0(zb)`K+^1=Gk zgSD^;R1q| zt)*+Fsis0H@p}{5-P6-!VPF!}zGdMZjo1NVY|=UE z9MpQyjYe>fN|_kAjzw>jLijml*G97$=!^EFoS;vfX-LuRT%$l%(+J}E`0=ApjU$Fy zqa1ca7z54wXL65o=X`N+c`dJz@C_Lb~5fR%O~&Gt!-M&1^`F1WqB&TJGw8IMkZs4ndn6aR80#Xf!UC zqHt94H4)LyvrdBq%EYmwlF@pD)aCwYH!zuilcD4d2$5$3)#z%ok`n;9V23bN>;N#g ze06tYp^_c&N<|W(f z#b8nYBLbd7oHOI0Aj@7upvU*=u`XZ_TP!^#KPo=#%_uS?!zuwFI39-RB$SIDuJ;WW z%J^QbDvDnaw@{Yazx`m?5ABY|q&%ZiTl3Cj$&CB3u9YhD{PsJ;S?tZ*^GBcAvi(3= zpD6)^QiwC9B_JB(U;!mStD?$=^LZrUAOL_um#nT_DGGlAI^Vt*z{SO>j3|*?FPam_ zac6z7Ow~!p%p4jFisgLM_POnoP*e{Yb5yIa{%}PU%@JpqZiy1P{gHUUEAznNvy84bcJ4MNT>zTdIX(TUL&;W&J&jP?3zrT+Z?7*%OP-iLW&yNFK=~S`i z<`|Cv5HiANK?;3*+i129S>!{*g=N`iC&z(r(;v6v<-rayCJ0L-zY{E2Za-WHs;zVq z?N4)(OSMypzlMXMzxr||^u0Mc55GQ6^ZvHyZ*SV{YkzRk@W1`;J4+BQJk^r%nDMWY z2)lm#$?mnDb-nId3EOs?33QUG)rOtXCNfw)6QXM8-EGh8f`jeNUtF9E3qpJK?Cgm! zAiM^w68aAS=)fqkhv7=FfK7ZKobxCRy%D|gle1IH+~508pZekOAn*2f8}9n%()ULT zH(!lCJpE5Cp+pi^oa{yTB5JB2S|As&ACN)O>Z2-Td)dyx)PCj^d_Fu(eI^SMz9^*p zbiPpnX=fo&<)X@m2n(abIAv@Y&#P+Zrd+Mbp13^hA;GR^?s~&fn9~(7=YMl=_p+S$ zyegCg4+Zf!fD1q;gLI!!%=XWgfNY>3nTPx9E1%ujDSb}_hW9taEtMRisOy_A%2=RH zC3;%Ld0_|w)SDxEA#+F#UK{ah&DwEQwrq%(w=>k;LIko z$YR6N_joN4E{<1oy)i2f5$(Kr)FV%O%=8q~dC>Iw=%cH*YrmhcIk^NKs_)?dTdiGX zEo5=L50Rked6P_JvsLsi^l(yDaFG!z>Lruz@-tq`dSfmlx_hp^dKwXzm~r4@!WgNmw3>R&~KKYH7}zA~0VqS8c=!5T0v!F5n#=RfuY z02KA)aULAcAZ4TJVVf(nkTnFB)KM!@)aHnU{BO6Ub&q{Rh-EL*^H@a zwz#3}$Wv0jy}i?PMr_$2-m?@R0^Iam|8xuxY2k5T%IFI@Ra0Ctr#p}MhA#YSCW4}_~W_OW6(yr0{O-4{xCTM;P(kQNrT`LtL_Gly)iXcj5 z8O+IB<}PeYtd97!5|bzl!eAx+wZrn8Zr@pcn6B0My}7E~dF zoXf;fO4+GmF#(-QK`rdmBkLCSf_t4W7vbHz50u;^3feNTW^>oKn$1*DMA(M}&8(4F z_9p@N)zxMA^!bxoUDNqkBqSsi>@_M3u<4-*P_I{goUwn51v z@7Pa%j(tt03}lNs{ef&%%1}x0&so6~upV3&l|~XFp{irWMHM-opJlKVo+c=11JJdpDVfQ*1HCMW{-hHht}zmp6kJna#` z#3Y5C$!E`=LY+;!PJ5C)7L^}m$7RFmr%N@Dp#JJ!EO(lq*@Qp<1rZUgB?MnIMr#+b z+iZmgq_-Dae{fid9PYDe0Qo+huO$qa;5YiOg1@~T**;u}Nsi=;gazNpzPrBt0$M+*+?Bm8n-Zu- zGgfrZE#M$=s$5TJBVD|9zK+nBD>AAbkjk9pV_GKX;nzs%XIA<+j^h?`2Wp{MC8wwRWLo++u ziobXQJuzMj|(`TUjU+LlYTfLMJuj+IoNtCgXC%P(%xn*b z!Dt3H!F*lb_hI*3uYArjcKg#!RufqJ+=}W7f>w;TRx57Dg2*)k_CD*buWv)O!YFER z&3ehddhtfj>*3B4*51UG$_=-2U1scEw$Uhjuby0-ulAAed>(2Es&Usc1fe_aW;BaD zikWDV(b`SSr7A`fBb`H!8B=(i2bMuj4AOELdm@qY6i41nxus>mDLck&TEFkplDIU& z#a!eRY2gf{%4JC_4u$K9nPMbHq5LA<^L6C6X0u?82o%UvC=if29UmV)_NI6bGD#v_ z`dUP=WYc7Jd>#=o_eDfS^mUB@fSn*BD%J=GoRrkn`@u-3BWNrL*;CAFdpf8XX39z_ z(^ll$&S?Drsfm}qxuf+@#=O#&gHKVag{(^cK8f0W%EL_nBBucHMC^I~nhj3Qg3tVH z83hb{tT~2)elE|KHN^R{#@r9tXzYtddCZg%4MjNKA0yBE;ke+620X|6kzuCnMnvzL zw_T?C?$BB;g| zk>DdgdGNY6@j@39_9qKQn$6hzV?F7E?zI|Bc4)p!Sc$NKrZ~U>4T`!GRg>&M*JAHc z=+vUtm3@D9w3{&GH7&DR`p9Be(uf$ZW9`@^=ybb;T`v5Z z7a*mEP5Re99bwZf`f?qoQnt##wCC72G2!*<*(F*93WT>&n}GL=8s8Rxv2Vz_lU}AO zFTDTj*RKOe>vZxX{pF5j}q%x!Io;H>CS2E5BnHAT!zZ=Pm z2%b~TgP3KOlAxt16fl)5u9NFlX1`tw1)SSP$7jb|y>Zr;@(3kgMhAPHf!-gWXYzi! ze@ZYI%R(kHK$e)TZZ>r9fRLCgPFry}8R-oyH(Ez?3_gyaIuD9kjEw{ME4CtZIypV^ zEerlYBzJlbA{aupm%s(KH+bneFU(WtvsL|mS`|aJj!MDF$*F7tlMH+g?U5MJEkdl; zgg6;duO!$IpK44bw%6-i5P(Dm%UbM}$NeSyg?(%8G=)!!^YTTJ{6vnC8Wjsw_((dW z;joOoWv~U2(a^%&tfXY^@6LC-bC3QyjyA%UG}R(iP5``2A`mFDmAM{l#P7a|DDl7k z^{>iksaSH>QCZ{OQ@cy@1Ga`>6oRIH9~_eTObcQNR&Vd8zHAvcTJ5l59vwErp{?&? zK4F_ln)U8(X!||5{lk_y&@CMb`4#!H3=EYKO;1uXn}OHJiaUXxI6+auz9|id`#UjR z1^|l6{Kbnh66i5jV(Y4E6x15BEMB9*TmR+DPb#Ts-vT_u|&v50eV z7Wo-1GQhIG!WIPxrJg@O_I@7OB+s8Tg}KR=i>vR;lDb@m2#71`i{5H7Q>ilaoS8oL zu{VmQVz!l6CTJPE&nt6Z(E}uF^##igmUGP)iz%m&x0FjIHh9o05*lE>ahi-!u*vuw z#O@x&J}jd*6PA%d{mXLgjnCx5C}QEjR3>Uq z1?sS!D4j)Nk?o_=g9DtI#d6lN zH2Uxb4&zF)p|mT=%!Q=444BFRS%_t%T({PesF%@_u0`<>_}~cs?(hCiCDm9OEpQM$ z6sNJ2K9hF*R$0|ZA~{jww4RJDMWEZv4p2(VnvVK$&yn;e3es>O_>-?MmzQ#AwOTb2 zyUfOFfe`4a60=^(kk!rDGJig{^*J(!juV3i-eneMC@Y?40bl~x%yVj(gG5k+=gRYK zo1vzBZ{O!N(3-oKuV1;99h~K2qmnGupRLz8nGe|@1G-p<&R2VqHI5@}8(~ubk1vGvq2C=_Dk=SE zY~Lg$SOQYbw?=WBjb^*4%+NDO_Z@@Cv+no$`Z<6wM1^>7deNj0{GGE)1>-M2{}mhz zYv}GC?(LcewhV!}V#<>cSy+|=Z?woc95d#qU_qP$73y-9QM+O55=BEStpp8`7-0H_rSR#;-UBS&?PU-U=PA@Fp-!BeCONY zRKP9^&*8~a$(|{d)nY52^~lvw_8Y$pB(Nw8hQ82F9D5;v(qOb z2fui4B23a^wzfc}$7oxagfSApejV1D_?XVeZn~iNns?2;rhkB;&^~+bprtCqVAOXR z_|PpEyzlhf{?1`Qz73ETf+bY0V2-0QM(O$O+jlz0*jmQUs15=S#W_q*i%s2>??h zJQu2O=2HVzZ`uw0?kIeC|H+SS-ya4JexovhiGaLdLCZ%Hjjbn^XJTiE3%K7K-A4n*r0r5 zM$)W0;6GJQ$=JAFQH!oaGXEb->Ns$&4Nm%m0m~=)+x}NkxCB@PQY$KQ5B;Io^FT{0 z+nP)#iY#$l+&lM55HTE$G{?(l(R2Cm;hm}{Jaevx>^jlAeq`U{e%P4&jORRyGurTx z>(EC;c}0~BG@oC*c;OQyzk&tW^H>iuYkvRZ$Im`tf%6Sp2WC4Njl*$K22_TTjf!d& zbM)MQe=tzy+uGd?%!PwC=JVI!5?}!9!5iK0G>*X0)b6aZh}Z8&X1$H(U&$h)YFy@VS|L>q zz8KNg0E`y1m@VfNuxMF07J}b6ay0QVC|=7hgjuXw0aPMD zA+T=jw$x8$@6uLd`i9D1uieqTUs#ZTdUB@cQEEi=_}aWvBnXLo5}P4yFXQV) zn=Iu2@G)ti)kXiF_u}&|FE2HzM70e&cYJ){+Sk3vdEg_76jcTsICu|UPoR!nfXoX{ z_4r>T|DwXt9S#GPDE1AmTGbXym}v7Pod0@mZ{NbvKNGu}Z0a~xGY^`(W;Z(O$=mit z`OM$mP8^i}x!lw<#+I@d#!03i^+pTQ#PNa978hC8d05-;_7&7|P#*3;_17GvVMPVp z3+MAOiM+gJGGimBPV$to;4ZbTjCT$Pa7#FGc93AUI{@2ae;Z7}pdYzIma7?LRRO{^ z_PGVA%B+Xx2d}P177VP+E{h&nz+mK~kr}6&#Qr`!-ILvn+7T_=S10bPufCGM`|#nD zd;8O!I5e)WX9_+lkhYB%W)nFF>+tE*omz<-_aHlzefs3!d*)h+*5kNSL@6Vh!MrRB zmA43ZGN0NnZGEno4@49*`;dCmoKnR*P~>-Zad-D^4}(^T{_O$1r7}a-VPaVq5Xx#dbhsY?%Vgqng{HH;5LUrg&=G<9@eA712Z_ailqbuFqDHb z7MP4RH*7B74h2RF07uCTN~{9p3uO<(RevxRYY=@`vikdW*Bk*N02Lt@A39wRzx0}H z$o?;5*%18@dyRkj`YT(TBWZw-r}HqH-}~O^Aq>V{mn}FEA++|I?&##m|MKA%Tl-?N zmetVd4fq}VUfD8Ca|mO5Ms^G3ow8rMRp<=bzFac{Vp##%$MNZ5c<9`Tr3vs@zL-T# zj7-uIL{ee>e03j=%{cAu+s0~l!qv?epR>(Xu!s_6uurzeME)p6(IaXDBFvRS*_*iqSZ4XhjT3ZeheCV1nyS%!hU#Wd#0kf;rEr4S6zEWwZCwqQ+;VY%8FPDmeG0a}?)(rJBoS!$$_+7b!liyg@vGDUH zBhD58+5xq!KCpKW&0wK9FdTIQx&nK94Ku3f3|OF01$^)u zJ76V_4v#EiU%L5ptu_Oo8cL=(a0M^+{5(D0aZ84kU$7m%|Vg<+)2mbKr!oh>TTtgIf z8W#5csI8TXHBZJkn=&el_*2!gQlJXxBpSy|S)4CWTx#jA5? zZYuQ0BX9YjtwFHQ&bg-D^PA^p6ADq!qEskr7TcK({P<-YE^qp_XHLLj*xF27yjz-~IOAxP$!@d)7nu>EpdQ@UCU*PdpXNULPuDJEpdmSKFmI z66Z@*#ulMGh&a}@Maf862JbcKca%L4Ig3)hkI zYZKjy%iL!Yj`4^kK(bF|sC@YU>98k=e4jFbrRrH~o5c0hI2)g*%x*GQ)D9>ou4$a7 z=Dw4xKBRaD=Q2tAAeW;46*0$h{GA*L4ki99GE)7!|L(s{_QN81Uh2m_stvX8Z$YA4 zfOX{HenupO$_gW8>Fhl{^x0zS7b<-Qw{ON8btvxP-G_TUZ~F8pUx0vRA+G&MtZ+p! z6h>WnRb!O(v!~yD{W?T0`=~@lNip&RsLU6lK+ZM+gF(eQ_mQ7oy3@Y7vztyz=)M_K zoW}`+$1}qIrE-#12iTht#}O28ve$T~TpO?D`gu>z-#6cU<5RpsiqvKw#0>QY^4w&< z0l>)sF7zoA8H!<7SJ#&9JOsG2H8N<9wp4~{v#!tkbp2WHO}4{&O5@z_U-%Ep<{|Zf zM?aM|Ud$KD961Nv&(a)EI=vDrN7;~wD(Uri_qVD9P}Yy?V)ls3+&A_U+Q4sA)8#lpbFm;@4yb{xws7X@sKQn%nFmX;75siaNjP1X%%a|DA%`)8jYE25Jz(*hdVqwmWe zP}W+Ed;}rE%#cxY*16uuuo%}IdzFlxCU}TAl`t8NwAWI6#^+C;LOOOz$(fN-mAzqU zyrr^N@RZc~)T^so3j|s&;xC-C%u1!GNtfAtr~sNVW&Jz@8{R^^CKe>3v#!=wrJ57* z7c#l^M$^&qWJ+W*-1xoStccEbVEF@x2)~7$jja-;I1)M-^6$0g@0pQ&deonfwpRWv&|*+EVXz2{DlP?1cH-JkE4ov0Dhvds zK0MshbdUPs?Y^Crtvfw_=F$q6Y;u^XU25;gq|}OEaHQ0RB@tb zNXZsn=IML}K*@tVrGLn4w$hf`2v1k70B)&yfCix8;jX4nH(yMBRBW;=YPOU>2V+MT zQTp+Cico@>sb-8P*lS?&VFZDaCVgvq*6`=Xp0#FJ#of%`EjtdNYVEmtkcgmGdjs2e z7B!D-L6{FbIXh!cP^vk$2}02h7D_wx$-4c4En-K2o~U}}Q{f722+B&7<~ooT29x+O0xm^9?p-zVxcrxi1|m!R}N4d`xvBz84@XpV;Lwior~myeySN! z&uk|ud3LX!xk?UY)r@N{U$VoA{32t8t3Y*Q#}4>Cqh$c`Y;V%jjf!9^a}*uR`i2hK zP}_}uuTi%w$s%`)XqRRL!pZ_33l{U{jZTmDrHW^vraQ1qFrV4@^0hp9r(OGC-jV0;-oJHs!CLtns)ust$9_M zQa*-^rhFZ*7w31nmI+NR0YgZ|HlR+%0Hr}2&m;v3VVrh$c0B+i^b`0H*#8JlXw{I# zL)lEZx`)Q{W-jVkdibr~x}1BlSPkRh1O+dOULcu6ty1^RdeaQa6$o3q%^bj}fWlhC zf&{8F%k?%)Z9gMtuZE$nj%d6SS2RrkVfd$>R-g^xI z0qHCj)2w+ZQfb1)vZLmnIcD3w!*P$P3=iivq#+Q-aKHz{fn}c^vMCD^C$=8&@=qYL z(Q{vJBA#Zxn8AD}?tP7Q6+k@KG>_aj$HClir3L|4#?cRjlYkvj-Km3$rqU(*#T3uj z0#rVi-oKsefGq3Y{-HVRQ~NBn;%u*1B)v+X8&Z+J?On)h7^7t5k;$79Dmt+KeHe}?KAObcw6tO#`flmwXfE43NQ#@A)G>M zyk4)0BK8$+ik(g+;isg06@_fbi@(w(c7W`3h zoeB&$9uu&l9Wb}QD`8`H-h+52*ds~h+$ZEX&djR?bFdU@Qcl6h?d{ah5bE4IG3Bky z_Ov^;zqfOD@#N5+ecjG^DI6c4yK$GGY8l{1Slbrub$V`YrpmULB_p@e6)Bg@Cl<2q z^QRm0o6bFdx~B;=voT@09lG(N<6gWubkAO$xa{x${>}F>gIYcIuZXCK5>uUTG6=E} zkl$(6m$I)xR~WT?*CLpRV_R|%AtJmN$?t6x^;6m(4aX?*XrY@;aY-xVNK};6Yxp%1 zKJDcuMplj^0a{5WGu6#a^`sLGYGr{qB7SC>jxuRbn{51_enK0(rxI1v(2o7dw7<^v z#*A+h=UtOZ1vIRz(JG17$k>wJ`r~XcnLoXj$W+(x$sD{Y2COX+8(At}<8kk?r^oNp zAEKwuzgc5mPXNHbqlD>H5qlEV`(Ecx87fW`=bH+ta4#IBbY?itvSuDD2N9)zZ40fQ zPm~Q6Q=KwfNguLSi7rK1sItNAekwZ)_TbmL^698qO1dMS^vP8Ffqp0VlRQgheOwK~Hip=+i0)DZfImJJ548LoSv&s$!AY33a@N_R35^yp>Ox3e$-TDjWa&&mZh} zgFiexK&4I3kA0T*YXtzU#dY4qU_;1dWs9Yvi<}fY>L~&&iy}LjRMAapgnpYBb*&KN zn^*;5B&$}ft4fz*2N-GPGr30qQ}h*e{*rN692#R;WQT}I5wyH~`BK#m<ief@W|S z>@&_SXW-R~r#|74h&tl-e6eM5ol6pBAuDA>k?D*S&j1EU_#E4d{JBWb;aq35XCvJ& z8GGc1H+w~(zFZJr$gWV8*=$RJy|=g5Qsr+~{O~)EsD6N)3JEh=jdKyaiOEjf=;ko< zKG7N9JaWXVV!)`Ry}tzoiG7K(#0hbg{w-#+gyEAOJCjV1ho!+JL2E(wBIECF(UzcE zT2OL<_u}W*wr?0Lv z6G5C)v++4ej7KKKxIdv_1_WBI*>knPW_NeT!N~bv|MOqe+Ji|@6<_n}7Z*pUI1m_U z{~?mYexTx!&#THPEYp`SU&J#&EAPwICuxUdk2Qy$69SLyoa_gH2%!~#mfQO~eUI1V z?U%{qc`aZeGWPj|5hjN5NB;JvtA*o$=Is!GNR+^cW&7|hH!Nd;jOzZbs|;svZy(Vj z$AhHwQL0n}S$2OoqUY?()tYZw777awUm&h9BSXuQrt^h*(?iQT?PpLnI0SmG%a^x z2+8Q-GA}0wh(nl0ZK>go)T6mnnpTEaV^necMgam~9l%?FZdkjF&y#`E_8G%_v)9V&R=>ESRUMVuuR=whi1+C@>3zz7MErA4EfRVRuh<0orFxvm*0D#>JI zWEYEBG;rq769W-aSz0q}$q1bbQT{Qq=%v)6Vu~}9$m^*d5NvVHySw|~+tu#4zwi3b zpRcJT_~*}Fp_UZdossYMruOV|Zf<*NOmHA&GH=<91t`ctLXuIcN>6!XSzNYIai{0c z-QeLqYzc-4Fgaw1p=7)n(t>#W*Q9^J)*!oehX+ku0R7vrzgLIz-?eNnFt!G+z%o{i zzu~-j=A+S2+zV0PKDLZpKC9+;6ra1=cICC>JmMJXMzPP&j;s(8$J2}F5R?Xqq8mzYJ{jzT=K(h6-ou?)%9+cAHMm}vF#|j3-nd%XvfsU$pN`t0TH3mU-HK(5b6d-eUGIi+ zamY(|zG&yWYRNmi^v>-cHXICV5A8eu`prw!2W@{nNR{yD=rACiy1(DD47e4ZK0DWp z@g}qNOOzSmd4Qd8yL31A_ukvR747}UQ*c6eAydeDX7|CAbb4(O-^^E39@20bclAt{5_TYUDZaFaDuD2q2nwZH?Lx=-6II>}58ahC(H0_6urN zm<`JT2TdNu)Q1=AW-&06Q7FO6=`^EFz=0%Mq#ahKZoy{GlZj&&R*}R;XEL@U*S7Xk zcXE8;;9xjBIPn>Ka1Re;>p=m8*BIzl@}iAKl*sP1d(sY2J2FgsvFz11W=fOMO!Lih z{G5NBAX@J8*-AZSY|}=US-`#psxVZ(YW0?_!?8Wb{YRPQ_WeVcO#5mlWikr^N~=3% z@i>5W!IA#;Pw(su4DAHR*5>^~+m>f*&hkP*4b;mqP|XtRikqQYX<$2PMF|T=2|b(5 z2bdjEY|Lmvr)4(cP-=5=TyUifKVy%9>x*PWP`z%M-wy?|^V6|Cd$U@H`^VDkU@{Cw zRe$3!Nh<2&2Qbo z-eLI5AK$w1(1O!!+5BoH93HjQ%8>tQ>=wU|<1or@(;jW2P$eE?acw7Zjv8A^8PRao z6MqZCaKk~tZoN}Qy`H|9`rP8dtkcoSn5ieziGM>aK_N0JdgGE$}4 zYRY1j&Y^#~x=#DlkGjNZrcO^`HbaF*YCvLOB7;hK-o-WE>ATCzTcPlYv_a>mn(f>> zBTDJ@Kv;rOj?j``S)Lp8`^xlw<-D~FuuMw8=oYey%ZPzgsR=Di2b=O7RYSC3E#!1JVznR}T&}y^vN`cqXhHJ=uuD%_#$i zP7zE2^R43A9}e~2I4^O;C!FPX_jmqZ$%&={z;JyJH*u^k*1NxJ5;5|o)4L#Z4d2>1idYznXVt@ZK^0qyF}}ZK}eX>#Nbv+d&}Ar{*eJir?V+=~IGF zz!1@{A?ReEL+%8r6XQToa55*%JyNlu>Loq;N?Buz+l1yAp@>v%E5T$$dg8n?*2nQo zwd$CkciTrsLUB-p&7*>xS{RuK)%jol=l{k1_~Tn;?Wd>b?l<3jn<{aIR3(w0F~^@U z5mIIFC<`7QLhexziW@BeI0jJrYqc5@aeDXe3l%^g75k#5H4Y6%zw`5psJXC>7=WSO zU&Mec#VNoTAEQE8cglKE^f;P$ChK2<+JmbkUBFqa#G5(2n1DaMy zc;?fYP~-*M+c1U+H)IYTJNeVwpW+;UHfFD=x>1EIO30_F-aZ`fv<^t1X!D7PD8W~x zWJF+8;`?NiX@&J;#gU3z%Ig7XgP1^P>%{(wDN*Z|Jf~Q};cJHDDiWA$pZ9yCK=#aY z-aXjUxbsZyu$3wRwCyStRSfld>q#lF9efF>^KiU{&-`- z=EAbis0B}jPou8(S%CK#pR|Oz`a3IZ!;Je+m=yH#b_mhdGeB?lf_kFfVY-0p| z_j+>+Mnhmmo^`psnR<)siM0bhqh4<$Bh^9TRs>EYQv?HIXEZy-I2-K|1g@~%NYg^{ zgwu!WnE)ngBgihB-54v|&t!wYzaO}-Uw@ZWGy)So0=6<0LB)vu!L>2X^W%@d2oG>} zcJ@eO#`{8u^TUt#=G066yYIfU_bzX~pUz;r3cgya1xjV~%Nf^*TFxyma*tV@rvm{A zOWITbo2_f9T;{`i6{MGp{x9(tpdzWtxz!-IR|KJm*vp^O@hAAegl~i*ydp);*vk8)n9MrW%Q8}qQfx4bAx9;rXRGIzK zUTfLB|M2M(rDh@0rvn}=0vJwUiqzSmKg>uT-%DvIA!;DYNkvKkFrh4_(pWU!%G5d7 z5*A4)U@_jjh7F+le3aQ#ptKRqBI6AOQHyeqFwK!6u`VuHMzmOIvyhe152qI%H_u8l zxE6tXUQaoZh&XwTem7OtT3hA?mVwL|H7;IDq~7g!d@Py99xVh6YIXPsjAYaZqx#$5 z{?<0uaTLfIPDC_BKmZsCtif`%(1?p=2@-7aiS9Pn2@kZcK_$GR=WDacgz04A9(rh+ zkDO&10ih5WlXe=-0Ojh{&5f%!n`lxaAE?Z5j!dGxPt_v&%d1;6I3u^yY!PJ^E2E_eWeG@`b;0v&Ns6D&1$S{kJS z_(=xMs11F{*H52WBrm-Kk+~e~9|XWe_ZEHA!)6Ya-Z_)h7KC}w>rmc}hUj9Cg#W;i z4r3tm2r!Gh{n^w}3WSzA#8jk&QgWOzU)X*vP*&GGJV7WGq?6+lJzJhLDtK{!Q=Z=(bCc#1+C&%YL&eJ;DzGZVQmn_3gF(UbDcnf;8@C>4pfU@L8pTyEr=+=Hd1Z zmNPUH0&S2Wc)8EJ+xvE)^1_tJ2ExBwQQx<-7*L@)vvvRY>76~Bc{n<0x$}z?X)@$2 zi{{H^R<F2iLYNdjXI3sL#CtEpaHV?a=eBp^M(r{4JL>7(0ALp-BstP;OA9Qtn<#Iu-B%cos zCbRv+y)cpuT_|IpnvoaB!YT&63sK+#g(*I^<2ZAZF~P%3`VH`dz)~mya+eWhEBj2b zWE}5wIaem*_NYDD^$AQ$%L9AN9%jGN!XlWba;UbQW!%li4lI`8@U+H|gsl_uvg=T- zRT2_0J|kz>vkaYw3C`KBC*zsQxV@(RU?*~I?{iR6b>rSL(C%a!iuRmt+jn6$4xTO- zjBjIeWV4aoGwob^Kv|0syZ1dm=}}1xI_u{b$2!B_!+2O%>*xCVN=ZfJ0O<=?M4~Vl zi~|aPTew!U=)U^;S$O&Ki5s-r;nT-Yez#R}FP>dk zhR&Rek6Ifc=SKaB`}v2D&X(4%7tmvr{onuY55ABsxj+B$7f1rVH?#2Me0AqCn=9 zu)28;id1*(dt}R67A{t`BTCziOirP z-bAZ9?LYIL@7}!=s~Br(_Z);E70)tJZ{&z3W8l5{-3Tz-svn#7h7;s|LK#~XDw#Rz z+v`i0%9JMWtKV1kBr=Vq?Sq7(b?1?@o=7-f%n3=!{4UQkB1D5rsb_a0`ZvxvX7fH) zN3^EO{@a}gm(DKp4N84zGvkCbm!8Rpx0v#9!g{6aAvlo^w&vtPoaW?wD0#BZNfpN5 z*?SP7VvJ)&Aeq;<&;RqEe-O{89bomgrIBr)C5?efNrA}K)n&qzH&J!LoRu;sz+z&) zTZ6KW3|d^X1yLdL>Gl5wB~8W@H#LmTzBXG zRy-3a2LtCtXKOr;Hf1__oDyMw0W^!;>l3Zp|M9>5$)0aP=Ya43@V6hdPtq*7Y#B?O zQ7_OBwLnrTazZpS7hSE!3?9d)40DxE##9AHP4FUO(ZZ;+@5r!HZu%tC5_yiThn^QG zcr*{lgtpRkUnq$oyQh~Mepkl_M)_KY`^%& zb_ml7AehDWSc&JbXHG1w=BX;SL9V(f2R#~1LS)n{(jUfo@K}OPgTJ{yFC;?`q}9@rFUIdZ6`~mAdIIQc%f{+?d!QT>N)r3_0y2`!A~b6 zq38K*)GKIZX#I|c(g+YP;^oWNIuk$teCunq zLV!>Tz#`}EhaY}$H4A=d3Df$xx@w1h|K4SP^ShVdv#|73c$3Ih77^N#z_Y_Zc3G>H zaBitcLo~)vu$pNigW-WmMapuKX;P+vKCfKPDocwum6e<-WGeJ6AZ!@Vh89WnfhQ;f z=2}3Hvso@K&K2FH#24H!4meR`+F++F{)DZnLsK1>iX8AJxbsuIXBxF1TTpUC~8_3#rKxBfdHhnl(kc4Vx|~J zo*E!FT~emjMko7J``>-6ahC$P|78PN04E*z=7v=aB!&mODTzIeek?s z+2a~YmTlQ2RBom+meR&{+FfBYsM7EZV&Fa1 z@HVV;e~A8PA#HIsiBF=T~no+}_a>*B{UQLwDp>TQr|7 zFtzMty4d=$8P-DC97xpytMSyXEbBIw;af^<&(nGK4ZZlmXrL-0`~z_Qg9rNgm;2CY zR>J@AKmJbwPyYDRCo`mb{`mCReYyVPK7PIrR3nd0_5o7~j0J`3STvqJ1+MFDWL}&? z=~*nPq>c7L(*h9e2@db_%NM_I#-9BOzNpjf5|C-E51az{576dYz`GD-*|VT@&P>_R zu9KjNe)+}ICzkEk14@Zh5(wCr=D_YP@Mhwu+1eAeZ?RDJFeYMiq5j1)Fl8foQ@b9V z)~8P&6Va(+0#;lk^sxD~C8+@V{-z_aBmq|xe~}o8WSeO(Zl!_HvkbB+0j!&weozBF z5sCWw{ZBgAFJ8RS^vstp(J{_v8$X+D)Q`@1ODwi<>}SON?*i3`wX{Fh?iM}^Z(B7S zpH(Cx#Wc;y;gPS{=RdVQH|P$MN|yz7wwEnEp}OIRgE{H3=z+n6*fpas5LGF2-qvv` z?R1d&5BFmv$sBUhJ?>{$L1~!GC;s#0t=Wx&?Smbk$%@6$RqZmLzdZ5>M;0Jg!Fes+ zd})Ezwi7O%?Ye*S4{zL?Z%)l&|KjfMzu4~=!(e0y>A39vcgH7|SUL-! z?LBsy1q+vkcE4@gt8IIBBlp7mRX(3_?T1^bVSadj<+kP@U%&X)<$T%y@t^=#YYtorS~0r|X+J$xHX~5L_@sR#-sk;~Z~1pv zZ{X;58cB-^X{8WfBLuQQHd1&i3N7xVs#k;Fq)%1XlD?~ppt;D5m-JrA%zc6ZkoGja zvWOFysTWTM%YBf6#r|q^+!$msf&@8gQM^#Fk9^E~yP9p*^GQ|YlI%_uh7@rvFiWyF zY0ChI47eor@TZBCtdlXKhnn_mQ`Ry{#QGg`UIbs<MfFZC|xET;>zGyCjuev3#X@P4GRijj6=YPwWBx9 zTE2PqT>Jg!pMQ2K(;2@*w#VL0Ro?#m=Rb=hKK8Z`#keSJf_Wbp+;}YkEB7=_w8B$% zHqFsT?){KfV^TIn#9ySeXUlhe{Uw}VoTXI+S}YL|S!-zgFw*U2#l{!uXeqNU708o) zNF}P&G2s3r5pX|TuMzwg*OHOv9;D~X`uHyn{%6n&(T)gA~RKYWhr zT4ori%w)51%|Jrz?bY;m&LNblaB~y<5^xZ({r>mg+PXI!y+``k@87@o+&6p(af&4a zs~>fL57q?0SX!Orf3{D2y;_THHT$N}|3T-9y&n?Y@;JqS4glKqc9v_Es-9ou%#Zg0 zzq@!ODkWhqQeF7H{eA5tUZWm9ZP3Q9m=rT6#q<^2zT{hKB>Yzb%!t9^^c0u;5PABOoWVrQt-g=mip_PI+3VU?65?fVxOPa-2` zfswJO^Ij~2y_t*K0>;RQus;ktji~={eSMcqEJ*22b~^z!!-(mExy7~xUzR|6wBb3c z1VY#mFe+zf@SQY5(nb@Z6=QZ!o}Oxsd@))G1U={v)WSPGJ@eoGhW$7V|LH&dXA4Zx zqA0`>ZQFwe{TUi3E%I(`@IO^&d!f)KmR^t&o6er7ZV#uV%vz&DYM}KZp@`! zy=KN%8rylz(VE4#zw@Owt1{?S0(Qx1PLr}5ks*u6c*$VUb^QT-@1Tqw9`@OMCeoP$ zTkzR*A||)xVy+p*N*RRw!oKV<4hQ<#c7xnwEV*(eIb zW~RT3v!WzNU!`uD=;_I^SfC#6?_3cePepXCnnNa#0AK+T2nz@J2E&9AES54ZEmbk5 z0bl{{`h+~hN9n!3NJ1#lq)v^^O{pyjoOE!+L$5KF9f1bB-8P^S|N7NyWiK2K4%OwC z&%m@i9OP)jk8N`?W67aEJUnpqDgdn5zqUgxQ5V*Ro(As=WhCo_qLvv&m8Mx2HYfnG z-ThVsT5U8Q6D-T~QByiavI|BKw+|>4tS_l@+-`+rtIB=kh3ndJxWd{q(I{M}~XDh}$ zapIzMrC$zU6AS=Z3kMSiMy$m$tAIm$_4<{R#IA3y z{V(r+30K!w=8T2`r9PhHfju`WMd*v~G+J_i5qvgC=r>(LP*e<2ENP+ZBgkNe7Gps* z2J8iy9?rEQK<607Ig#ji&Hh(~U101A%nDTi_Q^u$EK>W<)>9A9G$J3v@j#f0jaexa z#A-X$Kp;dFN&R`05yRG(?fKc4M_U^nbc+=L3?W@_&wFh)W1+R`_a5y0cJ1GD%T)H= zx8J_@Cl=s;e2>KFgYXaI+19Tt88|#S6iOW0xuxyJ;dthc&kkJH&RN%-6Vncxknx2| z0ZsyqEit%+Q2pyyZ!DXA2G|NsZp-E@TOG~ZyI=0znFR_UtSu+-^sP6M8(TB_;BpT3 zcHQ00tw33}p8n^zU)*kM$DYw4oY{^Fz;dyMD_=GU3=jX2kT8Dx?Bc1ozUkLyGZ8le zn;%&o<#rO99Td9|{IIjQ-pt*h8T3~#o;id)E-fJX<~P4V#SuzmfdLsaELNx#TDEE# zJ!R;z?elJLBpZuS1Sl-Y;Tt~VqznKIzOtoZ@>D=743ZXJ3ZSogOKxCDgth1_w@PG z=K!~e(CyH&*mM8oa^$A=+)tmKx*4O=leu_AXpv9}jw8=XvM^}`5AZIC52D}i5`5Xu zvm%!{I4!!Dug~qe`odeBKWX{hy{ZMGA8jqy?(Cu!TDxVlre^0$TYq?5^V!_W&dIes zk85{v-cVWJXXctkCpne2d_YYwgrnnv-)X|gH?XtrJvrTM*25mUjRo8fw-0`=b)>{| zIGBf0v1&n7)&2D2d)MyvMO#Y%`~3MivpDY4<=gP}H_xaZxc~G2`j_zI-+ne*I&uH_ zPv4k5x%AnmFOI&)XfM<=iv7iOmO0@gmqQ}ji}TtmWidn(Qc_#3)Ptiu#VC(+{?`my z_DhU;jU|rKYLyh70S*CRgv03eBZo0YjnY0&G~rNBoYW#%#N=PSdKKXBoz2FIMEMTU{$K=9?QquN91ZcL!b5l0gFXqh*)I+ zof>)h%a5ggu1luoY)U3=td1{?753Lw+qy^5u(dVNH(+!yy9XPFzZRg+|t z0HKmTfUcDwkVABD#=IQmfu(AMQg%xvCp4BdBo5} zK-gB=0j%90|M-J@`_pGztD(y2WcW;-X#EyZqyzw(M0=7mgt$iRaTYWk@!Q{gtIz!F z-`>Uvf`nNR-UQZer&0GC#B1zL=%EX+2v|57gRJc|6ZF2aKC%5#!JEz}vM+Cw$`7B5 z?c|IDR%5?u;ze?~5fc-G!2IK6LBtg;LQ-*j%;2$aR7~J!FBhZ<;E}_{zNdU4i46(E z{@nJ|+V9l8JBOr8?YEi0bJ*%!QFxbR>SFDga^kGxBO-4GaKx8Fr!(=iehNh|O>?T3wNfPpwsR)2 zoRS6hBLNke`_0YJYbOJ{l1->wgoO{ z`>Je3`xb3OY#YwqWH^vU1r;W$Vff4FfS=pf1tMc1GWD%JRXcnSJRH+$#2D;U8s;~v zsv14q4}G;#6~O$bw;y~-{O*VHX{?|4H}0k)-D&Oa*nMC7mOU5j??3;`|Lgwpm%juo zf@LWP3U6b{g1;+#Q10sxGs^_|ZvJ%1m*ht?5vT1VzJov4(PAP{%-`*+KX?e={q9@; z%{N~Otn(lLwo##!9;j^N*_(42BB6eJW2qr*BL)6im}XJoE)i_gsdk6L;d!B zlw(}X+;VDBvybwUIS5S_(K)nsYGG?xV%?(LSpxDZ{(oeh%Wu;_5XN^mwreL1Dv4SJ zRccSDQY%10;@*E54xBi^9WGpu;Mf-+#G_5>BzAU~-)vmk63N;;9NTN}V`g@~@6!jt zv;y%v`z>@pAOtoq2Wy&D)?x|^jU@7#)|L`pzIyHU_a_~FtWK`E4zN9=RyFYycazvy>(K&*%Vf7Iyi@Mdcz9U zwqV;+sMd!v?!HM=C)kah&1)mJWU(;d|~yi)#`;4+6c}mje5qr zZi7h38b2!MXrafs(KhjQmKJVb{CJm47 zjoc~9WlUBiAgY;^pq3!OeG0EyDVZIYPka}mdOtCv^t?~s1$X%PDABc^ox+j_yO#X; zcIlc$%6oZwxaAKH4rDOw^Y~E9Gov_@UW5V8+IE1_cP^ILVp9bVJg&!;Ab0wXn1a0| zTG-6P18$Hll9}|n@_>};P`%qNJy7k>FMf9UKIo@oaLVujc{O@Ddg|@1%59G(ZoD;4 zgiGq-|C&cPZ1#0WumIBn|~Q zV`0($tJ5p|mmnVW-!+>b4)%=5WG%|#ifSMm)u!I3OEvbGIFW|Sz^?Nw2!wbv9@P{x zk29#LfUrt8-lqE%4avYZ*i16CT{0(l?PYU~NR1FmexQcCV<9%IVJ)6~qa5x{6Knwu zklH`vY%6O*F}aaq_6@iYm-Yv zk>O!;UHbgv{o&ceGhxS(4Qpq!tLSxD*1li3*!Fe&84DTa6)?)s`hc`?+|V|n?PR4! z;EJ-+?@J-;e0i<+O~+Psvt+1kl6;oEM^hnMgu~g1oL$W)bO)L2PSk*@WOP#Dt-B}B zkF1}cou9a!-5vMr#gYH~<&%5!_MIFbAG?nq-n*bqVXgBTVtjuE7yu46-HU{=Gy(ts N002ovPDHLkV1h80sI~wA literal 0 HcmV?d00001 From 9988709bc26e8a804e4455bc3437e35be22833c8 Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Sat, 20 Sep 2014 01:25:41 +0800 Subject: [PATCH 02/21] implements support for transparency progress with #30 --- defs.js | 31 ++++-- index.js | 168 ++++++++++--------------------- src/decoder/buffer_worker.cpp | 7 +- src/decoder/decoder.h | 6 +- src/decoder/png_decoder.cpp | 29 +++--- src/decoder/util.cpp | 90 ++++++++++++----- src/encoder/encoder.h | 2 + src/encoder/init.cpp | 6 +- src/encoder/jpeg_worker.cpp | 12 ++- src/encoder/png_worker.cpp | 63 +++++++++--- src/image/crop_worker.cpp | 2 +- src/image/hsl_worker.cpp | 5 +- src/image/image.cpp | 28 ++++-- src/image/image.h | 7 +- src/image/pad_worker.cpp | 18 ++-- src/image/rotate_worker.cpp | 18 ++-- tests/02.operations/00.open.js | 10 ++ tests/02.operations/03.scale.js | 2 +- tests/02.operations/04.rotate.js | 13 +-- tests/02.operations/05.crop.js | 2 +- tests/imgs.js | 1 + 21 files changed, 294 insertions(+), 226 deletions(-) diff --git a/defs.js b/defs.js index b1978642..95cda15c 100644 --- a/defs.js +++ b/defs.js @@ -5,9 +5,11 @@ DEF_ROTATE_COLOR: 'gray', DEF_BORDER_COLOR: 'gray', DEF_PAD_COLOR: 'gray', + DEF_COLOR_ALPHA: 100, DEF_JPEG_QUALITY: 100, PNG_DEF_COMPRESSION: 'fast', - PNG_DEF_INTERLACED: false + PNG_DEF_INTERLACED: false, + PNG_DEF_TRANSPARENT: 'auto', }; exports.interpolations = { @@ -23,47 +25,56 @@ 'black': { r: 0, g: 0, - b: 0 + b: 0, + a: 100 }, 'white': { r: 255, g: 255, - b: 255 + b: 255, + a: 100 }, 'red': { r: 255, g: 0, - b: 0 + b: 0, + a: 100 }, 'blue': { r: 0, g: 0, - b: 255 + b: 255, + a: 100 }, 'green': { r: 0, g: 255, - b: 0 + b: 0, + a: 100 }, 'cyan': { r: 0, g: 255, - b: 255 + b: 255, + a: 100 }, 'yellow': { r: 255, g: 255, - b: 0 + b: 0, + a: 100 }, 'gray': { r: 128, g: 128, - b: 128 + b: 128, + a: 100 }, 'magenta': { r: 255, g: 0, - b: 255 + b: 255, + a: 100 } }; diff --git a/index.js b/index.js index f13eb08c..1d3df3f5 100644 --- a/index.js +++ b/index.js @@ -21,9 +21,36 @@ return v !== undefined; } - function image(pixelsBuf, width, height) { + function normalizeColor(color) { + if (typeof color === 'string') { + if (defs.colors[color]) color = defs.colors[color]; + else throw Error('Unknown color ' + color); + } else { + if (color instanceof Array) { + color = { + r: color[0], + g: color[1], + b: color[2], + a: color[3] + }; + } + if (color.a !== 0) color.a = color.a || defs.defaults.DEF_COLOR_ALPHA; + if (color.r != parseInt(color.r) || color.r < 0 || color.r > 255) + throw Error('\'red\' color component is invalid'); + if (color.g != parseInt(color.g) || color.g < 0 || color.g > 255) + throw Error('\'green\' color component is invalid'); + if (color.b != parseInt(color.b) || color.b < 0 || color.b > 255) + throw Error('\'blue\' color component is invalid'); + if (color.a != parseInt(color.a) || color.a < 0 || color.a > 100) + throw Error('\'alpha\' color component is invalid'); + } + return color; + } + + function image(pixelsBuf, width, height, trans) { this.__lwip = new lwip_image.LwipImage(pixelsBuf, width, height); this.__locked = false; + this.__trans = trans; } image.prototype.__lock = function() { @@ -93,25 +120,9 @@ try { var that = this; decree(defs.args.rotate)(arguments, function(degs, color, callback) { - if (typeof color === 'string') { - if (defs.colors[color]) color = defs.colors[color]; - else throw Error('Unknown color ' + color); - } else { - if (color instanceof Array) { - color = { - r: color[0], - g: color[1], - b: color[2] - }; - } - if (color.r != parseInt(color.r) || color.r < 0 || color.r > 255) - throw Error('\'red\' color component is invalid'); - if (color.g != parseInt(color.g) || color.g < 0 || color.g > 255) - throw Error('\'green\' color component is invalid'); - if (color.b != parseInt(color.b) || color.b < 0 || color.b > 255) - throw Error('\'blue\' color component is invalid'); - } - that.__lwip.rotate(+degs, +color.r, +color.g, +color.b, function(err) { + color = normalizeColor(color); + if (color.a < 100) that.__trans = true; + that.__lwip.rotate(+degs, +color.r, +color.g, +color.b, +color.a, function(err) { that.__release(); callback(err, that); }); @@ -277,25 +288,9 @@ try { var that = this; decree(defs.args.pad)(arguments, function(left, top, right, bottom, color, callback) { - if (typeof color === 'string') { - if (defs.colors[color]) color = defs.colors[color]; - else throw Error('Unknown color ' + color); - } else { - if (color instanceof Array) { - color = { - r: color[0], - g: color[1], - b: color[2] - }; - } - if (color.r != parseInt(color.r) || color.r < 0 || color.r > 255) - throw Error('\'red\' color component is invalid'); - if (color.g != parseInt(color.g) || color.g < 0 || color.g > 255) - throw Error('\'green\' color component is invalid'); - if (color.b != parseInt(color.b) || color.b < 0 || color.b > 255) - throw Error('\'blue\' color component is invalid'); - } - that.__lwip.pad(+left, +top, +right, +bottom, +color.r, +color.g, +color.b, function(err) { + color = normalizeColor(color); + if (color.a < 100) that.__trans = true; + that.__lwip.pad(+left, +top, +right, +bottom, +color.r, +color.g, +color.b, +color.a, function(err) { that.__release(); callback(err, that); }); @@ -311,26 +306,10 @@ try { var that = this; decree(defs.args.border)(arguments, function(width, color, callback) { - if (typeof color === 'string') { - if (defs.colors[color]) color = defs.colors[color]; - else throw Error('Unknown color ' + color); - } else { - if (color instanceof Array) { - color = { - r: color[0], - g: color[1], - b: color[2] - }; - } - if (color.r != parseInt(color.r) || color.r < 0 || color.r > 255) - throw Error('\'red\' color component is invalid'); - if (color.g != parseInt(color.g) || color.g < 0 || color.g > 255) - throw Error('\'green\' color component is invalid'); - if (color.b != parseInt(color.b) || color.b < 0 || color.b > 255) - throw Error('\'blue\' color component is invalid'); - } + color = normalizeColor(color); + if (color.a < 100) that.__trans = true; // we can just use image.pad... - that.__lwip.pad(+width, +width, +width, +width, +color.r, +color.g, +color.b, function(err) { + that.__lwip.pad(+width, +width, +width, +width, +color.r, +color.g, +color.b, +color.a, function(err) { that.__release(); callback(err, that); }); @@ -385,12 +364,17 @@ else throw Error('Invalid PNG compression'); params.interlaced = params.interlaced || defs.defaults.PNG_DEF_INTERLACED; if (typeof params.interlaced !== 'boolean') throw Error('PNG \'interlaced\' must be boolean'); + params.transparency = params.transparency || defs.defaults.PNG_DEF_TRANSPARENT; + if (typeof params.transparency !== 'boolean' && params.transparency.toLowerCase() !== 'auto') + throw Error('PNG \'transparency\' must be boolean or \'auto\''); + if (params.transparency.toLowerCase() !== 'auto') params.transparency = that.__trans; return encoder.png( that.__lwip.buffer(), that.__lwip.width(), that.__lwip.height(), params.compression, params.interlaced, + params.transparency, function(err, buffer) { that.__release(); callback(err, buffer); @@ -471,24 +455,7 @@ var that = this, decs = defs.args.rotate.slice(0, -1); // cut callback declaration decree(decs)(arguments, function(degs, color) { - if (typeof color === 'string') { - if (defs.colors[color]) color = defs.colors[color]; - else throw Error('Unknown color ' + color); - } else { - if (color instanceof Array) { - color = { - r: color[0], - g: color[1], - b: color[2] - }; - } - if (color.r != parseInt(color.r) || color.r < 0 || color.r > 255) - throw Error('\'red\' color component is invalid'); - if (color.g != parseInt(color.g) || color.g < 0 || color.g > 255) - throw Error('\'green\' color component is invalid'); - if (color.b != parseInt(color.b) || color.b < 0 || color.b > 255) - throw Error('\'blue\' color component is invalid'); - } + color = normalizeColor(color); that.__addOp(that.__image.rotate, [degs, color].filter(undefinedFilter)); }); return this; @@ -575,24 +542,7 @@ var that = this, decs = defs.args.pad.slice(0, -1); // cut callback declaration decree(decs)(arguments, function(left, top, right, bottom, color) { - if (typeof color === 'string') { - if (defs.colors[color]) color = defs.colors[color]; - else throw Error('Unknown color ' + color); - } else { - if (color instanceof Array) { - color = { - r: color[0], - g: color[1], - b: color[2] - }; - } - if (color.r != parseInt(color.r) || color.r < 0 || color.r > 255) - throw Error('\'red\' color component is invalid'); - if (color.g != parseInt(color.g) || color.g < 0 || color.g > 255) - throw Error('\'green\' color component is invalid'); - if (color.b != parseInt(color.b) || color.b < 0 || color.b > 255) - throw Error('\'blue\' color component is invalid'); - } + color = normalizeColor(color); that.__addOp(that.__image.pad, [left, top, right, bottom, color].filter(undefinedFilter)); }); return this; @@ -602,24 +552,7 @@ var that = this, decs = defs.args.border.slice(0, -1); // cut callback declaration decree(decs)(arguments, function(width, color) { - if (typeof color === 'string') { - if (defs.colors[color]) color = defs.colors[color]; - else throw Error('Unknown color ' + color); - } else { - if (color instanceof Array) { - color = { - r: color[0], - g: color[1], - b: color[2] - }; - } - if (color.r != parseInt(color.r) || color.r < 0 || color.r > 255) - throw Error('\'red\' color component is invalid'); - if (color.g != parseInt(color.g) || color.g < 0 || color.g > 255) - throw Error('\'green\' color component is invalid'); - if (color.b != parseInt(color.b) || color.b < 0 || color.b > 255) - throw Error('\'blue\' color component is invalid'); - } + color = normalizeColor(color); that.__addOp(that.__image.border, [width, color].filter(undefinedFilter)); }); return this; @@ -648,6 +581,9 @@ throw Error('Invalid PNG compression'); params.interlaced = params.interlaced || defs.defaults.PNG_DEF_INTERLACED; if (typeof params.interlaced !== 'boolean') throw Error('PNG \'interlaced\' must be boolean'); + params.transparency = params.transparency || defs.defaults.PNG_DEF_TRANSPARENT; + if (typeof params.transparency !== 'boolean' && params.transparency.toLowerCase() !== 'auto') + throw Error('PNG \'transparency\' must be boolean or \'auto\''); } else throw Error('Unknown type \'' + type + '\''); that.exec(function(err, image) { if (err) return callback(err); @@ -676,14 +612,14 @@ var opener = getOpener(type); fs.readFile(source, function(err, imbuff) { if (err) return callback(err); - opener(imbuff, function(err, pixelsBuf, width, height) { - callback(err, err ? undefined : new image(pixelsBuf, width, height)); + opener(imbuff, function(err, pixelsBuf, width, height, channels, trans) { + callback(err, err ? undefined : new image(pixelsBuf, width, height, trans)); }); }); } else if (source instanceof Buffer) { var opener = getOpener(type); - opener(source, function(err, pixelsBuf, width, height) { - callback(err, err ? undefined : new image(pixelsBuf, width, height)); + opener(source, function(err, pixelsBuf, width, height, channels, trans) { + callback(err, err ? undefined : new image(pixelsBuf, width, height, trans)); }); } else throw Error("Invalid source"); }); diff --git a/src/decoder/buffer_worker.cpp b/src/decoder/buffer_worker.cpp index b1cec437..70c3814e 100644 --- a/src/decoder/buffer_worker.cpp +++ b/src/decoder/buffer_worker.cpp @@ -31,7 +31,8 @@ void DecodeBufferWorker::Execute () { SetErrorMessage(err.c_str()); return; } - err = to3Channels(&img); + _trans = img->spectrum() == 2 || img->spectrum() == 4; + err = toRGBA(&img); if (err != "") { if (img) delete img; SetErrorMessage(err.c_str()); @@ -41,9 +42,7 @@ void DecodeBufferWorker::Execute () { _pixbuf = img->data(); _width = img->width(); _height = img->height(); - // TODO: support transparency: - _channels = 3; - _trans = false; + _channels = 4; delete img; return; } diff --git a/src/decoder/decoder.h b/src/decoder/decoder.h index 0af62e0a..c5da4d8f 100644 --- a/src/decoder/decoder.h +++ b/src/decoder/decoder.h @@ -4,6 +4,8 @@ #define cimg_display 0 #define cimg_verbosity 0 +#define N_CHANNELS 4 + #include #include #include @@ -64,10 +66,10 @@ inline void lwip_jpeg_error_exit (j_common_ptr cinfo) { /** * Utility function to take a CIMG object (**tmp), and convert it to 3 channels - * (if not 3 channels already) + * RGBA (if not 4 channels already) * Returns an error string if there was an error. Empty string otherwise. */ -string to3Channels(CImg ** img); +string toRGBA(CImg ** img); string decode_jpeg_buffer(char * buffer, size_t size, CImg ** img); string decode_png_buffer(char * buffer, size_t size, CImg ** img); diff --git a/src/decoder/png_decoder.cpp b/src/decoder/png_decoder.cpp index 20b69c16..b68873f7 100644 --- a/src/decoder/png_decoder.cpp +++ b/src/decoder/png_decoder.cpp @@ -53,21 +53,19 @@ string decode_png_buffer(char * buffer, size_t size, CImg ** cimg is_gray = true; bit_depth = 8; } - - if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) || - color_type == PNG_COLOR_TYPE_GRAY_ALPHA || - color_type == PNG_COLOR_TYPE_RGB_ALPHA) { - png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); - return "PNG transparency not supported"; + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { + png_set_tRNS_to_alpha(png_ptr); + color_type |= PNG_COLOR_MASK_ALPHA; } - - if (color_type == PNG_COLOR_TYPE_GRAY) { + if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { png_set_gray_to_rgb(png_ptr); color_type |= PNG_COLOR_MASK_COLOR; is_gray = true; } + if (color_type == PNG_COLOR_TYPE_RGB) + png_set_filler(png_ptr, 0xffffU, PNG_FILLER_AFTER); - if (color_type != PNG_COLOR_TYPE_RGB) { + if (!(color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA)) { png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); return "Invalid PNG color coding"; } @@ -77,21 +75,24 @@ string decode_png_buffer(char * buffer, size_t size, CImg ** cimg return "Invalid PNG bit depth"; } + bool is_alpha = (color_type == PNG_COLOR_TYPE_RGBA); + png_read_update_info(png_ptr, info_ptr); const int byte_depth = bit_depth >> 3; // Allocate Memory for Image Read png_bytep * const imgData = new png_bytep[height]; for (unsigned int row = 0; row < height; ++row) - imgData[row] = new png_byte[byte_depth * 3 * width]; + imgData[row] = new png_byte[byte_depth * N_CHANNELS * width]; png_read_image(png_ptr, imgData); png_read_end(png_ptr, end_info); *cimg = new CImg(); - (*cimg)->assign(width, height, 1, is_gray ? 1 : 3); + (*cimg)->assign(width, height, 1, (is_gray ? 1 : 3) + (is_alpha ? 1 : 0)); unsigned char * ptr_r = (*cimg)->data(0, 0, 0, 0), *ptr_g = is_gray ? 0 : (*cimg)->data(0, 0, 0, 1), - *ptr_b = is_gray ? 0 : (*cimg)->data(0, 0, 0, 2); + *ptr_b = is_gray ? 0 : (*cimg)->data(0, 0, 0, 2), + *ptr_a = is_alpha ? (*cimg)->data(0, 0, 0, is_gray ? 1 : 3) : NULL; switch (bit_depth) { case 8: cimg_forY(**cimg, y) { @@ -100,17 +101,19 @@ string decode_png_buffer(char * buffer, size_t size, CImg ** cimg *(ptr_r++) = (unsigned char) * (ptrs++); if (ptr_g) *(ptr_g++) = (unsigned char) * (ptrs++); else ++ptrs; if (ptr_b) *(ptr_b++) = (unsigned char) * (ptrs++); else ++ptrs; + if (ptr_a) *(ptr_a++) = (unsigned char) * (ptrs++); else ++ptrs; } } break; case 16: cimg_forY(**cimg, y) { const unsigned short * ptrs = (unsigned short *)(imgData[y]); - if (!cimg::endianness()) cimg::invert_endianness(ptrs, 3 * (*cimg)->width()); + if (!cimg::endianness()) cimg::invert_endianness(ptrs, N_CHANNELS * (*cimg)->width()); cimg_forX(**cimg, x) { *(ptr_r++) = (unsigned char) * (ptrs++); if (ptr_g) *(ptr_g++) = (unsigned char) * (ptrs++); else ++ptrs; if (ptr_b) *(ptr_b++) = (unsigned char) * (ptrs++); else ++ptrs; + if (ptr_a) *(ptr_a++) = (unsigned char) * (ptrs++); else ++ptrs; } } break; diff --git a/src/decoder/util.cpp b/src/decoder/util.cpp index 5ef1972c..6815108a 100644 --- a/src/decoder/util.cpp +++ b/src/decoder/util.cpp @@ -1,31 +1,75 @@ #include "decoder.h" -string to3Channels(CImg ** img) { +string toRGBA(CImg ** img) { CImg * tmp = NULL; - // need to convert image to 3 channels spectrum? + // need to convert image to 4 channels RGBA spectrum? int spectrum = (*img)->spectrum(); - if (spectrum != 3) { - if (spectrum == 1) { - try { - // create a 3-channels image of the same dimensions. - // TODO: maybe can realloc current image and do it in-place? - tmp = new CImg(**img, "x,y,1,3"); - } catch (CImgException e) { - if (tmp) delete tmp; - return "Out of memory"; - } - // copy the gray value of very pixel to each of R,G and B: - cimg_forXYZ(**img, x, y, z) { - unsigned char c = (*img)->atXYZC(x, y, z, 0); - tmp->fillC(x, y, z, c, c, c); - } - delete *img; - *img = tmp; - } else { - // TODO: support 4 channels (CMYK) - // need to find out if the 4th channel is alpha... - return "Unsupported number of channels"; + if (spectrum == 4) { // RGBA + cimg_forXYZ(**img, x, y, z) { + unsigned char r = (*img)->atXYZC(x, y, z, 0), + g = (*img)->atXYZC(x, y, z, 1), + b = (*img)->atXYZC(x, y, z, 2), + a = (*img)->atXYZC(x, y, z, 3); + a = (unsigned char) ((a / 255.0) * 100.0); // range change [0,255] -> [0,100] + (*img)->fillC(x, y, z, r, g, b, a); } + } else if (spectrum == 1) { // grayscale + try { + // create a 4-channels image of the same dimensions. + // TODO: maybe can realloc current image and do it in-place? + tmp = new CImg(**img, "x,y,1,4"); + } catch (CImgException e) { + if (tmp) delete tmp; + return "Out of memory"; + } + // copy the gray value of very pixel to each of R,G and B + // and set alpha=100% + cimg_forXYZ(**img, x, y, z) { + unsigned char c = (*img)->atXYZC(x, y, z, 0); + tmp->fillC(x, y, z, c, c, c, 100); + } + delete *img; + *img = tmp; + } else if (spectrum == 2) { // grayscale + alpha + try { + // create a 4-channels image of the same dimensions. + // TODO: maybe can realloc current image and do it in-place? + tmp = new CImg(**img, "x,y,1,4"); + } catch (CImgException e) { + if (tmp) delete tmp; + return "Out of memory"; + } + // copy the gray value of very pixel to each of R,G and B + // and set alpha=alpha + cimg_forXYZ(**img, x, y, z) { + unsigned char c = (*img)->atXYZC(x, y, z, 0), + a = (*img)->atXYZC(x, y, z, 1); + a = (unsigned char) ((a / 255.0) * 100.0); // range change [0,255] -> [0,100] + tmp->fillC(x, y, z, c, c, c, a); + } + delete *img; + *img = tmp; + } else if (spectrum == 3) { // RGB + try { + // create a 4-channels image of the same dimensions. + // TODO: maybe can realloc current image and do it in-place? + tmp = new CImg(**img, "x,y,1,4"); + } catch (CImgException e) { + if (tmp) delete tmp; + return "Out of memory"; + } + // copy the RGB values of very pixel to each of R,G and B + // and set alpha=100% + cimg_forXYZ(**img, x, y, z) { + unsigned char r = (*img)->atXYZC(x, y, z, 0), + g = (*img)->atXYZC(x, y, z, 1), + b = (*img)->atXYZC(x, y, z, 2); + tmp->fillC(x, y, z, r, g, b, 100); + } + delete *img; + *img = tmp; + } else { + return "Unsupported number of channels"; } return ""; } diff --git a/src/encoder/encoder.h b/src/encoder/encoder.h index daffa0bd..a09823a3 100644 --- a/src/encoder/encoder.h +++ b/src/encoder/encoder.h @@ -51,6 +51,7 @@ class EncodeToPngBufferWorker : public NanAsyncWorker { size_t height, int compression, bool interlaced, + bool trans, NanCallback * callback ); ~EncodeToPngBufferWorker(); @@ -62,6 +63,7 @@ class EncodeToPngBufferWorker : public NanAsyncWorker { size_t _height; int _compression; bool _interlaced; + bool _trans; char * _pngbuf; size_t _pngbufsize; }; diff --git a/src/encoder/init.cpp b/src/encoder/init.cpp index 87ec2ded..75087cb4 100644 --- a/src/encoder/init.cpp +++ b/src/encoder/init.cpp @@ -22,7 +22,7 @@ NAN_METHOD(encodeToJpegBuffer) { NanReturnUndefined(); } -// encoder.png(pixbuf, width, height, compression, interlaced, callback) +// encoder.png(pixbuf, width, height, compression, interlaced, trans, callback) NAN_METHOD(encodeToPngBuffer) { NanScope(); @@ -32,7 +32,8 @@ NAN_METHOD(encodeToPngBuffer) { size_t height = args[2].As()->Value(); int compression = args[3].As()->Value(); bool interlaced = args[4]->BooleanValue(); - NanCallback * callback = new NanCallback(args[5].As()); + bool trans = args[5]->BooleanValue(); + NanCallback * callback = new NanCallback(args[6].As()); NanAsyncQueueWorker( new EncodeToPngBufferWorker( @@ -41,6 +42,7 @@ NAN_METHOD(encodeToPngBuffer) { height, compression, interlaced, + trans, callback ) ); diff --git a/src/encoder/jpeg_worker.cpp b/src/encoder/jpeg_worker.cpp index 90357441..d6c05062 100644 --- a/src/encoder/jpeg_worker.cpp +++ b/src/encoder/jpeg_worker.cpp @@ -1,5 +1,7 @@ #include "encoder.h" +#define RGB_N_CHANNELS 3 + EncodeToJpegBufferWorker::EncodeToJpegBufferWorker( unsigned char * pixbuf, size_t width, @@ -11,19 +13,21 @@ EncodeToJpegBufferWorker::EncodeToJpegBufferWorker( // pixbuf needs to be copied, because the buffer may be gc'ed by // V8 at any time. // !!! _pixbuf still needs to be freed by us when no longer needed (see Execute) - _pixbuf = (unsigned char *) malloc(width * height * 3 * sizeof(unsigned char)); + _pixbuf = (unsigned char *) malloc(width * height * RGB_N_CHANNELS * sizeof(unsigned char)); if (_pixbuf == NULL) { // TODO: check - can I use SetErrorMessage here? SetErrorMessage("Out of memory"); return; } - memcpy(_pixbuf, pixbuf, width * height * 3 * sizeof(unsigned char)); + // pixbuf is actually RGBA (4 channels), but we discard the A channel + // for jpeg, which is at the end of the buffer. + memcpy(_pixbuf, pixbuf, width * height * RGB_N_CHANNELS * sizeof(unsigned char)); } EncodeToJpegBufferWorker::~EncodeToJpegBufferWorker() {} void EncodeToJpegBufferWorker::Execute () { - unsigned int dimbuf = 3; + unsigned int dimbuf = RGB_N_CHANNELS; J_COLOR_SPACE colortype = JCS_RGB; JSAMPROW row_pointer[1]; unsigned char * tmp = NULL; @@ -58,7 +62,7 @@ void EncodeToJpegBufferWorker::Execute () { jpeg_start_compress(&cinfo, TRUE); // shared memory cimg. no new memory is allocated. - CImg tmpimg(_pixbuf, _width, _height, 1, 3, true); + CImg tmpimg(_pixbuf, _width, _height, 1, RGB_N_CHANNELS, true); while (cinfo.next_scanline < cinfo.image_height) { unsigned char * ptrd = tmp; const unsigned char diff --git a/src/encoder/png_worker.cpp b/src/encoder/png_worker.cpp index 074caa61..35e08bd7 100644 --- a/src/encoder/png_worker.cpp +++ b/src/encoder/png_worker.cpp @@ -1,31 +1,50 @@ #include "encoder.h" +#define RGB_N_CHANNELS 3 +#define RGBA_N_CHANNELS 4 + EncodeToPngBufferWorker::EncodeToPngBufferWorker( unsigned char * pixbuf, size_t width, size_t height, int compression, bool interlaced, + bool trans, NanCallback * callback ): NanAsyncWorker(callback), _width(width), _height(height), - _compression(compression), _interlaced(interlaced), _pngbuf(NULL), - _pngbufsize(0) { + _compression(compression), _interlaced(interlaced), _trans(trans), + _pngbuf(NULL), _pngbufsize(0) { // pixbuf needs to be copied, because the buffer may be gc'ed by // V8 at any time. // !!! _pixbuf still needs to be freed by us when no longer needed (see Execute) - _pixbuf = (unsigned char *) malloc(width * height * 3 * sizeof(unsigned char)); + _pixbuf = (unsigned char *) malloc( + width * + height * + (_trans ? RGBA_N_CHANNELS : RGB_N_CHANNELS) * + sizeof(unsigned char) + ); if (_pixbuf == NULL) { // TODO: check - can I use SetErrorMessage here? SetErrorMessage("Out of memory"); return; } - memcpy(_pixbuf, pixbuf, width * height * 3 * sizeof(unsigned char)); + // pixbuf is actually RGBA (4 channels), but we copy the A channel (which is + // at the end of the buffer) only if the image is marked as transparent. + memcpy( + _pixbuf, + pixbuf, + width * + height * + (_trans ? RGBA_N_CHANNELS : RGB_N_CHANNELS) * + sizeof(unsigned char) + ); } EncodeToPngBufferWorker::~EncodeToPngBufferWorker() {} void EncodeToPngBufferWorker::Execute () { - unsigned int rowBytes = _width * 3; // TODO: 3 channels per pixel is currently hard coded + int n_chan = _trans ? RGBA_N_CHANNELS : RGB_N_CHANNELS; + unsigned int rowBytes = _width * n_chan; int interlaceType; int compLevel; switch (_compression) { @@ -94,22 +113,40 @@ void EncodeToPngBufferWorker::Execute () { } } - png_set_IHDR(png_ptr, info_ptr, _width, _height, - 8, PNG_COLOR_TYPE_RGB, interlaceType, - PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + png_set_IHDR( + png_ptr, + info_ptr, + _width, + _height, + 8, + _trans ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB, + interlaceType, + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT + ); png_set_compression_level(png_ptr, compLevel); pngWriteCbData buffinf = {NULL, 0}; png_set_write_fn(png_ptr, (voidp) &buffinf, pngWriteCB, NULL); - CImg tmpimg(_pixbuf, _width, _height, 1, 3, true); + CImg tmpimg( + _pixbuf, + _width, + _height, + 1, + n_chan, + true + ); cimg_forXY(tmpimg, x, y) { unsigned char r = tmpimg.atXYZC(x, y, 0, 0), g = tmpimg.atXYZC(x, y, 0, 1), - b = tmpimg.atXYZC(x, y, 0, 2); - rowPnts[y][3 * x] = r; - rowPnts[y][3 * x + 1] = g; - rowPnts[y][3 * x + 2] = b; + b = tmpimg.atXYZC(x, y, 0, 2), + a = _trans ? tmpimg.atXYZC(x, y, 0, 3) : 0; + a = (unsigned char) ((a / 100.0) * 255.0); // range change [0,100] -> [0,255] + rowPnts[y][n_chan * x] = r; + rowPnts[y][n_chan * x + 1] = g; + rowPnts[y][n_chan * x + 2] = b; + if (_trans) rowPnts[y][n_chan * x + 3] = a; } png_set_rows(png_ptr, info_ptr, rowPnts); diff --git a/src/image/crop_worker.cpp b/src/image/crop_worker.cpp index 63cff8bb..cefe2aa1 100644 --- a/src/image/crop_worker.cpp +++ b/src/image/crop_worker.cpp @@ -14,7 +14,7 @@ CropWorker::~CropWorker() {} void CropWorker::Execute () { try { - _cimg->crop(_left, _top, 0, 0, _right, _bottom, 0, 2); + _cimg->crop(_left, _top, 0, 0, _right, _bottom, 0, N_CHANNELS - 1); } catch (CImgException e) { SetErrorMessage("Unable to crop image"); return; diff --git a/src/image/hsl_worker.cpp b/src/image/hsl_worker.cpp index c6649580..cd29b671 100644 --- a/src/image/hsl_worker.cpp +++ b/src/image/hsl_worker.cpp @@ -16,7 +16,8 @@ void HSLWorker::Execute () { cimg_forXY(*_cimg, x, y) { unsigned char r = (*_cimg)(x, y, 0, 0), g = (*_cimg)(x, y, 0, 1), - b = (*_cimg)(x, y, 0, 2); + b = (*_cimg)(x, y, 0, 2), + a = (*_cimg)(x, y, 0, 3); float h, s, l; rgb_to_hsl(r, g, b, &h, &s, &l); @@ -39,7 +40,7 @@ void HSLWorker::Execute () { hsl_to_rgb(h, s, l, &r, &g, &b); - _cimg->fillC(x, y, 0, r, g, b); + _cimg->fillC(x, y, 0, r, g, b, a); } } catch (CImgException e) { SetErrorMessage("Unable to modify HSL"); diff --git a/src/image/image.cpp b/src/image/image.cpp index 7b160ecb..9fb705f2 100644 --- a/src/image/image.cpp +++ b/src/image/image.cpp @@ -27,7 +27,7 @@ void LwipImage::Init(Handle exports) { LwipImage::LwipImage(unsigned char * data, size_t width, size_t height) { // TODO: CImg constructor may throw an exception. handle it in LwipImage::New. - _cimg = new CImg(data, width, height, 1, 3, false); + _cimg = new CImg(data, width, height, 1, N_CHANNELS, false); } LwipImage::~LwipImage() { @@ -120,7 +120,8 @@ NAN_METHOD(LwipImage::resize) { // args[1] - R // args[2] - G // args[3] - B -// args[4] - callback +// args[4] - A +// args[5] - callback NAN_METHOD(LwipImage::rotate) { NanScope(); @@ -128,7 +129,8 @@ NAN_METHOD(LwipImage::rotate) { unsigned char r = (unsigned char) args[1].As()->Value(); unsigned char g = (unsigned char) args[2].As()->Value(); unsigned char b = (unsigned char) args[3].As()->Value(); - NanCallback * callback = new NanCallback(args[4].As()); + unsigned char a = (unsigned char) args[4].As()->Value(); + NanCallback * callback = new NanCallback(args[5].As()); CImg * cimg = ObjectWrap::Unwrap(args.This())->_cimg; NanAsyncQueueWorker( @@ -137,6 +139,7 @@ NAN_METHOD(LwipImage::rotate) { r, g, b, + a, cimg, callback ) @@ -233,10 +236,11 @@ NAN_METHOD(LwipImage::mirror) { // args[1] - top // args[2] - right // args[3] - bottom -// args[4] - red -// args[5] - green -// args[6] - blue -// args[7] - callback +// args[4] - r +// args[5] - g +// args[6] - b +// args[7] - a +// args[8] - callback NAN_METHOD(LwipImage::pad) { NanScope(); @@ -244,10 +248,11 @@ NAN_METHOD(LwipImage::pad) { size_t top = (size_t) args[1].As()->Value(); size_t right = (size_t) args[2].As()->Value(); size_t bottom = (size_t) args[3].As()->Value(); - unsigned char r = (unsigned char) args[4].As()->Value(); - unsigned char g = (unsigned char) args[5].As()->Value(); - unsigned char b = (unsigned char) args[6].As()->Value(); - NanCallback * callback = new NanCallback(args[7].As()); + unsigned char r = (unsigned char) args[4].As()->Value(), + g = (unsigned char) args[5].As()->Value(), + b = (unsigned char) args[6].As()->Value(), + a = (unsigned char) args[7].As()->Value(); + NanCallback * callback = new NanCallback(args[8].As()); CImg * cimg = ObjectWrap::Unwrap(args.This())->_cimg; NanAsyncQueueWorker( @@ -259,6 +264,7 @@ NAN_METHOD(LwipImage::pad) { r, g, b, + a, cimg, callback ) diff --git a/src/image/image.h b/src/image/image.h index 9c013a73..dadfac98 100644 --- a/src/image/image.h +++ b/src/image/image.h @@ -4,6 +4,8 @@ #define cimg_display 0 #define cimg_verbosity 0 +#define N_CHANNELS 4 + #include #include #include @@ -72,6 +74,7 @@ class RotateWorker : public NanAsyncWorker { unsigned char r, unsigned char g, unsigned char b, + unsigned char a, CImg * cimg, NanCallback * callback ); @@ -83,6 +86,7 @@ class RotateWorker : public NanAsyncWorker { unsigned char _r; unsigned char _g; unsigned char _b; + unsigned char _a; CImg * _cimg; }; @@ -149,6 +153,7 @@ class PadWorker : public NanAsyncWorker { unsigned char r, unsigned char g, unsigned char b, + unsigned char a, CImg * cimg, NanCallback * callback ); @@ -163,6 +168,7 @@ class PadWorker : public NanAsyncWorker { unsigned char _r; unsigned char _g; unsigned char _b; + unsigned char _a; CImg * _cimg; }; @@ -204,6 +210,5 @@ void rgb_to_hsl(unsigned char r, unsigned char g, unsigned char b, float * h, float * s, float * l); void hsl_to_rgb(float h, float s, float l, unsigned char * r, unsigned char * g, unsigned char * b); -float hue2rgb(float p, float q, float t); #endif diff --git a/src/image/pad_worker.cpp b/src/image/pad_worker.cpp index 244a94a4..e7176444 100644 --- a/src/image/pad_worker.cpp +++ b/src/image/pad_worker.cpp @@ -8,10 +8,11 @@ PadWorker::PadWorker( unsigned char r, unsigned char g, unsigned char b, + unsigned char a, CImg * cimg, NanCallback * callback ): NanAsyncWorker(callback), _left(left), _top(top), _right(right), - _bottom(bottom), _r(r), _g(g), _b(b), _cimg(cimg) {} + _bottom(bottom), _r(r), _g(g), _b(b), _a(a), _cimg(cimg) {} PadWorker::~PadWorker() {} @@ -23,7 +24,7 @@ void PadWorker::Execute () { newheight = oldheight + _top + _bottom; if (oldwidth != newwidth || oldheight != newheight) { try { - res = new CImg(newwidth, newheight, 1, 3); + res = new CImg(newwidth, newheight, 1, N_CHANNELS); } catch (CImgException e) { SetErrorMessage("Out of memory"); return; @@ -31,33 +32,34 @@ void PadWorker::Execute () { // fill left border: for (size_t x = 0; x < _left; x++) { for (size_t y = 0; y < newheight; y++) { - res->fillC(x, y, 0, _r, _g, _b); + res->fillC(x, y, 0, _r, _g, _b, _a); } } // fill right border: for (size_t x = oldwidth + _left; x < newwidth; x++) { for (size_t y = 0; y < newheight; y++) { - res->fillC(x, y, 0, _r, _g, _b); + res->fillC(x, y, 0, _r, _g, _b, _a); } } // fill top border: for (size_t x = _left; x < oldwidth + _left; x++) { for (size_t y = 0; y < _top; y++) { - res->fillC(x, y, 0, _r, _g, _b); + res->fillC(x, y, 0, _r, _g, _b, _a); } } // fill bottom border: for (size_t x = _left; x < oldwidth + _left; x++) { for (size_t y = _top + oldheight; y < newheight; y++) { - res->fillC(x, y, 0, _r, _g, _b); + res->fillC(x, y, 0, _r, _g, _b, _a); } } // paste original image: cimg_forXY(*_cimg, x, y) { unsigned char r = (*_cimg)(x, y, 0, 0), g = (*_cimg)(x, y, 0, 1), - b = (*_cimg)(x, y, 0, 2); - res->fillC(x + _left, y + _top, 0, r, g, b); + b = (*_cimg)(x, y, 0, 2), + a = (*_cimg)(x, y, 0, 3); + res->fillC(x + _left, y + _top, 0, r, g, b, a); } res->move_to(*_cimg); delete res; diff --git a/src/image/rotate_worker.cpp b/src/image/rotate_worker.cpp index 731e09a0..8bfd3b19 100644 --- a/src/image/rotate_worker.cpp +++ b/src/image/rotate_worker.cpp @@ -5,9 +5,10 @@ RotateWorker::RotateWorker( unsigned char r, unsigned char g, unsigned char b, + unsigned char a, CImg * cimg, NanCallback * callback -): NanAsyncWorker(callback), _degs(degs), _r(r), _g(g), _b(b), _cimg(cimg) {} +): NanAsyncWorker(callback), _degs(degs), _r(r), _g(g), _b(b), _a(a), _cimg(cimg) {} RotateWorker::~RotateWorker() {} @@ -19,24 +20,25 @@ void RotateWorker::Execute () { oldheight = _cimg->height(); try { // 2 pixels wider and taller - res = new CImg(oldwidth + 2, oldheight + 2, 1, 3); + res = new CImg(oldwidth + 2, oldheight + 2, 1, N_CHANNELS); } catch (CImgException e) { SetErrorMessage("Out of memory"); return; } cimg_forX(*res, x) { - res->fillC(x, 0, 0, _r, _g, _b); - res->fillC(x, oldheight + 1, 0, _r, _g, _b); + res->fillC(x, 0, 0, _r, _g, _b, _a); + res->fillC(x, oldheight + 1, 0, _r, _g, _b, _a); } cimg_forY(*res, y) { - res->fillC(0, y, 0, _r, _g, _b); - res->fillC(oldwidth + 1, y, 0, _r, _g, _b); + res->fillC(0, y, 0, _r, _g, _b, _a); + res->fillC(oldwidth + 1, y, 0, _r, _g, _b, _a); } cimg_forXY(*_cimg, x, y) { unsigned char r = (*_cimg)(x, y, 0, 0), g = (*_cimg)(x, y, 0, 1), - b = (*_cimg)(x, y, 0, 2); - res->fillC(x + 1, y + 1, 0, r, g, b); + b = (*_cimg)(x, y, 0, 2), + a = (*_cimg)(x, y, 0, 3); + res->fillC(x + 1, y + 1, 0, r, g, b, a); } res->move_to(*_cimg); delete res; diff --git a/tests/02.operations/00.open.js b/tests/02.operations/00.open.js index 4150e1e8..74af5d62 100644 --- a/tests/02.operations/00.open.js +++ b/tests/02.operations/00.open.js @@ -88,6 +88,16 @@ describe('lwip.open', function() { }); }); + describe('transparent image', function() { + it('should succeed', function(done) { + lwip.open(imgs.png.trans, function(err, img) { + should(err).not.be.Error; + img.should.be.OK; + done(); + }); + }); + }); + describe('invalid image', function() { it('should fail', function(done) { lwip.open(imgs.png.inv, function(err, img) { diff --git a/tests/02.operations/03.scale.js b/tests/02.operations/03.scale.js index 87e2b895..20f2953f 100644 --- a/tests/02.operations/03.scale.js +++ b/tests/02.operations/03.scale.js @@ -18,7 +18,7 @@ describe('lwip.scale', function() { }); beforeEach(function(done) { - lwip.open(imgs.png.rgb, function(err, img) { + lwip.open(imgs.png.trans, function(err, img) { image = img; done(err); }); diff --git a/tests/02.operations/04.rotate.js b/tests/02.operations/04.rotate.js index 9e0ac047..bf3957bd 100644 --- a/tests/02.operations/04.rotate.js +++ b/tests/02.operations/04.rotate.js @@ -106,20 +106,21 @@ describe('lwip.rotate', function() { }); }); - describe('45 degs, [50,100,300] fill', function() { + describe('45 degs, [50,100,300,75] fill', function() { it('should succeed', function(done) { - current.push(45, 'degs', [50, 100, 250]); - image.rotate(45, [50, 100, 250], done); + current.push(45, 'degs', [50, 100, 250,75]); + image.rotate(45, [50, 100, 250, 75], done); }); }); - describe('-5 degs, {r:200,g:110,b:220} fill', function() { + describe('-5 degs, {r:200,g:110,b:220,a:50} fill', function() { it('should succeed', function(done) { - current.push(-5, 'degs', 'r-200,g-110,b-220'); + current.push(-5, 'degs', 'r-200,g-110,b-220,a-50'); image.rotate(-5, { r: 200, g: 110, - b: 220 + b: 220, + a: 50 }, done); }); }); diff --git a/tests/02.operations/05.crop.js b/tests/02.operations/05.crop.js index 7994cd20..5ff4b942 100644 --- a/tests/02.operations/05.crop.js +++ b/tests/02.operations/05.crop.js @@ -18,7 +18,7 @@ describe('lwip.crop', function() { }); beforeEach(function(done) { - lwip.open(imgs.jpg.rgb, function(err, img) { + lwip.open(imgs.png.trans, function(err, img) { image = img; done(err); }); diff --git a/tests/imgs.js b/tests/imgs.js index 33d99995..582e1947 100644 --- a/tests/imgs.js +++ b/tests/imgs.js @@ -13,6 +13,7 @@ module.exports = { gs: join(__dirname, imbase, 'gs.png'), rgb: join(__dirname, imbase, 'rgb.png'), noex: join(__dirname, imbase, 'rgbpng'), + trans: join(__dirname, imbase, 'trans.png'), inv: join(__dirname, imbase, 'invalid.png') } }; From 0faef84e8acc07b2d1938b3c1107088966718e9c Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Sat, 20 Sep 2014 01:32:29 +0800 Subject: [PATCH 03/21] inits v0.0.5 branch --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 08c7abf8..4f4c2d64 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![Version](http://img.shields.io/npm/v/lwip.svg)](https://www.npmjs.org/package/lwip) -[![Build Status](https://api.travis-ci.org/EyalAr/lwip.svg?branch=master)](https://travis-ci.org/EyalAr/lwip) -[![Coverage Status](https://img.shields.io/coveralls/EyalAr/lwip.svg)](https://coveralls.io/r/EyalAr/lwip) +[![Build Status](https://api.travis-ci.org/EyalAr/lwip.svg?branch=v0.0.5)](https://travis-ci.org/EyalAr/lwip) +[![Coverage Status](https://img.shields.io/coveralls/EyalAr/lwip/v0.0.5.svg)](https://coveralls.io/r/EyalAr/lwip) # Light-weight image processor for NodeJS From 54a54936d1848c138f05fbdeb72367292cfed509 Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Sat, 20 Sep 2014 11:44:24 +0800 Subject: [PATCH 04/21] tweaks test suite --- package.json | 6 +++--- tests/02.operations/01.writeFile.js | 2 -- tests/04.batch/index.js | 4 ---- tests/mocha.opts | 4 ++++ 4 files changed, 7 insertions(+), 9 deletions(-) create mode 100644 tests/mocha.opts diff --git a/package.json b/package.json index acd18b26..1d5b108c 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,9 @@ }, "scripts": { "install": "node-gyp rebuild", - "test": "./node_modules/.bin/mocha --reporter spec --recursive tests/", - "coverage": "./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha -- --reporter spec --recursive tests/", - "travis": "./node_modules/.bin/istanbul cover --report lcovonly ./node_modules/.bin/_mocha -- --reporter spec --recursive tests/ && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" + "test": "./node_modules/.bin/mocha --opts tests/mocha.opts tests", + "coverage": "./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha -- --opts tests/mocha.opts tests", + "travis": "./node_modules/.bin/istanbul cover --report lcovonly ./node_modules/.bin/_mocha -- --opts tests/mocha.opts --bail tests && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" }, "gypfile": true, "description": "Comprehensive, fast, and simple image processing and manipulation", diff --git a/tests/02.operations/01.writeFile.js b/tests/02.operations/01.writeFile.js index 77fb3ed4..2d1a8b9c 100644 --- a/tests/02.operations/01.writeFile.js +++ b/tests/02.operations/01.writeFile.js @@ -119,7 +119,6 @@ describe('lwip.writeFile', function() { }); describe('params specified - high compression, not interlaced', function() { - this.timeout(4000); // 4 seconds. high compression can take more time it('should succeed', function(done) { image.writeFile(outpathPng, { compression: 'high', @@ -129,7 +128,6 @@ describe('lwip.writeFile', function() { }); describe('params specified - high compression, interlaced', function() { - this.timeout(4000); // 4 seconds. high compression can take more time it('should succeed', function(done) { image.writeFile(outpathPng, { compression: 'high', diff --git a/tests/04.batch/index.js b/tests/04.batch/index.js index 3ef5104a..0001ee7f 100644 --- a/tests/04.batch/index.js +++ b/tests/04.batch/index.js @@ -80,7 +80,6 @@ describe('image.batch', function() { }); describe('high compression', function() { - this.timeout(4000); // 4 seconds. high compression can take more time it('should succeed', function(done) { batch.toBuffer('png', { interlaced: false, @@ -118,7 +117,6 @@ describe('image.batch', function() { }); describe('high compression', function() { - this.timeout(4000); // 4 seconds. high compression can take more time it('should succeed', function(done) { batch.toBuffer('png', { interlaced: true, @@ -186,7 +184,6 @@ describe('image.batch', function() { }); describe('high compression', function() { - this.timeout(4000); // 4 seconds. high compression can take more time it('should succeed', function(done) { batch.writeFile(join(tmpDir, 'btch-noint#hicomp-' + ops.join('#') + '.png'), 'png', { interlaced: false, @@ -218,7 +215,6 @@ describe('image.batch', function() { }); describe('high compression', function() { - this.timeout(4000); // 4 seconds. high compression can take more time it('should succeed', function(done) { batch.writeFile(join(tmpDir, 'btch-intr#hicomp-' + ops.join('#') + '.png'), 'png', { interlaced: true, diff --git a/tests/mocha.opts b/tests/mocha.opts new file mode 100644 index 00000000..f98c0424 --- /dev/null +++ b/tests/mocha.opts @@ -0,0 +1,4 @@ +--recursive +--reporter spec +--timeout 5000 +--slow 200 From 910d8c4915a3b5f2531382089b018b612a577ec6 Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Sat, 20 Sep 2014 12:52:23 +0800 Subject: [PATCH 05/21] tweaks tests --- tests/02.operations/12.lighten.js | 7 +++--- tests/02.operations/13.darken.js | 2 +- tests/05.stress/index.js | 42 ++++++++++++++++++++++++++++--- tests/utils.js | 3 ++- 4 files changed, 46 insertions(+), 8 deletions(-) diff --git a/tests/02.operations/12.lighten.js b/tests/02.operations/12.lighten.js index 951347fc..72b0945c 100644 --- a/tests/02.operations/12.lighten.js +++ b/tests/02.operations/12.lighten.js @@ -18,7 +18,7 @@ describe('lwip.lighten', function() { }); beforeEach(function(done) { - lwip.open(imgs.jpg.rgb, function(err, img) { + lwip.open(imgs.png.trans, function(err, img) { image = img; done(err); }); @@ -29,8 +29,9 @@ describe('lwip.lighten', function() { }); afterEach(function(done) { - image.writeFile(join(tmpDir, current.join('_') + '.jpg'), 'jpeg', { - quality: 100 + image.writeFile(join(tmpDir, current.join('_') + '.png'), 'png', { + compression: 'fast', + interlaced: true }, done); }); diff --git a/tests/02.operations/13.darken.js b/tests/02.operations/13.darken.js index a7f8976c..52d5ee76 100644 --- a/tests/02.operations/13.darken.js +++ b/tests/02.operations/13.darken.js @@ -18,7 +18,7 @@ describe('lwip.darken', function() { }); beforeEach(function(done) { - lwip.open(imgs.jpg.rgb, function(err, img) { + lwip.open(imgs.png.trans, function(err, img) { image = img; done(err); }); diff --git a/tests/05.stress/index.js b/tests/05.stress/index.js index 7c8587ae..32a47533 100644 --- a/tests/05.stress/index.js +++ b/tests/05.stress/index.js @@ -79,7 +79,7 @@ describe('stress tests', function() { }); }); - describe('rotate an image 30 times (up to 90degs)', function() { + describe('rotate an image 30 times (up to 90degs) (1)', function() { it('should succeed', function(done) { var a = 3; lwip.open(imgs.jpg.rgb, 'jpeg', function(err, image) { @@ -96,7 +96,25 @@ describe('stress tests', function() { }); }); - describe('25 random manipulations on one image', function() { + describe('rotate an image 30 times (up to 90degs) (2)', function() { + it('should succeed', function(done) { + var a = 3; + lwip.open(imgs.png.trans, 'png', function(err, image) { + if (err) return done(err); + async.timesSeries(50, function(i, done) { + image.rotate(a, utils.getRandomColor(), done); + }, function(err) { + if (err) return done(err); + image.writeFile(outpathPng, 'png', { + compression: 'fast', + interlaced: false + }, done); + }); + }); + }); + }); + + describe('25 random manipulations on one image (1)', function() { it('should succeed', function(done) { lwip.open(imgs.png.rgb, 'png', function(err, image) { if (err) return done(err); @@ -107,7 +125,25 @@ describe('stress tests', function() { }, function(err) { if (err) return done(err); var data = ops.join('\n'); - fs.writeFile(join(tmpDir, 'stress-25rnd.txt'), data, done); + fs.writeFile(join(tmpDir, 'stress-25rnd.jpg.txt'), data, done); + }); + }); + }); + }); + + describe('25 random manipulations on one image (2)', function() { + it('should succeed', function(done) { + lwip.open(imgs.png.trans, 'png', function(err, image) { + if (err) return done(err); + var batch = image.batch(); + var ops = utils.generateRandomBatch(batch, 25); + batch.writeFile(join(tmpDir, 'stress-25rnd.png'), 'png', { + compression: 'fast', + interlaced: false + }, function(err) { + if (err) return done(err); + var data = ops.join('\n'); + fs.writeFile(join(tmpDir, 'stress-25rnd.png.txt'), data, done); }); }); }); diff --git a/tests/utils.js b/tests/utils.js index 6f7d30aa..e368403d 100644 --- a/tests/utils.js +++ b/tests/utils.js @@ -81,6 +81,7 @@ function getRandomColor() { return { r: Math.floor(Math.random() * 256), g: Math.floor(Math.random() * 256), - b: Math.floor(Math.random() * 256) + b: Math.floor(Math.random() * 256), + a: Math.floor(Math.random() * 50) + 51 }; } From 6f6a02c828c9cf035e45f0ca4ccceb5a2bdb8e08 Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Sat, 20 Sep 2014 19:42:08 +0800 Subject: [PATCH 06/21] modified hslAdjust to hslaAdjust (with alpha) --- README.md | 79 ++++++++++--------- binding.gyp | 2 +- defs.js | 5 +- index.js | 22 +++--- src/image/{hsl_worker.cpp => hsla_worker.cpp} | 24 ++++-- src/image/image.cpp | 17 ++-- src/image/image.h | 10 ++- .../{15.hslAdjust.js => 15.hslaAdjust.js} | 30 ++++--- tests/03.safety/00.locks.js | 6 +- tests/03.safety/01.releases.js | 6 +- 10 files changed, 115 insertions(+), 86 deletions(-) rename src/image/{hsl_worker.cpp => hsla_worker.cpp} (83%) rename tests/02.operations/{15.hslAdjust.js => 15.hslaAdjust.js} (65%) diff --git a/README.md b/README.md index bfb07f28..2ced5b4b 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ 0. [Installation](#installation) 0. [Usage](#usage) 0. [Supported formats](#supported-formats) + 0. [Colors specification](#colors-specification) 0. [Note on transparent images](#note-on-transparent-images) 0. [API](#api) 0. [Open an image from file or buffer](#open-an-image) @@ -25,12 +26,11 @@ 0. [Adjust saturation](#saturate) 0. Adjust lightness: [lighten](#lighten) / [darken](#darken) 0. [Adjust hue](#hue) - 0. [Set alpha channel](#set-alpha-channel) + 0. [Fade (adjust transparency)](#fade) 0. [Opacify](#opacify) 0. [Getters](#getters) 0. [Width](#width) 0. [Height](#height) - 0. [Transparency](#transparency) 0. [Get as a Buffer](#get-as-a-buffer) 0. [JPEG](#jpeg) 0. [PNG](#png) @@ -135,12 +135,33 @@ Other formats may also be supported in the future, but are probably less urgent. Check the issues to see [which formats are planned to be supported](https://github.com/EyalAr/lwip/issues?labels=format+request&page=1&state=open). Open an issue if you need support for a format which is not already listed. +### Colors specification + +In LWIP colors are coded as RGBA values (red, green, blue and an alpha channel). + +Colors are specified in one of three ways: + +- As a string. possible values: + - `"black" // {r: 0, g: 0, b: 0, a: 100}` + - `"white" // {r: 255, g: 255, b: 255, a: 100}` + - `"gray" // {r: 128, g: 128, b: 128, a: 100}` + - `"red" // {r: 255, g: 0, b: 0, a: 100}` + - `"green" // {r: 0, g: 255, b: 0, a: 100}` + - `"blue" // {r: 0, g: 0, b: 255, a: 100}` + - `"yellow" // {r: 255, g: 255, b: 0, a: 100}` + - `"cyan" // {r: 0, g: 255, b: 255, a: 100}` + - `"magenta" // {r: 255, g: 0, b: 255, a: 100}` +- As an array `[R, G, B, A]` where `R`, `G` and `B` are integers between 0 and + 255 and `A` is an integer between 0 and 100. +- As an object `{r: R, g: G, b: B, a: A}` where `R`, `G` and `B` are integers + between 0 and 255 and `A` is an integer between 0 and 100. + ### Note on transparent images 0. Transparency is supported through an alpha channel. 0. Note that not all formats support transparency. If an image with an alpha channel is encoded with a format which does not support transparency, the - alpha channel will be set to 100% for all pixels. + alpha channel will ignored (effectively setting it to 100% for all pixels). ## API @@ -223,13 +244,8 @@ fs.readFile('path/to/image.png', function(err, buffer){ `image.rotate(degs, color, callback)` 0. `degs {Float}`: Clockwise rotation degrees. -0. `color {String / Array / Object}`: **Optional** Color of the canvas. - - As a string, possible values: `"black"`, `"white"`, `"gray"`, `"blue"`, - `"red"`, `"green"`, `"yellow"`, `"cyan"`, `"magenta"`. - - As an array `[R, G, B]` where `R`, `G` and `B` are integers between 0 and - 255. - - As an object `{r: R, g: G, b: B}` where `R`, `G` and `B` are integers - between 0 and 255. +0. `color {String / Array / Object}`: **Optional** Color of the canvas. See + [colors specification](#colors-specification). 0. `callback {Function(err, image)}` #### Crop @@ -287,13 +303,8 @@ Add a colored border to the image. `image.border(width, color, callback)` 0. `width {Integer}`: Border width in pixels. -0. `color {String / Array / Object}`: **Optional** Color of the border. - - As a string, possible values: `"black"`, `"white"`, `"gray"`, `"blue"`, - `"red"`, `"green"`, `"yellow"`, `"cyan"`, `"magenta"`. - - As an array `[R, G, B]` where `R`, `G` and `B` are integers between 0 and - 255. - - As an object `{r: R, g: G, b: B}` where `R`, `G` and `B` are integers - between 0 and 255. +0. `color {String / Array / Object}`: **Optional** Color of the border. See + [colors specification](#colors-specification). 0. `callback {Function(err, image)}` #### Pad @@ -303,13 +314,8 @@ Pad image edges with colored pixels. `image.pad(left, top, right, bottom, color, callback)` 0. `left, top, right, bottom {Integer}`: Number of pixels to add to each edge. -0. `color {String / Array / Object}`: **Optional** Color of the padding. - - As a string, possible values: `"black"`, `"white"`, `"gray"`, `"blue"`, - `"red"`, `"green"`, `"yellow"`, `"cyan"`, `"magenta"`. - - As an array `[R, G, B]` where `R`, `G` and `B` are integers between 0 and - 255. - - As an object `{r: R, g: G, b: B}` where `R`, `G` and `B` are integers - between 0 and 255. +0. `color {String / Array / Object}`: **Optional** Color of the padding. See + [colors specification](#colors-specification). 0. `callback {Function(err, image)}` #### Saturate @@ -369,17 +375,23 @@ Adjust image hue. **Note:** The hue is shifted in a circular manner in the range [0,360] for each pixel individually. -#### Set alpha channel +#### Fade -Globally set the alpha channel for all pixels. +Adjust image transperancy. -`image.setAlpha(alpha, callback)` +`image.fade(delta, callback)` -0. `alpha {Integer}`: In the range [0,100] where 100 is completely opaque and - 0 is completely transparent. +0. `delta {Float}`: By how much to increase / decrease the transperancy. 0. `callback {Function(err, image)}` -**Note**: Previous pixel's alpha channel values will be overwritten. +**Note:** The alpha channel is adjusted independently for each pixel. + +**Examples**: + +0. `image.fade(0, ...)` will have no effect on the image. +0. `image.fade(0.5, ...)` will increase the transparency by 50%. +0. `image.fade(1, ...)` will make the image completely transparent. +0. `image.fade(-1, ...)` will make the image completely opaque. #### Opacify @@ -390,7 +402,7 @@ to 100%. 0. `callback {Function(err, image)}` -**Note**: Equivalent to `image.setAlpha(100, ...)`. +**Note**: Equivalent to `image.fade(-1, ...)`. ### Getters @@ -402,11 +414,6 @@ to 100%. `image.height()` returns the image's height in pixels. -#### Transparency - -`image.transparency()` returns `true` / `false` depending whether the image has -transparent components. - #### Get as a Buffer Get encoded binary image data as a NodeJS diff --git a/binding.gyp b/binding.gyp index a686abab..a6c6b681 100644 --- a/binding.gyp +++ b/binding.gyp @@ -206,7 +206,7 @@ "src/image/mirror_worker.cpp", "src/image/pad_worker.cpp", "src/image/sharpen_worker.cpp", - "src/image/hsl_worker.cpp", + "src/image/hsla_worker.cpp", ], 'include_dirs': [ ' * cimg, NanCallback * callback -): NanAsyncWorker(callback), _hs(hs), _sd(sd), _ld(ld), _cimg(cimg) {} +): NanAsyncWorker(callback), _hs(hs), _sd(sd), _ld(ld), _ad(ad), _cimg(cimg) {} -HSLWorker::~HSLWorker() {} +HSLAWorker::~HSLAWorker() {} -void HSLWorker::Execute () { - if (_hs == 0 && _sd == 0 && _ld == 0) return; +void HSLAWorker::Execute () { + if (_hs == 0 && _sd == 0 && _ld == 0 && _ad == 0) return; try { cimg_forXY(*_cimg, x, y) { unsigned char r = (*_cimg)(x, y, 0, 0), g = (*_cimg)(x, y, 0, 1), b = (*_cimg)(x, y, 0, 2), a = (*_cimg)(x, y, 0, 3); - float h, s, l; + float h, s, l, af = (float) a; rgb_to_hsl(r, g, b, &h, &s, &l); if (_hs != 0) { @@ -38,18 +39,25 @@ void HSLWorker::Execute () { if (l < 0) l = 0; } + if (_ad != 0) { + af *= 1.0 + _ad; + if (af > 255) af = 255; + if (af < 0) af = 0; + } + hsl_to_rgb(h, s, l, &r, &g, &b); + a = (unsigned char) af; _cimg->fillC(x, y, 0, r, g, b, a); } } catch (CImgException e) { - SetErrorMessage("Unable to modify HSL"); + SetErrorMessage("Unable to modify HSLA"); return; } return; } -void HSLWorker::HandleOKCallback () { +void HSLAWorker::HandleOKCallback () { NanScope(); Local argv[] = { NanNull() diff --git a/src/image/image.cpp b/src/image/image.cpp index 9fb705f2..aa108589 100644 --- a/src/image/image.cpp +++ b/src/image/image.cpp @@ -17,7 +17,7 @@ void LwipImage::Init(Handle exports) { NODE_SET_PROTOTYPE_METHOD(tpl, "mirror", mirror); NODE_SET_PROTOTYPE_METHOD(tpl, "pad", pad); NODE_SET_PROTOTYPE_METHOD(tpl, "sharpen", sharpen); - NODE_SET_PROTOTYPE_METHOD(tpl, "hslAdj", hslAdj); + NODE_SET_PROTOTYPE_METHOD(tpl, "hslaAdj", hslaAdj); NanAssignPersistent(constructor, tpl); exports->Set( NanNew("LwipImage"), @@ -296,27 +296,30 @@ NAN_METHOD(LwipImage::sharpen) { NanReturnUndefined(); } -// image.hslAdj(hd, sd, ld, callback): -// ----------------------------------- +// image.hslaAdj(hd, sd, ld, callback): +// ------------------------------------ // args[0] - hue delta // args[1] - saturation delta // args[2] - lightness delta -// args[3] - callback -NAN_METHOD(LwipImage::hslAdj) { +// args[3] - alpha delta +// args[4] - callback +NAN_METHOD(LwipImage::hslaAdj) { NanScope(); float hd = (float) args[0].As()->Value(); float sd = (float) args[1].As()->Value(); float ld = (float) args[2].As()->Value(); - NanCallback * callback = new NanCallback(args[3].As()); + float ad = (float) args[3].As()->Value(); + NanCallback * callback = new NanCallback(args[4].As()); CImg * cimg = ObjectWrap::Unwrap(args.This())->_cimg; NanAsyncQueueWorker( - new HSLWorker( + new HSLAWorker( hd, sd, ld, + ad, cimg, callback ) diff --git a/src/image/image.h b/src/image/image.h index dadfac98..438d901f 100644 --- a/src/image/image.h +++ b/src/image/image.h @@ -37,7 +37,7 @@ class LwipImage : public node::ObjectWrap { static NAN_METHOD(mirror); static NAN_METHOD(pad); static NAN_METHOD(sharpen); - static NAN_METHOD(hslAdj); + static NAN_METHOD(hslaAdj); static NAN_METHOD(width); static NAN_METHOD(height); static NAN_METHOD(buffer); @@ -187,22 +187,24 @@ class SharpenWorker : public NanAsyncWorker { CImg * _cimg; }; -class HSLWorker : public NanAsyncWorker { +class HSLAWorker : public NanAsyncWorker { public: - HSLWorker( + HSLAWorker( float hs, float sd, float ld, + float ad, CImg * cimg, NanCallback * callback ); - ~HSLWorker(); + ~HSLAWorker(); void Execute (); void HandleOKCallback (); private: float _hs; float _sd; float _ld; + float _ad; CImg * _cimg; }; diff --git a/tests/02.operations/15.hslAdjust.js b/tests/02.operations/15.hslaAdjust.js similarity index 65% rename from tests/02.operations/15.hslAdjust.js rename to tests/02.operations/15.hslaAdjust.js index 0e6ebd82..24699185 100644 --- a/tests/02.operations/15.hslAdjust.js +++ b/tests/02.operations/15.hslaAdjust.js @@ -6,10 +6,10 @@ var join = require('path').join, imgs = require('../imgs'); var tmpDir = join(__dirname, '../results'), - basename = 'hslAdjust', + basename = 'hslaAdjust', current; -describe('lwip.hslAdjust', function() { +describe('lwip.hslaAdjust', function() { var image; @@ -18,7 +18,7 @@ describe('lwip.hslAdjust', function() { }); beforeEach(function(done) { - lwip.open(imgs.png.rgb, function(err, img) { + lwip.open(imgs.png.trans, function(err, img) { image = img; done(err); }); @@ -32,39 +32,45 @@ describe('lwip.hslAdjust', function() { image.writeFile(join(tmpDir, current.join('_') + '.png'), 'png', done); }); - describe('hs=0, sd=0, ld=0', function() { + describe('hs=0, sd=0, ld=0, td=0', function() { var hs = 0, sd = 0, - ld = 0; + ld = 0, + td = 0; it('should succeed', function(done) { current.push('hs' + hs); current.push('sd' + sd); current.push('ld' + ld); - image.hslAdjust(hs, sd, ld, done); + current.push('td' + td); + image.hslaAdjust(hs, sd, ld, td, done); }); }); - describe('hs=50, sd=0.3, ld=0.4', function() { + describe('hs=50, sd=0.3, ld=0.4, td=0.5', function() { var hs = 50, sd = 0.3, - ld = 0.4; + ld = 0.4, + td = 0.5; it('should succeed', function(done) { current.push('hs' + hs); current.push('sd' + sd); current.push('ld' + ld); - image.hslAdjust(hs, sd, ld, done); + current.push('td' + td); + image.hslaAdjust(hs, sd, ld, td, done); }); }); - describe('hs=-50, sd=-0.3, ld=-0.4', function() { + describe('hs=-50, sd=-0.3, ld=-0.4, td=-1', function() { var hs = -50, sd = -0.3, - ld = -0.4; + ld = -0.4, + td = -1; it('should succeed', function(done) { current.push('hs' + hs); current.push('sd' + sd); current.push('ld' + ld); - image.hslAdjust(hs, sd, ld, done); + current.push('td' + td); + image.hslaAdjust(hs, sd, ld, td, done); }); }); diff --git a/tests/03.safety/00.locks.js b/tests/03.safety/00.locks.js index a098b245..7b5960f9 100644 --- a/tests/03.safety/00.locks.js +++ b/tests/03.safety/00.locks.js @@ -101,13 +101,13 @@ describe('simultaneous operations locks', function() { describe('image.hue lock', function() { it('should lock image', function() { image.hue.bind(image, 10, function() {}).should.not.throwError(); - image.hslAdjust.bind(image, 100, 1, 1, function() {}).should.throwError(); + image.hslaAdjust.bind(image, 100, 1, 1, 1, function() {}).should.throwError(); }); }); - describe('image.hslAdjust lock', function() { + describe('image.hslaAdjust lock', function() { it('should lock image', function() { - image.hslAdjust.bind(image, 100, 1, 1, function() {}).should.not.throwError(); + image.hslaAdjust.bind(image, 100, 1, 1, 0, function() {}).should.not.throwError(); image.resize.bind(image, 100, 100, function() {}).should.throwError(); }); }); diff --git a/tests/03.safety/01.releases.js b/tests/03.safety/01.releases.js index 782487d2..35541809 100644 --- a/tests/03.safety/01.releases.js +++ b/tests/03.safety/01.releases.js @@ -101,13 +101,13 @@ describe('failed ops lock release', function() { describe('image.hue release', function() { it('should release image lock', function() { image.hue.bind(image, 'foo', function() {}).should.throwError(); - image.hslAdjust.bind(image, 100, 1, 1, function() {}).should.not.throwError(); + image.hslaAdjust.bind(image, 100, 1, 1, 0, function() {}).should.not.throwError(); }); }); - describe('image.hslAdjust release', function() { + describe('image.hslaAdjust release', function() { it('should release image lock', function() { - image.hslAdjust.bind(image, 'foo', 'foo', 'foo', function() {}).should.throwError(); + image.hslaAdjust.bind(image, 'foo', 'foo', 'foo', 'foo', function() {}).should.throwError(); image.resize.bind(image, 100, 100, function() {}).should.not.throwError(); }); }); From 0c56c1b9d59cd5fea0a473fc14e48afeb628b7cb Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Sun, 21 Sep 2014 02:03:44 +0800 Subject: [PATCH 07/21] finished transparency support. closes #30 --- binding.gyp | 1 + defs.js | 11 +++ examples/fade.js | 16 ++++ index.js | 56 ++++++++++++- src/image/hsla_worker.cpp | 6 +- src/image/image.cpp | 21 +++++ src/image/image.h | 14 ++++ src/image/opacify_worker.cpp | 31 ++++++++ tests/00.argsValidation/004.image.toBuffer.js | 8 ++ .../00.argsValidation/005.image.writeFile.js | 8 ++ tests/00.argsValidation/103.batch.rotate.js | 15 +++- tests/00.argsValidation/107.batch.pad.js | 6 ++ tests/02.operations/08.pad.js | 22 +++++- tests/02.operations/15.hslaAdjust.js | 24 +++--- tests/02.operations/16.fade.js | 78 +++++++++++++++++++ tests/02.operations/16.opacify.js | 36 +++++++++ tests/03.safety/00.locks.js | 14 ++++ tests/03.safety/01.releases.js | 14 ++++ tests/utils.js | 11 ++- 19 files changed, 366 insertions(+), 26 deletions(-) create mode 100644 examples/fade.js create mode 100644 src/image/opacify_worker.cpp create mode 100644 tests/02.operations/16.fade.js create mode 100644 tests/02.operations/16.opacify.js diff --git a/binding.gyp b/binding.gyp index a6c6b681..d9b227f3 100644 --- a/binding.gyp +++ b/binding.gyp @@ -207,6 +207,7 @@ "src/image/pad_worker.cpp", "src/image/sharpen_worker.cpp", "src/image/hsla_worker.cpp", + "src/image/opacify_worker.cpp", ], 'include_dirs': [ ' 0) that.__trans = true; + that.__release(); + callback(err, that); + }); + }); + } catch (e) { + this.__release(); + throw e; + } + } + + image.prototype.opacify = function() { + this.__lock(); + try { + var that = this; + decree(defs.args.opacify)(arguments, function(callback) { + that.__lwip.opacify(function(err) { + that.__trans = false; + that.__release(); + callback(err, that); + }); + }); + } catch (e) { + this.__release(); + throw e; + } + } + image.prototype.hue = function() { this.__lock(); try { @@ -473,8 +507,8 @@ batch.prototype.hslaAdjust = function() { var that = this, decs = defs.args.hslaAdjust.slice(0, -1); // cut callback declaration - decree(decs)(arguments, function(hs, sd, ld, td) { - that.__addOp(that.__image.hslaAdjust, [hs, sd, ld, td].filter(undefinedFilter)); + decree(decs)(arguments, function(hs, sd, ld, ad) { + that.__addOp(that.__image.hslaAdjust, [hs, sd, ld, ad].filter(undefinedFilter)); }); return this; } @@ -506,6 +540,20 @@ return this; } + batch.prototype.fade = function() { + var that = this, + decs = defs.args.fade.slice(0, -1); // cut callback declaration + decree(decs)(arguments, function(delta) { + that.__addOp(that.__image.fade, [delta].filter(undefinedFilter)); + }); + return this; + } + + batch.prototype.opacify = function() { + this.__addOp(this.__image.opacify, []); + return this; + } + batch.prototype.hue = function() { var that = this, decs = defs.args.hue.slice(0, -1); // cut callback declaration diff --git a/src/image/hsla_worker.cpp b/src/image/hsla_worker.cpp index 1887b787..89ab1d70 100644 --- a/src/image/hsla_worker.cpp +++ b/src/image/hsla_worker.cpp @@ -36,13 +36,13 @@ void HSLAWorker::Execute () { if (_ld != 0) { l *= 1.0 + _ld; if (l > 1) l = 1; - if (l < 0) l = 0; + else if (l < 0) l = 0; } if (_ad != 0) { af *= 1.0 + _ad; - if (af > 255) af = 255; - if (af < 0) af = 0; + if (af > 100) af = 100; + else if (af < 0) af = 0; } hsl_to_rgb(h, s, l, &r, &g, &b); diff --git a/src/image/image.cpp b/src/image/image.cpp index aa108589..60ae090b 100644 --- a/src/image/image.cpp +++ b/src/image/image.cpp @@ -18,6 +18,7 @@ void LwipImage::Init(Handle exports) { NODE_SET_PROTOTYPE_METHOD(tpl, "pad", pad); NODE_SET_PROTOTYPE_METHOD(tpl, "sharpen", sharpen); NODE_SET_PROTOTYPE_METHOD(tpl, "hslaAdj", hslaAdj); + NODE_SET_PROTOTYPE_METHOD(tpl, "opacify", opacify); NanAssignPersistent(constructor, tpl); exports->Set( NanNew("LwipImage"), @@ -327,3 +328,23 @@ NAN_METHOD(LwipImage::hslaAdj) { NanReturnUndefined(); } + +// image.opacify(callback): +// ------------------------------------ + +// args[0] - callback +NAN_METHOD(LwipImage::opacify) { + NanScope(); + + NanCallback * callback = new NanCallback(args[0].As()); + CImg * cimg = ObjectWrap::Unwrap(args.This())->_cimg; + + NanAsyncQueueWorker( + new OpacifyWorker( + cimg, + callback + ) + ); + + NanReturnUndefined(); +} diff --git a/src/image/image.h b/src/image/image.h index 438d901f..54c443c1 100644 --- a/src/image/image.h +++ b/src/image/image.h @@ -38,6 +38,7 @@ class LwipImage : public node::ObjectWrap { static NAN_METHOD(pad); static NAN_METHOD(sharpen); static NAN_METHOD(hslaAdj); + static NAN_METHOD(opacify); static NAN_METHOD(width); static NAN_METHOD(height); static NAN_METHOD(buffer); @@ -208,6 +209,19 @@ class HSLAWorker : public NanAsyncWorker { CImg * _cimg; }; +class OpacifyWorker : public NanAsyncWorker { +public: + OpacifyWorker( + CImg * cimg, + NanCallback * callback + ); + ~OpacifyWorker(); + void Execute (); + void HandleOKCallback (); +private: + CImg * _cimg; +}; + void rgb_to_hsl(unsigned char r, unsigned char g, unsigned char b, float * h, float * s, float * l); void hsl_to_rgb(float h, float s, float l, unsigned char * r, unsigned char * g, diff --git a/src/image/opacify_worker.cpp b/src/image/opacify_worker.cpp new file mode 100644 index 00000000..3e7290dd --- /dev/null +++ b/src/image/opacify_worker.cpp @@ -0,0 +1,31 @@ +#include "image.h" + +OpacifyWorker::OpacifyWorker( + CImg * cimg, + NanCallback * callback +): NanAsyncWorker(callback), _cimg(cimg) {} + +OpacifyWorker::~OpacifyWorker() {} + +void OpacifyWorker::Execute () { + try { + cimg_forXY(*_cimg, x, y) { + unsigned char r = (*_cimg)(x, y, 0, 0), + g = (*_cimg)(x, y, 0, 1), + b = (*_cimg)(x, y, 0, 2); + _cimg->fillC(x, y, 0, r, g, b, 100); + } + } catch (CImgException e) { + SetErrorMessage("Unable to modify HSLA"); + return; + } + return; +} + +void OpacifyWorker::HandleOKCallback () { + NanScope(); + Local argv[] = { + NanNull() + }; + callback->Call(1, argv); +} diff --git a/tests/00.argsValidation/004.image.toBuffer.js b/tests/00.argsValidation/004.image.toBuffer.js index 3bed60ad..9f0c1ecd 100644 --- a/tests/00.argsValidation/004.image.toBuffer.js +++ b/tests/00.argsValidation/004.image.toBuffer.js @@ -50,6 +50,14 @@ describe('image.toBuffer arguments validation', function() { }); }); + describe('invalid transparency', function() { + it('should throw an error', function() { + image.toBuffer.bind(image, 'png', { + transparency: 'foo' + }, function() {}).should.throwError(); + }); + }); + }); }); diff --git a/tests/00.argsValidation/005.image.writeFile.js b/tests/00.argsValidation/005.image.writeFile.js index a3afbe28..7adf8442 100644 --- a/tests/00.argsValidation/005.image.writeFile.js +++ b/tests/00.argsValidation/005.image.writeFile.js @@ -50,5 +50,13 @@ describe('image.writeFile arguments validation', function() { }); }); + describe('invalid transparency', function() { + it('should throw an error', function() { + image.writeFile.bind(image, 'res.jpg', 'png', { + transparency: 'foo' + }, function() {}).should.throwError(); + }); + }); + }); }); diff --git a/tests/00.argsValidation/103.batch.rotate.js b/tests/00.argsValidation/103.batch.rotate.js index 57f7d3ea..97216f78 100644 --- a/tests/00.argsValidation/103.batch.rotate.js +++ b/tests/00.argsValidation/103.batch.rotate.js @@ -28,7 +28,8 @@ describe('batch.rotate arguments validation', function() { batch.rotate.bind(batch, 5, { r: -5, g: -8, - b: -1000 + b: -1000, + a: -200 }).should.throwError(); }); }); @@ -59,19 +60,25 @@ describe('batch.rotate arguments validation', function() { describe('invalid color array (5)', function() { it('should throw an error', function() { - batch.rotate.bind(batch, 5, [1000, 100, 100]).should.throwError(); + batch.rotate.bind(batch, 5, [1000, 100, 100, 100]).should.throwError(); }); }); describe('invalid color array (6)', function() { it('should throw an error', function() { - batch.rotate.bind(batch, 5, [100, 1000, 100]).should.throwError(); + batch.rotate.bind(batch, 5, [100, 1000, 100, 100]).should.throwError(); }); }); describe('invalid color array (7)', function() { it('should throw an error', function() { - batch.rotate.bind(batch, 5, [100, 100, 1000]).should.throwError(); + batch.rotate.bind(batch, 5, [100, 100, 1000, 100]).should.throwError(); + }); + }); + + describe('invalid color array (8)', function() { + it('should throw an error', function() { + batch.rotate.bind(batch, 5, [100, 100, 100, 1000]).should.throwError(); }); }); diff --git a/tests/00.argsValidation/107.batch.pad.js b/tests/00.argsValidation/107.batch.pad.js index 2bdf2633..61bfb484 100644 --- a/tests/00.argsValidation/107.batch.pad.js +++ b/tests/00.argsValidation/107.batch.pad.js @@ -75,6 +75,12 @@ describe('batch.pad arguments validation', function() { }); }); + describe('invalid color array (8)', function() { + it('should throw an error', function() { + batch.pad.bind(batch, 5, 5, 5, 5, [100, 100, 100, 1000]).should.throwError(); + }); + }); + describe('invalid color string', function() { it('should throw an error', function() { batch.pad.bind(batch, 5, 5, 5, 5, 'foo').should.throwError(); diff --git a/tests/02.operations/08.pad.js b/tests/02.operations/08.pad.js index 210b6c99..f9fa3514 100644 --- a/tests/02.operations/08.pad.js +++ b/tests/02.operations/08.pad.js @@ -49,7 +49,21 @@ describe('lwip.pad', function() { height = 333 + 30 + 80; it('image should have the correct size', function(done) { current.push('l10_t30_r50_b80_[120,50,200]'); - image.pad(10, 30, 50, 80, [120,50,200], function(err, im) { + image.pad(10, 30, 50, 80, [120, 50, 200], function(err, im) { + if (err) return done(err); + assert(im.width() === width); + assert(im.height() === height); + done(); + }); + }); + }); + + describe('left-10, top-30, right-50, bottom-80, color-[20,150,20,80]', function() { + var width = 500 + 10 + 50, + height = 333 + 30 + 80; + it('image should have the correct size', function(done) { + current.push('l10_t30_r50_b80_[20,150,20,80]'); + image.pad(10, 30, 50, 80, [20, 150, 20, 80], function(err, im) { if (err) return done(err); assert(im.width() === width); assert(im.height() === height); @@ -63,7 +77,11 @@ describe('lwip.pad', function() { height = 333 + 15 + 40; it('image should have the correct size', function(done) { current.push('l1_t15_r25_b40_red120_grn50_blu200'); - image.pad(5, 15, 25, 40, {r:120,g:50,b:200}, function(err, im) { + image.pad(5, 15, 25, 40, { + r: 120, + g: 50, + b: 200 + }, function(err, im) { if (err) return done(err); assert(im.width() === width); assert(im.height() === height); diff --git a/tests/02.operations/15.hslaAdjust.js b/tests/02.operations/15.hslaAdjust.js index 24699185..d5b86a05 100644 --- a/tests/02.operations/15.hslaAdjust.js +++ b/tests/02.operations/15.hslaAdjust.js @@ -32,45 +32,45 @@ describe('lwip.hslaAdjust', function() { image.writeFile(join(tmpDir, current.join('_') + '.png'), 'png', done); }); - describe('hs=0, sd=0, ld=0, td=0', function() { + describe('hs=0, sd=0, ld=0, ad=0', function() { var hs = 0, sd = 0, ld = 0, - td = 0; + ad = 0; it('should succeed', function(done) { current.push('hs' + hs); current.push('sd' + sd); current.push('ld' + ld); - current.push('td' + td); - image.hslaAdjust(hs, sd, ld, td, done); + current.push('ad' + ad); + image.hslaAdjust(hs, sd, ld, ad, done); }); }); - describe('hs=50, sd=0.3, ld=0.4, td=0.5', function() { + describe('hs=50, sd=0.3, ld=0.4, ad=0.5', function() { var hs = 50, sd = 0.3, ld = 0.4, - td = 0.5; + ad = 0.5; it('should succeed', function(done) { current.push('hs' + hs); current.push('sd' + sd); current.push('ld' + ld); - current.push('td' + td); - image.hslaAdjust(hs, sd, ld, td, done); + current.push('ad' + ad); + image.hslaAdjust(hs, sd, ld, ad, done); }); }); - describe('hs=-50, sd=-0.3, ld=-0.4, td=-1', function() { + describe('hs=-50, sd=-0.3, ld=-0.4, ad=-1', function() { var hs = -50, sd = -0.3, ld = -0.4, - td = -1; + ad = -1; it('should succeed', function(done) { current.push('hs' + hs); current.push('sd' + sd); current.push('ld' + ld); - current.push('td' + td); - image.hslaAdjust(hs, sd, ld, td, done); + current.push('ad' + ad); + image.hslaAdjust(hs, sd, ld, ad, done); }); }); diff --git a/tests/02.operations/16.fade.js b/tests/02.operations/16.fade.js new file mode 100644 index 00000000..f588c687 --- /dev/null +++ b/tests/02.operations/16.fade.js @@ -0,0 +1,78 @@ +var join = require('path').join, + should = require('should'), + assert = require('assert'), + mkdirp = require('mkdirp'), + lwip = require('../../'), + imgs = require('../imgs'); + +var tmpDir = join(__dirname, '../results'), + basename = 'fade', + current; + +describe('lwip.fade', function() { + + var image; + + before(function(done) { + mkdirp(tmpDir, done); + }); + + beforeEach(function(done) { + lwip.open(imgs.png.trans, function(err, img) { + image = img; + done(err); + }); + }); + + beforeEach(function() { + current = [basename]; + }); + + afterEach(function(done) { + image.writeFile(join(tmpDir, current.join('_') + '.png'), 'png', { + compression: 'fast', + interlaced: true + }, done); + }); + + describe('delta=0', function() { + var d = 0; + it('should succeed', function(done) { + current.push('d' + d); + image.fade(d, done); + }); + }); + + describe('delta=0.5', function() { + var d = 0.5; + it('should succeed', function(done) { + current.push('d' + d); + image.fade(d, done); + }); + }); + + describe('delta=-0.5', function() { + var d = -0.5; + it('should succeed', function(done) { + current.push('d' + d); + image.fade(d, done); + }); + }); + + describe('delta=1', function() { + var d = 1; + it('should succeed', function(done) { + current.push('d' + d); + image.fade(d, done); + }); + }); + + describe('delta=-1', function() { + var d = -1; + it('should succeed', function(done) { + current.push('d' + d); + image.fade(d, done); + }); + }); + +}); diff --git a/tests/02.operations/16.opacify.js b/tests/02.operations/16.opacify.js new file mode 100644 index 00000000..0d9676e3 --- /dev/null +++ b/tests/02.operations/16.opacify.js @@ -0,0 +1,36 @@ +var join = require('path').join, + should = require('should'), + assert = require('assert'), + mkdirp = require('mkdirp'), + lwip = require('../../'), + imgs = require('../imgs'); + +var tmpDir = join(__dirname, '../results'); + +describe('lwip.opacify', function() { + + var image; + + before(function(done) { + mkdirp(tmpDir, done); + }); + + beforeEach(function(done) { + lwip.open(imgs.png.trans, function(err, img) { + image = img; + done(err); + }); + }); + + afterEach(function(done) { + image.writeFile(join(tmpDir, 'opacify.png'), 'png', { + compression: 'fast', + interlaced: true + }, done); + }); + + it('should succeed', function(done) { + image.opacify(done); + }); + +}); diff --git a/tests/03.safety/00.locks.js b/tests/03.safety/00.locks.js index 7b5960f9..d88d87d6 100644 --- a/tests/03.safety/00.locks.js +++ b/tests/03.safety/00.locks.js @@ -101,6 +101,20 @@ describe('simultaneous operations locks', function() { describe('image.hue lock', function() { it('should lock image', function() { image.hue.bind(image, 10, function() {}).should.not.throwError(); + image.fade.bind(image, 1, function() {}).should.throwError(); + }); + }); + + describe('image.fade lock', function() { + it('should lock image', function() { + image.fade.bind(image, 1, function() {}).should.not.throwError(); + image.opacify.bind(image, function() {}).should.throwError(); + }); + }); + + describe('image.opacify lock', function() { + it('should lock image', function() { + image.opacify.bind(image, function() {}).should.not.throwError(); image.hslaAdjust.bind(image, 100, 1, 1, 1, function() {}).should.throwError(); }); }); diff --git a/tests/03.safety/01.releases.js b/tests/03.safety/01.releases.js index 35541809..d7045953 100644 --- a/tests/03.safety/01.releases.js +++ b/tests/03.safety/01.releases.js @@ -101,6 +101,20 @@ describe('failed ops lock release', function() { describe('image.hue release', function() { it('should release image lock', function() { image.hue.bind(image, 'foo', function() {}).should.throwError(); + image.fade.bind(image, 1, function() {}).should.not.throwError(); + }); + }); + + describe('image.fade release', function() { + it('should release image lock', function() { + image.fade.bind(image, 'foo', function() {}).should.throwError(); + image.opacify.bind(image, function() {}).should.not.throwError(); + }); + }); + + describe('image.opacify release', function() { + it('should release image lock', function() { + image.opacify.bind(image, 'foo').should.throwError(); image.hslaAdjust.bind(image, 100, 1, 1, 0, function() {}).should.not.throwError(); }); }); diff --git a/tests/utils.js b/tests/utils.js index e368403d..2f9b44f0 100644 --- a/tests/utils.js +++ b/tests/utils.js @@ -6,7 +6,7 @@ module.exports = { function generateRandomBatch(batch, n) { var ops = []; for (var i = 0; i < n; i++) { - var r = Math.floor(Math.random() * 12); + var r = Math.floor(Math.random() * 14); switch (r) { case 0: var sd = Math.floor(Math.random() * 20); @@ -72,6 +72,15 @@ function generateRandomBatch(batch, n) { batch = batch.hue(s); ops.push('hue' + s); break; + case 12: + var d = Math.random() * 2 - 1; + batch = batch.fade(d); + ops.push('fad' + d.toFixed(2)); + break; + case 13: + batch = batch.opacify(); + ops.push('opc'); + break; } } return ops; From 988900cfdee72a11d62e861d51bf94f7a7e5531f Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Sun, 21 Sep 2014 02:08:03 +0800 Subject: [PATCH 08/21] Updates read me --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 52c9ca57..248bbcc0 100644 --- a/README.md +++ b/README.md @@ -391,7 +391,6 @@ Adjust image transperancy. 0. `image.fade(0, ...)` will have no effect on the image. 0. `image.fade(0.5, ...)` will increase the transparency by 50%. 0. `image.fade(1, ...)` will make the image completely transparent. -0. `image.fade(-1, ...)` will make the image completely opaque. #### Opacify @@ -402,8 +401,6 @@ to 100%. 0. `callback {Function(err, image)}` -**Note**: Equivalent to `image.fade(-1, ...)`. - ### Getters #### Width From 62120ca7b3252c13e194d7c550e57419ac936121 Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Sun, 21 Sep 2014 02:16:35 +0800 Subject: [PATCH 09/21] updates readme --- README.md | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 248bbcc0..44a6b4f9 100644 --- a/README.md +++ b/README.md @@ -142,15 +142,17 @@ In LWIP colors are coded as RGBA values (red, green, blue and an alpha channel). Colors are specified in one of three ways: - As a string. possible values: - - `"black" // {r: 0, g: 0, b: 0, a: 100}` - - `"white" // {r: 255, g: 255, b: 255, a: 100}` - - `"gray" // {r: 128, g: 128, b: 128, a: 100}` - - `"red" // {r: 255, g: 0, b: 0, a: 100}` - - `"green" // {r: 0, g: 255, b: 0, a: 100}` - - `"blue" // {r: 0, g: 0, b: 255, a: 100}` - - `"yellow" // {r: 255, g: 255, b: 0, a: 100}` - - `"cyan" // {r: 0, g: 255, b: 255, a: 100}` - - `"magenta" // {r: 255, g: 0, b: 255, a: 100}` + ```Javascript + "black" // {r: 0, g: 0, b: 0, a: 100} + "white" // {r: 255, g: 255, b: 255, a: 100} + "gray" // {r: 128, g: 128, b: 128, a: 100} + "red" // {r: 255, g: 0, b: 0, a: 100} + "green" // {r: 0, g: 255, b: 0, a: 100} + "blue" // {r: 0, g: 0, b: 255, a: 100} + "yellow" // {r: 255, g: 255, b: 0, a: 100} + "cyan" // {r: 0, g: 255, b: 255, a: 100} + "magenta" // {r: 255, g: 0, b: 255, a: 100} + ``` - As an array `[R, G, B, A]` where `R`, `G` and `B` are integers between 0 and 255 and `A` is an integer between 0 and 100. - As an object `{r: R, g: G, b: B, a: A}` where `R`, `G` and `B` are integers @@ -384,7 +386,7 @@ Adjust image transperancy. 0. `delta {Float}`: By how much to increase / decrease the transperancy. 0. `callback {Function(err, image)}` -**Note:** The alpha channel is adjusted independently for each pixel. +**Note:** The transparency is adjusted independently for each pixel. **Examples**: @@ -394,8 +396,7 @@ Adjust image transperancy. #### Opacify -Make image completely opaque. Effectively sets the alpha channel for each pixel -to 100%. +Make image completely opaque. `image.opacify(callback)` From 09b0719167088ad4fb2216f86ec8685ebd576fb1 Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Sun, 21 Sep 2014 02:19:28 +0800 Subject: [PATCH 10/21] updates read me --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 44a6b4f9..3f9ee00b 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,7 @@ In LWIP colors are coded as RGBA values (red, green, blue and an alpha channel). Colors are specified in one of three ways: - As a string. possible values: + ```Javascript "black" // {r: 0, g: 0, b: 0, a: 100} "white" // {r: 255, g: 255, b: 255, a: 100} @@ -153,6 +154,7 @@ Colors are specified in one of three ways: "cyan" // {r: 0, g: 255, b: 255, a: 100} "magenta" // {r: 255, g: 0, b: 255, a: 100} ``` + - As an array `[R, G, B, A]` where `R`, `G` and `B` are integers between 0 and 255 and `A` is an integer between 0 and 100. - As an object `{r: R, g: G, b: B, a: A}` where `R`, `G` and `B` are integers From 0e39ca7f46ce04513b33393b5e5c5d660677f14b Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Sun, 21 Sep 2014 23:05:15 +0800 Subject: [PATCH 11/21] updates read me --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3f9ee00b..d6c47126 100644 --- a/README.md +++ b/README.md @@ -160,12 +160,16 @@ Colors are specified in one of three ways: - As an object `{r: R, g: G, b: B, a: A}` where `R`, `G` and `B` are integers between 0 and 255 and `A` is an integer between 0 and 100. +**Note**: The `A` value (alpha channel) is always optional and defaults to +100 (completely opaque). + ### Note on transparent images -0. Transparency is supported through an alpha channel. -0. Note that not all formats support transparency. If an image with an alpha - channel is encoded with a format which does not support transparency, the - alpha channel will ignored (effectively setting it to 100% for all pixels). +0. Transparency is supported through an alpha channel which ranges between 0 + and 100. 0 is completely transparent and 100 is completely opaque. +0. Not all formats support transparency. If an image with an alpha channel is + encoded with a format which does not support transparency, the alpha channel + will be ignored (effectively setting it to 100% for all pixels). ## API From 53aac7e389d6cffe3cdf3fd52e6da7affa557a11 Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Wed, 24 Sep 2014 01:06:05 +0800 Subject: [PATCH 12/21] implements lwip.create. closes #41 --- README.md | 32 +++++++- defs.js | 16 ++++ index.js | 21 +++++- .../{000.open.js => 001.open.js} | 0 tests/00.argsValidation/002.create.js | 75 +++++++++++++++++++ ...01.image.resize.js => 101.image.resize.js} | 0 ...{002.image.scale.js => 102.image.scale.js} | 0 ...03.image.rotate.js => 103.image.rotate.js} | 0 ...mage.toBuffer.js => 104.image.toBuffer.js} | 0 ...ge.writeFile.js => 105.image.writeFile.js} | 0 ...06.image.mirror.js => 106.image.mirror.js} | 0 .../{007.image.pad.js => 107.image.pad.js} | 0 ...08.image.border.js => 108.image.border.js} | 0 ...01.batch.resize.js => 201.batch.resize.js} | 0 ...{102.batch.scale.js => 202.batch.scale.js} | 0 ...03.batch.rotate.js => 203.batch.rotate.js} | 0 ...atch.toBuffer.js => 204.batch.toBuffer.js} | 0 ...ch.writeFile.js => 205.batch.writeFile.js} | 0 ...06.batch.mirror.js => 206.batch.mirror.js} | 0 .../{107.batch.pad.js => 207.batch.pad.js} | 0 ...08.batch.border.js => 208.batch.border.js} | 0 .../02.operations/{00.open.js => 001.open.js} | 0 tests/02.operations/002.create.js | 55 ++++++++++++++ .../{01.writeFile.js => 101.writeFile.js} | 0 .../{02.resize.js => 102.resize.js} | 0 .../{03.scale.js => 103.scale.js} | 0 .../{04.rotate.js => 104.rotate.js} | 0 .../02.operations/{05.crop.js => 105.crop.js} | 0 .../02.operations/{06.blur.js => 106.blur.js} | 0 .../{07.mirror.js => 107.mirror.js} | 0 tests/02.operations/{08.pad.js => 108.pad.js} | 0 .../{09.border.js => 109.border.js} | 0 .../{10.sharpen.js => 110.sharpen.js} | 0 .../{11.saturate.js => 111.saturate.js} | 0 .../{12.lighten.js => 112.lighten.js} | 0 .../{13.darken.js => 113.darken.js} | 0 tests/02.operations/{14.hue.js => 114.hue.js} | 0 .../{15.hslaAdjust.js => 115.hslaAdjust.js} | 0 .../02.operations/{16.fade.js => 116.fade.js} | 0 .../{16.opacify.js => 116.opacify.js} | 0 40 files changed, 195 insertions(+), 4 deletions(-) rename tests/00.argsValidation/{000.open.js => 001.open.js} (100%) create mode 100644 tests/00.argsValidation/002.create.js rename tests/00.argsValidation/{001.image.resize.js => 101.image.resize.js} (100%) rename tests/00.argsValidation/{002.image.scale.js => 102.image.scale.js} (100%) rename tests/00.argsValidation/{003.image.rotate.js => 103.image.rotate.js} (100%) rename tests/00.argsValidation/{004.image.toBuffer.js => 104.image.toBuffer.js} (100%) rename tests/00.argsValidation/{005.image.writeFile.js => 105.image.writeFile.js} (100%) rename tests/00.argsValidation/{006.image.mirror.js => 106.image.mirror.js} (100%) rename tests/00.argsValidation/{007.image.pad.js => 107.image.pad.js} (100%) rename tests/00.argsValidation/{008.image.border.js => 108.image.border.js} (100%) rename tests/00.argsValidation/{101.batch.resize.js => 201.batch.resize.js} (100%) rename tests/00.argsValidation/{102.batch.scale.js => 202.batch.scale.js} (100%) rename tests/00.argsValidation/{103.batch.rotate.js => 203.batch.rotate.js} (100%) rename tests/00.argsValidation/{104.batch.toBuffer.js => 204.batch.toBuffer.js} (100%) rename tests/00.argsValidation/{105.batch.writeFile.js => 205.batch.writeFile.js} (100%) rename tests/00.argsValidation/{106.batch.mirror.js => 206.batch.mirror.js} (100%) rename tests/00.argsValidation/{107.batch.pad.js => 207.batch.pad.js} (100%) rename tests/00.argsValidation/{108.batch.border.js => 208.batch.border.js} (100%) rename tests/02.operations/{00.open.js => 001.open.js} (100%) create mode 100644 tests/02.operations/002.create.js rename tests/02.operations/{01.writeFile.js => 101.writeFile.js} (100%) rename tests/02.operations/{02.resize.js => 102.resize.js} (100%) rename tests/02.operations/{03.scale.js => 103.scale.js} (100%) rename tests/02.operations/{04.rotate.js => 104.rotate.js} (100%) rename tests/02.operations/{05.crop.js => 105.crop.js} (100%) rename tests/02.operations/{06.blur.js => 106.blur.js} (100%) rename tests/02.operations/{07.mirror.js => 107.mirror.js} (100%) rename tests/02.operations/{08.pad.js => 108.pad.js} (100%) rename tests/02.operations/{09.border.js => 109.border.js} (100%) rename tests/02.operations/{10.sharpen.js => 110.sharpen.js} (100%) rename tests/02.operations/{11.saturate.js => 111.saturate.js} (100%) rename tests/02.operations/{12.lighten.js => 112.lighten.js} (100%) rename tests/02.operations/{13.darken.js => 113.darken.js} (100%) rename tests/02.operations/{14.hue.js => 114.hue.js} (100%) rename tests/02.operations/{15.hslaAdjust.js => 115.hslaAdjust.js} (100%) rename tests/02.operations/{16.fade.js => 116.fade.js} (100%) rename tests/02.operations/{16.opacify.js => 116.opacify.js} (100%) diff --git a/README.md b/README.md index d6c47126..de4bdb63 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ 0. [Note on transparent images](#note-on-transparent-images) 0. [API](#api) 0. [Open an image from file or buffer](#open-an-image) + 0. [Create a new blank image](#create-a-new-image) 0. [Image operations](#image-operations) 0. [Resize](#resize) 0. [Scale](#scale) @@ -173,12 +174,15 @@ Colors are specified in one of three ways: ## API -All operations are done on an `image` object. An `image` object is obtained with -the `open` method. +All operations are done on an `image` object. An `image` object can be obtained +by: + +0. Openning an existing image with the [`open`](#open-an-image) method. +0. Creating a new image with the [`create`](#create-a-new-image) method. ### Open an image -`open(source, type, callback)` +`lwip.open(source, type, callback)` 0. `source {String/Buffer}`: The path to the image on disk or an image buffer. 0. `type {String}`: **Optional** type of the image. If omitted, the type will be @@ -211,6 +215,28 @@ fs.readFile('path/to/image.png', function(err, buffer){ }); ``` +### Create a new image + +`lwip.create(width, height, color, callback)` + +0. `width {Integer>0}`: The width of the new image. +0. `height {Integer>0}`: The height of the new image. +0. `color {String / Array / Object}`: **Optional** Color of the canvas. See + [colors specification](#colors-specification). Defaults to a transparent + canvas `{r:0, g:0, b:0, a:0}`. +0. `callback {Function(err, image)}` + +**Example**: + +```Javascript +var lwip = require('lwip'); + +lwip.create(500, 500, 'yellow', function(err, image){ + // check err + // 'image' is a 500X500 solid yellow canvas. +}); +``` + ### Image operations #### Resize diff --git a/defs.js b/defs.js index 1b404550..a2e6e436 100644 --- a/defs.js +++ b/defs.js @@ -10,6 +10,7 @@ PNG_DEF_COMPRESSION: 'fast', PNG_DEF_INTERLACED: false, PNG_DEF_TRANSPARENT: 'auto', + DEF_CREATE_COLOR: [0, 0, 0, 0] }; exports.interpolations = { @@ -90,6 +91,21 @@ name: 'callback', type: 'function' }], + create: [{ + name: 'width', + type: 'p-number' + }, { + name: 'height', + type: 'p-number' + }, { + name: 'color', + types: ['string', 'array', 'hash'], + optional: true, + default: defaults.DEF_CREATE_COLOR + }, { + name: 'callback', + type: 'function' + }], scale: [{ name: 'wRation', type: 'p-number' diff --git a/index.js b/index.js index eac9a61b..674fb5e0 100644 --- a/index.js +++ b/index.js @@ -683,9 +683,28 @@ throw Error('Unknown type \'' + ext + '\''); } + function create() { + decree(defs.args.create)(arguments, function(width, height, color, callback) { + color = normalizeColor(color); + setImmediate(function() { + var trans = color.a < 100, + c_len = width * height, + pixelsBuf = new Buffer(c_len * 4); + for (var i = 0; i < width * height; i++) { + pixelsBuf[i] = color.r; + pixelsBuf[c_len + i] = color.g; + pixelsBuf[2 * c_len + i] = color.b; + pixelsBuf[3 * c_len + i] = color.a; + } + callback(null, new image(pixelsBuf, width, height, trans)); + }); + }); + } + // EXPORTS // ------- module.exports = { - open: open + open: open, + create: create }; })(void 0); diff --git a/tests/00.argsValidation/000.open.js b/tests/00.argsValidation/001.open.js similarity index 100% rename from tests/00.argsValidation/000.open.js rename to tests/00.argsValidation/001.open.js diff --git a/tests/00.argsValidation/002.create.js b/tests/00.argsValidation/002.create.js new file mode 100644 index 00000000..5a2f7450 --- /dev/null +++ b/tests/00.argsValidation/002.create.js @@ -0,0 +1,75 @@ +// methods should throw errors when arguments are invalid + +var should = require("should"), + lwip = require('../../'), + imgs = require('../imgs'); + +describe('lwip.create arguments validation', function() { + + describe('invalid color object (1)', function() { + it('should throw an error', function() { + lwip.create.bind(lwip, 500, 500, { + foo: 'bar', + bar: 'foo' + }, function() {}).should.throwError(); + }); + }); + + describe('invalid color object (2)', function() { + it('should throw an error', function() { + lwip.create.bind(lwip, 5000, 5000, { + r: -5, + g: -8, + b: -1000 + }, function() {}).should.throwError(); + }); + }); + + describe('invalid color array (1)', function() { + it('should throw an error', function() { + lwip.create.bind(lwip, 5, 5, ['a', 'b'], function() {}).should.throwError(); + }); + }); + + describe('invalid color array (2)', function() { + it('should throw an error', function() { + lwip.create.bind(lwip, 11, 43, ['a', 'b', 'c'], function() {}).should.throwError(); + }); + }); + + describe('invalid color array (3)', function() { + it('should throw an error', function() { + lwip.create.bind(lwip, 909, 1, [100, -100, 100], function() {}).should.throwError(); + }); + }); + + describe('invalid color array (4)', function() { + it('should throw an error', function() { + lwip.create.bind(lwip, 1, 1, [100, 100, -100], function() {}).should.throwError(); + }); + }); + + describe('invalid color array (5)', function() { + it('should throw an error', function() { + lwip.create.bind(lwip, 100, 50, [1000, 100, 100], function() {}).should.throwError(); + }); + }); + + describe('invalid color array (6)', function() { + it('should throw an error', function() { + lwip.create.bind(lwip, 100, 50, [100, 1000, 100], function() {}).should.throwError(); + }); + }); + + describe('invalid color array (7)', function() { + it('should throw an error', function() { + lwip.create.bind(lwip, 100, 50, [100, 100, 1000], function() {}).should.throwError(); + }); + }); + + describe('invalid color string', function() { + it('should throw an error', function() { + lwip.create.bind(lwip, 100, 50, 'foo', function() {}).should.throwError(); + }); + }); +}); diff --git a/tests/00.argsValidation/001.image.resize.js b/tests/00.argsValidation/101.image.resize.js similarity index 100% rename from tests/00.argsValidation/001.image.resize.js rename to tests/00.argsValidation/101.image.resize.js diff --git a/tests/00.argsValidation/002.image.scale.js b/tests/00.argsValidation/102.image.scale.js similarity index 100% rename from tests/00.argsValidation/002.image.scale.js rename to tests/00.argsValidation/102.image.scale.js diff --git a/tests/00.argsValidation/003.image.rotate.js b/tests/00.argsValidation/103.image.rotate.js similarity index 100% rename from tests/00.argsValidation/003.image.rotate.js rename to tests/00.argsValidation/103.image.rotate.js diff --git a/tests/00.argsValidation/004.image.toBuffer.js b/tests/00.argsValidation/104.image.toBuffer.js similarity index 100% rename from tests/00.argsValidation/004.image.toBuffer.js rename to tests/00.argsValidation/104.image.toBuffer.js diff --git a/tests/00.argsValidation/005.image.writeFile.js b/tests/00.argsValidation/105.image.writeFile.js similarity index 100% rename from tests/00.argsValidation/005.image.writeFile.js rename to tests/00.argsValidation/105.image.writeFile.js diff --git a/tests/00.argsValidation/006.image.mirror.js b/tests/00.argsValidation/106.image.mirror.js similarity index 100% rename from tests/00.argsValidation/006.image.mirror.js rename to tests/00.argsValidation/106.image.mirror.js diff --git a/tests/00.argsValidation/007.image.pad.js b/tests/00.argsValidation/107.image.pad.js similarity index 100% rename from tests/00.argsValidation/007.image.pad.js rename to tests/00.argsValidation/107.image.pad.js diff --git a/tests/00.argsValidation/008.image.border.js b/tests/00.argsValidation/108.image.border.js similarity index 100% rename from tests/00.argsValidation/008.image.border.js rename to tests/00.argsValidation/108.image.border.js diff --git a/tests/00.argsValidation/101.batch.resize.js b/tests/00.argsValidation/201.batch.resize.js similarity index 100% rename from tests/00.argsValidation/101.batch.resize.js rename to tests/00.argsValidation/201.batch.resize.js diff --git a/tests/00.argsValidation/102.batch.scale.js b/tests/00.argsValidation/202.batch.scale.js similarity index 100% rename from tests/00.argsValidation/102.batch.scale.js rename to tests/00.argsValidation/202.batch.scale.js diff --git a/tests/00.argsValidation/103.batch.rotate.js b/tests/00.argsValidation/203.batch.rotate.js similarity index 100% rename from tests/00.argsValidation/103.batch.rotate.js rename to tests/00.argsValidation/203.batch.rotate.js diff --git a/tests/00.argsValidation/104.batch.toBuffer.js b/tests/00.argsValidation/204.batch.toBuffer.js similarity index 100% rename from tests/00.argsValidation/104.batch.toBuffer.js rename to tests/00.argsValidation/204.batch.toBuffer.js diff --git a/tests/00.argsValidation/105.batch.writeFile.js b/tests/00.argsValidation/205.batch.writeFile.js similarity index 100% rename from tests/00.argsValidation/105.batch.writeFile.js rename to tests/00.argsValidation/205.batch.writeFile.js diff --git a/tests/00.argsValidation/106.batch.mirror.js b/tests/00.argsValidation/206.batch.mirror.js similarity index 100% rename from tests/00.argsValidation/106.batch.mirror.js rename to tests/00.argsValidation/206.batch.mirror.js diff --git a/tests/00.argsValidation/107.batch.pad.js b/tests/00.argsValidation/207.batch.pad.js similarity index 100% rename from tests/00.argsValidation/107.batch.pad.js rename to tests/00.argsValidation/207.batch.pad.js diff --git a/tests/00.argsValidation/108.batch.border.js b/tests/00.argsValidation/208.batch.border.js similarity index 100% rename from tests/00.argsValidation/108.batch.border.js rename to tests/00.argsValidation/208.batch.border.js diff --git a/tests/02.operations/00.open.js b/tests/02.operations/001.open.js similarity index 100% rename from tests/02.operations/00.open.js rename to tests/02.operations/001.open.js diff --git a/tests/02.operations/002.create.js b/tests/02.operations/002.create.js new file mode 100644 index 00000000..1805c3e1 --- /dev/null +++ b/tests/02.operations/002.create.js @@ -0,0 +1,55 @@ +var join = require('path').join, + assert = require('assert'), + mkdirp = require('mkdirp'), + lwip = require('../../'); + +var tmpDir = join(__dirname, '../results'); + +describe('lwip.create', function() { + + before(function(done) { + mkdirp(tmpDir, done); + }); + + describe('500X500, [255,0,0,50]', function() { + it('should succeed', function(done) { + var width = 500, + height = 500, + bg = [255, 0, 0, 50]; + lwip.create(width, height, bg, function(err, img) { + if (err) return done(err); + assert(img.width() === width); + assert(img.height() === height); + img.writeFile(join(tmpDir, 'create500X500[255,0,0,50].png'), done); + }); + }); + }); + + describe('500X500, [0,255,0]', function() { + it('should succeed', function(done) { + var width = 500, + height = 500, + bg = [0, 255, 0]; + lwip.create(width, height, bg, function(err, img) { + if (err) return done(err); + assert(img.width() === width); + assert(img.height() === height); + img.writeFile(join(tmpDir, 'create500X500[0,255,0].png'), done); + }); + }); + }); + + describe('500X500, no color specified', function() { + it('should succeed', function(done) { + var width = 500, + height = 500; + lwip.create(width, height, function(err, img) { + if (err) return done(err); + assert(img.width() === width); + assert(img.height() === height); + img.writeFile(join(tmpDir, 'create500X500nocolor.png'), done); + }); + }); + }); + +}); diff --git a/tests/02.operations/01.writeFile.js b/tests/02.operations/101.writeFile.js similarity index 100% rename from tests/02.operations/01.writeFile.js rename to tests/02.operations/101.writeFile.js diff --git a/tests/02.operations/02.resize.js b/tests/02.operations/102.resize.js similarity index 100% rename from tests/02.operations/02.resize.js rename to tests/02.operations/102.resize.js diff --git a/tests/02.operations/03.scale.js b/tests/02.operations/103.scale.js similarity index 100% rename from tests/02.operations/03.scale.js rename to tests/02.operations/103.scale.js diff --git a/tests/02.operations/04.rotate.js b/tests/02.operations/104.rotate.js similarity index 100% rename from tests/02.operations/04.rotate.js rename to tests/02.operations/104.rotate.js diff --git a/tests/02.operations/05.crop.js b/tests/02.operations/105.crop.js similarity index 100% rename from tests/02.operations/05.crop.js rename to tests/02.operations/105.crop.js diff --git a/tests/02.operations/06.blur.js b/tests/02.operations/106.blur.js similarity index 100% rename from tests/02.operations/06.blur.js rename to tests/02.operations/106.blur.js diff --git a/tests/02.operations/07.mirror.js b/tests/02.operations/107.mirror.js similarity index 100% rename from tests/02.operations/07.mirror.js rename to tests/02.operations/107.mirror.js diff --git a/tests/02.operations/08.pad.js b/tests/02.operations/108.pad.js similarity index 100% rename from tests/02.operations/08.pad.js rename to tests/02.operations/108.pad.js diff --git a/tests/02.operations/09.border.js b/tests/02.operations/109.border.js similarity index 100% rename from tests/02.operations/09.border.js rename to tests/02.operations/109.border.js diff --git a/tests/02.operations/10.sharpen.js b/tests/02.operations/110.sharpen.js similarity index 100% rename from tests/02.operations/10.sharpen.js rename to tests/02.operations/110.sharpen.js diff --git a/tests/02.operations/11.saturate.js b/tests/02.operations/111.saturate.js similarity index 100% rename from tests/02.operations/11.saturate.js rename to tests/02.operations/111.saturate.js diff --git a/tests/02.operations/12.lighten.js b/tests/02.operations/112.lighten.js similarity index 100% rename from tests/02.operations/12.lighten.js rename to tests/02.operations/112.lighten.js diff --git a/tests/02.operations/13.darken.js b/tests/02.operations/113.darken.js similarity index 100% rename from tests/02.operations/13.darken.js rename to tests/02.operations/113.darken.js diff --git a/tests/02.operations/14.hue.js b/tests/02.operations/114.hue.js similarity index 100% rename from tests/02.operations/14.hue.js rename to tests/02.operations/114.hue.js diff --git a/tests/02.operations/15.hslaAdjust.js b/tests/02.operations/115.hslaAdjust.js similarity index 100% rename from tests/02.operations/15.hslaAdjust.js rename to tests/02.operations/115.hslaAdjust.js diff --git a/tests/02.operations/16.fade.js b/tests/02.operations/116.fade.js similarity index 100% rename from tests/02.operations/16.fade.js rename to tests/02.operations/116.fade.js diff --git a/tests/02.operations/16.opacify.js b/tests/02.operations/116.opacify.js similarity index 100% rename from tests/02.operations/16.opacify.js rename to tests/02.operations/116.opacify.js From 3141bbd9a66e00115985b2cefb13d80e031fad3f Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Sat, 27 Sep 2014 13:13:06 +0800 Subject: [PATCH 13/21] implements image.clone. closes #48 --- README.md | 9 ++++++++ defs.js | 4 ++++ examples/clone.js | 44 +++++++++++++++++++++++++++++++++++++ index.js | 37 +++++++++++++++++++++++-------- tests/01.getters/index.js | 16 ++++++++++++++ tests/03.safety/02.clone.js | 36 ++++++++++++++++++++++++++++++ 6 files changed, 137 insertions(+), 9 deletions(-) create mode 100644 examples/clone.js create mode 100644 tests/03.safety/02.clone.js diff --git a/README.md b/README.md index de4bdb63..0fa4c03d 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ 0. [Getters](#getters) 0. [Width](#width) 0. [Height](#height) + 0. [Clone](#clone) 0. [Get as a Buffer](#get-as-a-buffer) 0. [JPEG](#jpeg) 0. [PNG](#png) @@ -444,6 +445,14 @@ Make image completely opaque. `image.height()` returns the image's height in pixels. +#### Clone + +Clone the image into a new image object. + +`image.clone(callback)` + +0. `callback {Function(err, newImage)}` + #### Get as a Buffer Get encoded binary image data as a NodeJS diff --git a/defs.js b/defs.js index a2e6e436..4dd7c72a 100644 --- a/defs.js +++ b/defs.js @@ -289,6 +289,10 @@ name: 'callback', type: 'function' }], + clone: [{ + name: 'callback', + type: 'function' + }], writeFile: [{ name: 'path', type: 'string' diff --git a/examples/clone.js b/examples/clone.js new file mode 100644 index 00000000..d4b05092 --- /dev/null +++ b/examples/clone.js @@ -0,0 +1,44 @@ +/** + * Example for using LWIP to clone an image. + * 1. Do some initial manipulations on the image. + * 2. Clone 2 separate images. + * 3. Do some more different manipulations in the clones. + */ + +var path = require('path'), + lwip = require('../'); + +lwip.open('lena.jpg', function(err, image) { + if (err) return console.log(err); + + image.batch() + .scale(0.75) + .border(5, [50, 100, 75, 75]) + .exec(function(err, image) { + if (err) return console.log(err); + + image.clone(function(err, clone1) { + if (err) return console.log("clone1:", err); + clone1.batch() + .mirror('y') + .hue(100) + .writeFile('lena_clone1.png', function(err) { + if (err) return console.log(err); + console.log('clone1: done'); + }); + }); + + image.clone(function(err, clone2) { + if (err) return console.log("clone2:", err); + clone2.batch() + .fade(0.5) + .mirror('x') + .writeFile('lena_clone2.png', function(err) { + if (err) return console.log(err); + console.log('clone2: done'); + }); + }); + + }); + +}); diff --git a/index.js b/index.js index 674fb5e0..a2cb55c4 100644 --- a/index.js +++ b/index.js @@ -370,6 +370,25 @@ } } + image.prototype.clone = function() { + // no need to lock the image. we don't modify the memory buffer. + // just copy it. + var that = this; + decree(defs.args.clone)(arguments, function(callback) { + // first we retrieve what we need (buffer, dimensions, ...) + // synchronously so that the original image doesn't have a chance + // to be changed (remember, we don't lock it); and only then call + // the callback asynchronously. + var pixbuff = that.__lwip.buffer(), + width = that.__lwip.width(), + height = that.__lwip.height(), + trans = that.__trans; + setImmediate(function() { + callback(null, new image(pixbuff, width, height, trans)); + }); + }); + } + image.prototype.toBuffer = function() { this.__lock(); try { @@ -686,16 +705,16 @@ function create() { decree(defs.args.create)(arguments, function(width, height, color, callback) { color = normalizeColor(color); + var trans = color.a < 100, + c_len = width * height, + pixelsBuf = new Buffer(c_len * 4); + for (var i = 0; i < width * height; i++) { + pixelsBuf[i] = color.r; + pixelsBuf[c_len + i] = color.g; + pixelsBuf[2 * c_len + i] = color.b; + pixelsBuf[3 * c_len + i] = color.a; + } setImmediate(function() { - var trans = color.a < 100, - c_len = width * height, - pixelsBuf = new Buffer(c_len * 4); - for (var i = 0; i < width * height; i++) { - pixelsBuf[i] = color.r; - pixelsBuf[c_len + i] = color.g; - pixelsBuf[2 * c_len + i] = color.b; - pixelsBuf[3 * c_len + i] = color.a; - } callback(null, new image(pixelsBuf, width, height, trans)); }); }); diff --git a/tests/01.getters/index.js b/tests/01.getters/index.js index a55ba5f0..acb27d09 100644 --- a/tests/01.getters/index.js +++ b/tests/01.getters/index.js @@ -32,3 +32,19 @@ describe('lwip.size', function() { assert(image.size().height === height); }); }); + +describe('lwip.clone', function() { + it('should return a new image object', function(done) { + image.clone(function(err, clonedImage) { + if (err) return done(err); + clonedImage.resize(100, 100, function(err) { + if (err) return done(err); + assert(image.width() === width); + assert(image.height() === height); + assert(clonedImage.width() === 100); + assert(clonedImage.height() === 100); + done(); + }); + }); + }); +}); diff --git a/tests/03.safety/02.clone.js b/tests/03.safety/02.clone.js new file mode 100644 index 00000000..5e1fea83 --- /dev/null +++ b/tests/03.safety/02.clone.js @@ -0,0 +1,36 @@ +var assert = require('assert'), + lwip = require('../../'), + imgs = require('../imgs'); + +describe('clone correct behavior', function() { + + var image; + + beforeEach(function(done) { + lwip.open(imgs.jpg.rgb, function(err, img) { + image = img; + done(err); + }); + }); + + describe('image.clone', function() { + it('should clone the image at the correct state', function(done) { + var width = image.width(), + height = image.height(); + image.clone(function(err, clone) { + // this callback is called asynchronously, + // so 'resize' is called before this callback is run, + // but still we want to make sure we get a clone of the + // original image. + if (err) return done(err); + assert(clone.width() === width); + assert(clone.height() === height); + done(); + }); + image.resize(100, 100, function(err) { + if (err) return done(err); + }); + }); + }); + +}); From 62ed8812cdc944280bbc7f25679a0619b36d68d9 Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Sat, 27 Sep 2014 13:18:54 +0800 Subject: [PATCH 14/21] updates docs for 'clone' method --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 0fa4c03d..c3f6dcdd 100644 --- a/README.md +++ b/README.md @@ -453,6 +453,21 @@ Clone the image into a new image object. 0. `callback {Function(err, newImage)}` +**Example**: See [`examples/clone.js`](examples/clone.js) + +**Note**: The image is cloned to the state it was at the time +`image.clone( ... )` was called, eventhough `callback` is called asynchronously. + +```Javascript +image.width(); // 500 +image.clone(function(err, clone){ + clone.width(); // 500 +}); +image.resize(100,100, function(err, image){ + image.width(); //100 +}); +``` + #### Get as a Buffer Get encoded binary image data as a NodeJS From 83bb0b8fb384d7dd78382cdfad848f28296aeff8 Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Mon, 29 Sep 2014 10:14:41 +0300 Subject: [PATCH 15/21] implements image.extract (copy). #36 --- README.md | 26 ++++++++++++++++++++--- defs.js | 16 ++++++++++++++ examples/extract.js | 39 +++++++++++++++++++++++++++++++++++ index.js | 20 ++++++++++++++++++ tests/01.getters/index.js | 13 ++++++++++++ tests/03.safety/03.extract.js | 33 +++++++++++++++++++++++++++++ 6 files changed, 144 insertions(+), 3 deletions(-) create mode 100644 examples/extract.js create mode 100644 tests/03.safety/03.extract.js diff --git a/README.md b/README.md index c3f6dcdd..16e980a6 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ 0. [Width](#width) 0. [Height](#height) 0. [Clone](#clone) + 0. [Extract / Copy](#extract) 0. [Get as a Buffer](#get-as-a-buffer) 0. [JPEG](#jpeg) 0. [PNG](#png) @@ -178,8 +179,12 @@ Colors are specified in one of three ways: All operations are done on an `image` object. An `image` object can be obtained by: -0. Openning an existing image with the [`open`](#open-an-image) method. -0. Creating a new image with the [`create`](#create-a-new-image) method. +0. Openning an existing image file or buffer with the [`open`](#open-an-image) + method. +0. Creating a new image object with the [`create`](#create-a-new-image) method. +0. Cloning an existing image object with the [`image.clone`](#clone) method. +0. Extracting a sub-image from an existing image object with the + [`image.extract`](#extract) method. ### Open an image @@ -463,11 +468,26 @@ image.width(); // 500 image.clone(function(err, clone){ clone.width(); // 500 }); -image.resize(100,100, function(err, image){ +image.resize(100, 100, function(err, image){ image.width(); //100 }); ``` +#### Extract + +Copy an area of the image into a new image object. + +`image.extract(left, top, right, bottom, callback)` + +0. `left, top, right, bottom {Integer}`: Coordinates of the area to copy. +0. `callback {Function(err, newImage)}` + +**Example**: See [`examples/extract.js`](examples/extract.js) + +**Note**: The sub-image is extracted from the original image in the state it was +at the time `image.extract( ... )` was called, eventhough `callback` is called +asynchronously. + #### Get as a Buffer Get encoded binary image data as a NodeJS diff --git a/defs.js b/defs.js index 4dd7c72a..ea6dcef8 100644 --- a/defs.js +++ b/defs.js @@ -293,6 +293,22 @@ name: 'callback', type: 'function' }], + extract: [{ + name: 'left', + type: 'nn-number' + }, { + name: 'top', + type: 'nn-number' + }, { + name: 'right', + type: 'nn-number' + }, { + name: 'bottom', + type: 'nn-number' + }, { + name: 'callback', + type: 'function' + }], writeFile: [{ name: 'path', type: 'string' diff --git a/examples/extract.js b/examples/extract.js new file mode 100644 index 00000000..0449c98d --- /dev/null +++ b/examples/extract.js @@ -0,0 +1,39 @@ +/** + * Example for using LWIP to extract parts of an image. + */ + +var path = require('path'), + lwip = require('../'); + +lwip.open('lena.jpg', function(err, image) { + if (err) return console.log(err); + + image.extract(230, 230, 370, 300, function(err, eyes) { + eyes.writeFile('lena_eyes.jpg', function(err) { + if (err) return console.log("eyes:", err); + console.log('eyes: done'); + }); + + eyes.extract(0, 0, 70, 71, function(err, left_eye) { + left_eye.writeFile('lena_eyes_left.jpg', function(err) { + if (err) return console.log("eyes left:", err); + console.log('eyes left: done'); + }); + }); + + eyes.extract(71, 0, 141, 71, function(err, right_eye) { + right_eye.writeFile('lena_eyes_right.jpg', function(err) { + if (err) return console.log("eyes right:", err); + console.log('eyes right: done'); + }); + }); + }); + + image.extract(240, 320, 350, 380, function(err, eyes) { + eyes.writeFile('lena_mouth.jpg', function(err) { + if (err) return console.log("mouth:", err); + console.log('mouth: done'); + }); + }); + +}); diff --git a/index.js b/index.js index a2cb55c4..85df0795 100644 --- a/index.js +++ b/index.js @@ -389,6 +389,26 @@ }); } + image.prototype.extract = function() { + // no need to lock the image. we don't modify the memory buffer. + // just copy it and then crop it. + var that = this; + decree(defs.args.extract)(arguments, function(left, top, right, bottom, callback) { + // first we retrieve what we need (buffer, dimensions, ...) + // synchronously so that the original image doesn't have a chance + // to be changed (remember, we don't lock it); then we crop it and + // only call the callback asynchronously. + var pixbuff = that.__lwip.buffer(), + width = that.__lwip.width(), + height = that.__lwip.height(), + trans = that.__trans, + eximg = new image(pixbuff, width, height, trans); + eximg.__lwip.crop(left, top, right, bottom, function(err) { + callback(err, eximg); + }); + }); + } + image.prototype.toBuffer = function() { this.__lock(); try { diff --git a/tests/01.getters/index.js b/tests/01.getters/index.js index acb27d09..9e9e47ca 100644 --- a/tests/01.getters/index.js +++ b/tests/01.getters/index.js @@ -48,3 +48,16 @@ describe('lwip.clone', function() { }); }); }); + +describe('lwip.extract', function() { + it('should return a new image object', function(done) { + image.extract(100, 120, 150, 140, function(err, exImage) { + if (err) return done(err); + assert(image.width() === width); + assert(image.height() === height); + assert(exImage.width() === 150 - 100 + 1); + assert(exImage.height() === 140 - 120 + 1); + done(); + }); + }); +}); diff --git a/tests/03.safety/03.extract.js b/tests/03.safety/03.extract.js new file mode 100644 index 00000000..467eccaf --- /dev/null +++ b/tests/03.safety/03.extract.js @@ -0,0 +1,33 @@ +var join = require('path').join, + lwip = require('../../'), + imgs = require('../imgs'); + +describe('extract correct behavior', function() { + + var tmpDir = join(__dirname, '../results'), + image; + + beforeEach(function(done) { + lwip.open(imgs.jpg.rgb, function(err, img) { + image = img; + done(err); + }); + }); + + describe('image.extract', function() { + it('should extract the sub-image at the correct state', function(done) { + image.extract(50, 50, 339, 299, function(err, extimg) { + // this callback is called asynchronously, + // so 'hue' is called before this callback is run, + // but still we want to make sure the extracted image has the + // original hue. + if (err) return done(err); + extimg.writeFile(join(tmpDir, 'extract_behaviour_SHOULD_HAVE_ORIGINAL_HUE.jpg'), done); + }); + image.hue(100, function(err) { + if (err) return done(err); + }); + }); + }); + +}); From 96679443534bf056b79f5cc405c6d9e59b48c149 Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Mon, 29 Sep 2014 21:39:55 +0300 Subject: [PATCH 16/21] refactors class names and file structure --- README.md | 8 +- binding.gyp | 216 ++--- index.js | 750 +----------------- lib/Batch.js | 233 ++++++ lib/Image.js | 450 +++++++++++ defs.js => lib/defs.js | 0 lib/obtain.js | 73 ++ lib/util.js | 40 + {lib => src/lib}/cimg/CImg.h | 0 .../lib}/cimg/Licence_CeCILL-C_V1-en.txt | 0 .../lib}/cimg/Licence_CeCILL_V2-en.txt | 0 {lib => src/lib}/cimg/README.txt | 0 {lib => src/lib}/jpeg/README | 0 {lib => src/lib}/jpeg/jaricom.c | 0 {lib => src/lib}/jpeg/jcapimin.c | 0 {lib => src/lib}/jpeg/jcapistd.c | 0 {lib => src/lib}/jpeg/jcarith.c | 0 {lib => src/lib}/jpeg/jccoefct.c | 0 {lib => src/lib}/jpeg/jccolor.c | 0 {lib => src/lib}/jpeg/jcdctmgr.c | 0 {lib => src/lib}/jpeg/jchuff.c | 0 {lib => src/lib}/jpeg/jcinit.c | 0 {lib => src/lib}/jpeg/jcmainct.c | 0 {lib => src/lib}/jpeg/jcmarker.c | 0 {lib => src/lib}/jpeg/jcmaster.c | 0 {lib => src/lib}/jpeg/jcomapi.c | 0 {lib => src/lib}/jpeg/jconfig.h | 0 {lib => src/lib}/jpeg/jcparam.c | 0 {lib => src/lib}/jpeg/jcprepct.c | 0 {lib => src/lib}/jpeg/jcsample.c | 0 {lib => src/lib}/jpeg/jdapimin.c | 0 {lib => src/lib}/jpeg/jdapistd.c | 0 {lib => src/lib}/jpeg/jdarith.c | 0 {lib => src/lib}/jpeg/jdatadst.c | 0 {lib => src/lib}/jpeg/jdatasrc.c | 0 {lib => src/lib}/jpeg/jdcoefct.c | 0 {lib => src/lib}/jpeg/jdcolor.c | 0 {lib => src/lib}/jpeg/jdct.h | 0 {lib => src/lib}/jpeg/jddctmgr.c | 0 {lib => src/lib}/jpeg/jdhuff.c | 0 {lib => src/lib}/jpeg/jdinput.c | 0 {lib => src/lib}/jpeg/jdmainct.c | 0 {lib => src/lib}/jpeg/jdmarker.c | 0 {lib => src/lib}/jpeg/jdmaster.c | 0 {lib => src/lib}/jpeg/jdmerge.c | 0 {lib => src/lib}/jpeg/jdpostct.c | 0 {lib => src/lib}/jpeg/jdsample.c | 0 {lib => src/lib}/jpeg/jerror.c | 0 {lib => src/lib}/jpeg/jerror.h | 0 {lib => src/lib}/jpeg/jfdctflt.c | 0 {lib => src/lib}/jpeg/jfdctfst.c | 0 {lib => src/lib}/jpeg/jfdctint.c | 0 {lib => src/lib}/jpeg/jidctflt.c | 0 {lib => src/lib}/jpeg/jidctfst.c | 0 {lib => src/lib}/jpeg/jidctint.c | 0 {lib => src/lib}/jpeg/jinclude.h | 0 {lib => src/lib}/jpeg/jmemmgr.c | 0 {lib => src/lib}/jpeg/jmemnobs.c | 0 {lib => src/lib}/jpeg/jmemsys.h | 0 {lib => src/lib}/jpeg/jmorecfg.h | 0 {lib => src/lib}/jpeg/jpegint.h | 0 {lib => src/lib}/jpeg/jpeglib.h | 0 {lib => src/lib}/jpeg/jquant1.c | 0 {lib => src/lib}/jpeg/jquant2.c | 0 {lib => src/lib}/jpeg/jutils.c | 0 {lib => src/lib}/jpeg/jversion.h | 0 {lib => src/lib}/jpeg/libjpeg.txt | 0 {lib => src/lib}/png/LICENSE | 0 {lib => src/lib}/png/README | 0 {lib => src/lib}/png/png.c | 0 {lib => src/lib}/png/png.h | 0 {lib => src/lib}/png/pngconf.h | 0 {lib => src/lib}/png/pngdebug.h | 0 {lib => src/lib}/png/pngerror.c | 0 {lib => src/lib}/png/pngget.c | 0 {lib => src/lib}/png/pnginfo.h | 0 {lib => src/lib}/png/pngmem.c | 0 {lib => src/lib}/png/pngpread.c | 0 {lib => src/lib}/png/pngpriv.h | 0 {lib => src/lib}/png/pngread.c | 0 {lib => src/lib}/png/pngrio.c | 0 {lib => src/lib}/png/pngrtran.c | 0 {lib => src/lib}/png/pngrutil.c | 0 {lib => src/lib}/png/pngset.c | 0 {lib => src/lib}/png/pngstruct.h | 0 {lib => src/lib}/png/pngtrans.c | 0 {lib => src/lib}/png/pngwio.c | 0 {lib => src/lib}/png/pngwrite.c | 0 {lib => src/lib}/png/pngwtran.c | 0 {lib => src/lib}/png/pngwutil.c | 0 {lib => src/lib}/zlib/README | 0 {lib => src/lib}/zlib/adler32.c | 0 {lib => src/lib}/zlib/compress.c | 0 {lib => src/lib}/zlib/crc32.c | 0 {lib => src/lib}/zlib/crc32.h | 0 {lib => src/lib}/zlib/deflate.c | 0 {lib => src/lib}/zlib/deflate.h | 0 {lib => src/lib}/zlib/gzclose.c | 0 {lib => src/lib}/zlib/gzguts.h | 0 {lib => src/lib}/zlib/gzlib.c | 0 {lib => src/lib}/zlib/gzread.c | 0 {lib => src/lib}/zlib/gzwrite.c | 0 {lib => src/lib}/zlib/infback.c | 0 {lib => src/lib}/zlib/inffast.c | 0 {lib => src/lib}/zlib/inffast.h | 0 {lib => src/lib}/zlib/inffixed.h | 0 {lib => src/lib}/zlib/inflate.c | 0 {lib => src/lib}/zlib/inflate.h | 0 {lib => src/lib}/zlib/inftrees.c | 0 {lib => src/lib}/zlib/inftrees.h | 0 {lib => src/lib}/zlib/trees.c | 0 {lib => src/lib}/zlib/trees.h | 0 {lib => src/lib}/zlib/uncompr.c | 0 {lib => src/lib}/zlib/zconf.h | 0 {lib => src/lib}/zlib/zlib.h | 0 {lib => src/lib}/zlib/zutil.c | 0 {lib => src/lib}/zlib/zutil.h | 0 117 files changed, 909 insertions(+), 861 deletions(-) create mode 100644 lib/Batch.js create mode 100644 lib/Image.js rename defs.js => lib/defs.js (100%) create mode 100644 lib/obtain.js create mode 100644 lib/util.js rename {lib => src/lib}/cimg/CImg.h (100%) rename {lib => src/lib}/cimg/Licence_CeCILL-C_V1-en.txt (100%) rename {lib => src/lib}/cimg/Licence_CeCILL_V2-en.txt (100%) rename {lib => src/lib}/cimg/README.txt (100%) rename {lib => src/lib}/jpeg/README (100%) rename {lib => src/lib}/jpeg/jaricom.c (100%) rename {lib => src/lib}/jpeg/jcapimin.c (100%) rename {lib => src/lib}/jpeg/jcapistd.c (100%) rename {lib => src/lib}/jpeg/jcarith.c (100%) rename {lib => src/lib}/jpeg/jccoefct.c (100%) rename {lib => src/lib}/jpeg/jccolor.c (100%) rename {lib => src/lib}/jpeg/jcdctmgr.c (100%) rename {lib => src/lib}/jpeg/jchuff.c (100%) rename {lib => src/lib}/jpeg/jcinit.c (100%) rename {lib => src/lib}/jpeg/jcmainct.c (100%) rename {lib => src/lib}/jpeg/jcmarker.c (100%) rename {lib => src/lib}/jpeg/jcmaster.c (100%) rename {lib => src/lib}/jpeg/jcomapi.c (100%) rename {lib => src/lib}/jpeg/jconfig.h (100%) rename {lib => src/lib}/jpeg/jcparam.c (100%) rename {lib => src/lib}/jpeg/jcprepct.c (100%) rename {lib => src/lib}/jpeg/jcsample.c (100%) rename {lib => src/lib}/jpeg/jdapimin.c (100%) rename {lib => src/lib}/jpeg/jdapistd.c (100%) rename {lib => src/lib}/jpeg/jdarith.c (100%) rename {lib => src/lib}/jpeg/jdatadst.c (100%) rename {lib => src/lib}/jpeg/jdatasrc.c (100%) rename {lib => src/lib}/jpeg/jdcoefct.c (100%) rename {lib => src/lib}/jpeg/jdcolor.c (100%) rename {lib => src/lib}/jpeg/jdct.h (100%) rename {lib => src/lib}/jpeg/jddctmgr.c (100%) rename {lib => src/lib}/jpeg/jdhuff.c (100%) rename {lib => src/lib}/jpeg/jdinput.c (100%) rename {lib => src/lib}/jpeg/jdmainct.c (100%) rename {lib => src/lib}/jpeg/jdmarker.c (100%) rename {lib => src/lib}/jpeg/jdmaster.c (100%) rename {lib => src/lib}/jpeg/jdmerge.c (100%) rename {lib => src/lib}/jpeg/jdpostct.c (100%) rename {lib => src/lib}/jpeg/jdsample.c (100%) rename {lib => src/lib}/jpeg/jerror.c (100%) rename {lib => src/lib}/jpeg/jerror.h (100%) rename {lib => src/lib}/jpeg/jfdctflt.c (100%) rename {lib => src/lib}/jpeg/jfdctfst.c (100%) rename {lib => src/lib}/jpeg/jfdctint.c (100%) rename {lib => src/lib}/jpeg/jidctflt.c (100%) rename {lib => src/lib}/jpeg/jidctfst.c (100%) rename {lib => src/lib}/jpeg/jidctint.c (100%) rename {lib => src/lib}/jpeg/jinclude.h (100%) rename {lib => src/lib}/jpeg/jmemmgr.c (100%) rename {lib => src/lib}/jpeg/jmemnobs.c (100%) rename {lib => src/lib}/jpeg/jmemsys.h (100%) rename {lib => src/lib}/jpeg/jmorecfg.h (100%) rename {lib => src/lib}/jpeg/jpegint.h (100%) rename {lib => src/lib}/jpeg/jpeglib.h (100%) rename {lib => src/lib}/jpeg/jquant1.c (100%) rename {lib => src/lib}/jpeg/jquant2.c (100%) rename {lib => src/lib}/jpeg/jutils.c (100%) rename {lib => src/lib}/jpeg/jversion.h (100%) rename {lib => src/lib}/jpeg/libjpeg.txt (100%) rename {lib => src/lib}/png/LICENSE (100%) rename {lib => src/lib}/png/README (100%) rename {lib => src/lib}/png/png.c (100%) rename {lib => src/lib}/png/png.h (100%) rename {lib => src/lib}/png/pngconf.h (100%) rename {lib => src/lib}/png/pngdebug.h (100%) rename {lib => src/lib}/png/pngerror.c (100%) rename {lib => src/lib}/png/pngget.c (100%) rename {lib => src/lib}/png/pnginfo.h (100%) rename {lib => src/lib}/png/pngmem.c (100%) rename {lib => src/lib}/png/pngpread.c (100%) rename {lib => src/lib}/png/pngpriv.h (100%) rename {lib => src/lib}/png/pngread.c (100%) rename {lib => src/lib}/png/pngrio.c (100%) rename {lib => src/lib}/png/pngrtran.c (100%) rename {lib => src/lib}/png/pngrutil.c (100%) rename {lib => src/lib}/png/pngset.c (100%) rename {lib => src/lib}/png/pngstruct.h (100%) rename {lib => src/lib}/png/pngtrans.c (100%) rename {lib => src/lib}/png/pngwio.c (100%) rename {lib => src/lib}/png/pngwrite.c (100%) rename {lib => src/lib}/png/pngwtran.c (100%) rename {lib => src/lib}/png/pngwutil.c (100%) rename {lib => src/lib}/zlib/README (100%) rename {lib => src/lib}/zlib/adler32.c (100%) rename {lib => src/lib}/zlib/compress.c (100%) rename {lib => src/lib}/zlib/crc32.c (100%) rename {lib => src/lib}/zlib/crc32.h (100%) rename {lib => src/lib}/zlib/deflate.c (100%) rename {lib => src/lib}/zlib/deflate.h (100%) rename {lib => src/lib}/zlib/gzclose.c (100%) rename {lib => src/lib}/zlib/gzguts.h (100%) rename {lib => src/lib}/zlib/gzlib.c (100%) rename {lib => src/lib}/zlib/gzread.c (100%) rename {lib => src/lib}/zlib/gzwrite.c (100%) rename {lib => src/lib}/zlib/infback.c (100%) rename {lib => src/lib}/zlib/inffast.c (100%) rename {lib => src/lib}/zlib/inffast.h (100%) rename {lib => src/lib}/zlib/inffixed.h (100%) rename {lib => src/lib}/zlib/inflate.c (100%) rename {lib => src/lib}/zlib/inflate.h (100%) rename {lib => src/lib}/zlib/inftrees.c (100%) rename {lib => src/lib}/zlib/inftrees.h (100%) rename {lib => src/lib}/zlib/trees.c (100%) rename {lib => src/lib}/zlib/trees.h (100%) rename {lib => src/lib}/zlib/uncompr.c (100%) rename {lib => src/lib}/zlib/zconf.h (100%) rename {lib => src/lib}/zlib/zlib.h (100%) rename {lib => src/lib}/zlib/zutil.c (100%) rename {lib => src/lib}/zlib/zutil.h (100%) diff --git a/README.md b/README.md index 16e980a6..871b7707 100644 --- a/README.md +++ b/README.md @@ -654,13 +654,13 @@ The native part of this module is compiled from source which uses the following: - Independent JPEG Group's free JPEG software: - [Website](http://www.ijg.org/) - - [Readme](https://github.com/EyalAr/lwip/blob/master/lib/jpeg/README) + - [Readme](https://github.com/EyalAr/lwip/blob/master/src/lib/jpeg/README) - libpng: - [Website](http://www.libpng.org/) - - [Readme](https://github.com/EyalAr/lwip/blob/master/lib/png/README) + - [Readme](https://github.com/EyalAr/lwip/blob/master/src/lib/png/README) - zlib: - [Website](http://www.zlib.net/) - - [Readme](https://github.com/EyalAr/lwip/blob/master/lib/zlib/README) + - [Readme](https://github.com/EyalAr/lwip/blob/master/src/lib/zlib/README) - The CImg Library - [Website](http://cimg.sourceforge.net/) - - [Readme](https://github.com/EyalAr/lwip/blob/master/lib/cimg/README.txt) + - [Readme](https://github.com/EyalAr/lwip/blob/master/src/lib/cimg/README.txt) diff --git a/binding.gyp b/binding.gyp index d9b227f3..25d309e4 100644 --- a/binding.gyp +++ b/binding.gyp @@ -11,70 +11,70 @@ "src/decoder/png_decoder.cpp", # LIB JPEG: ########### - "lib/jpeg/jmemnobs.c", - "lib/jpeg/jcomapi.c", - "lib/jpeg/jdapimin.c", - "lib/jpeg/jdapistd.c", - "lib/jpeg/jdatadst.c", - "lib/jpeg/jdatasrc.c", - "lib/jpeg/jdcoefct.c", - "lib/jpeg/jdcolor.c", - "lib/jpeg/jddctmgr.c", - "lib/jpeg/jdhuff.c", - "lib/jpeg/jdinput.c", - "lib/jpeg/jdmainct.c", - "lib/jpeg/jdmarker.c", - "lib/jpeg/jdmaster.c", - "lib/jpeg/jdpostct.c", - "lib/jpeg/jdsample.c", - "lib/jpeg/jerror.c", - "lib/jpeg/jfdctflt.c", - "lib/jpeg/jfdctfst.c", - "lib/jpeg/jfdctint.c", - "lib/jpeg/jidctflt.c", - "lib/jpeg/jidctfst.c", - "lib/jpeg/jidctint.c", - "lib/jpeg/jutils.c", - "lib/jpeg/jmemmgr.c", - "lib/jpeg/jdarith.c", - "lib/jpeg/jdmerge.c", - "lib/jpeg/jaricom.c", - "lib/jpeg/jquant1.c", - "lib/jpeg/jquant2.c", + "src/lib/jpeg/jmemnobs.c", + "src/lib/jpeg/jcomapi.c", + "src/lib/jpeg/jdapimin.c", + "src/lib/jpeg/jdapistd.c", + "src/lib/jpeg/jdatadst.c", + "src/lib/jpeg/jdatasrc.c", + "src/lib/jpeg/jdcoefct.c", + "src/lib/jpeg/jdcolor.c", + "src/lib/jpeg/jddctmgr.c", + "src/lib/jpeg/jdhuff.c", + "src/lib/jpeg/jdinput.c", + "src/lib/jpeg/jdmainct.c", + "src/lib/jpeg/jdmarker.c", + "src/lib/jpeg/jdmaster.c", + "src/lib/jpeg/jdpostct.c", + "src/lib/jpeg/jdsample.c", + "src/lib/jpeg/jerror.c", + "src/lib/jpeg/jfdctflt.c", + "src/lib/jpeg/jfdctfst.c", + "src/lib/jpeg/jfdctint.c", + "src/lib/jpeg/jidctflt.c", + "src/lib/jpeg/jidctfst.c", + "src/lib/jpeg/jidctint.c", + "src/lib/jpeg/jutils.c", + "src/lib/jpeg/jmemmgr.c", + "src/lib/jpeg/jdarith.c", + "src/lib/jpeg/jdmerge.c", + "src/lib/jpeg/jaricom.c", + "src/lib/jpeg/jquant1.c", + "src/lib/jpeg/jquant2.c", # LIB PNG: ########## - "lib/png/png.c", - "lib/png/pngset.c", - "lib/png/pngget.c", - "lib/png/pngrutil.c", - "lib/png/pngtrans.c", - "lib/png/pngread.c", - "lib/png/pngrio.c", - "lib/png/pngrtran.c", - "lib/png/pngmem.c", - "lib/png/pngerror.c", - "lib/png/pngpread.c", + "src/lib/png/png.c", + "src/lib/png/pngset.c", + "src/lib/png/pngget.c", + "src/lib/png/pngrutil.c", + "src/lib/png/pngtrans.c", + "src/lib/png/pngread.c", + "src/lib/png/pngrio.c", + "src/lib/png/pngrtran.c", + "src/lib/png/pngmem.c", + "src/lib/png/pngerror.c", + "src/lib/png/pngpread.c", # ZLIB: ####### - "lib/zlib/adler32.c", - "lib/zlib/crc32.c", - "lib/zlib/gzlib.c", - "lib/zlib/gzread.c", - "lib/zlib/infback.c", - "lib/zlib/inflate.c", - "lib/zlib/inftrees.c", - "lib/zlib/inffast.c", - "lib/zlib/uncompr.c", - "lib/zlib/zutil.c", - "lib/zlib/trees.c" + "src/lib/zlib/adler32.c", + "src/lib/zlib/crc32.c", + "src/lib/zlib/gzlib.c", + "src/lib/zlib/gzread.c", + "src/lib/zlib/infback.c", + "src/lib/zlib/inflate.c", + "src/lib/zlib/inftrees.c", + "src/lib/zlib/inffast.c", + "src/lib/zlib/uncompr.c", + "src/lib/zlib/zutil.c", + "src/lib/zlib/trees.c" ], 'include_dirs': [ ' 255) - throw Error('\'red\' color component is invalid'); - if (color.g != parseInt(color.g) || color.g < 0 || color.g > 255) - throw Error('\'green\' color component is invalid'); - if (color.b != parseInt(color.b) || color.b < 0 || color.b > 255) - throw Error('\'blue\' color component is invalid'); - if (color.a != parseInt(color.a) || color.a < 0 || color.a > 100) - throw Error('\'alpha\' color component is invalid'); - } - return color; - } - - function image(pixelsBuf, width, height, trans) { - this.__lwip = new lwip_image.LwipImage(pixelsBuf, width, height); - this.__locked = false; - this.__trans = trans; - } - - image.prototype.__lock = function() { - if (!this.__locked) this.__locked = true; - else throw Error("Another image operation already in progress"); - }; - - image.prototype.__release = function() { - this.__locked = false; - }; - - image.prototype.width = function() { - return this.__lwip.width(); - } - - image.prototype.height = function() { - return this.__lwip.height(); - } - - image.prototype.size = function() { - return { - width: this.__lwip.width(), - height: this.__lwip.height() - }; - } - - image.prototype.scale = function() { - this.__lock(); - try { - var that = this; - decree(defs.args.scale)(arguments, function(wRatio, hRatio, inter, callback) { - if (!defs.interpolations[inter]) throw Error("Unknown interpolation " + inter); - hRatio = hRatio || wRatio; - var width = +wRatio * that.width(), - height = +hRatio * that.height(); - that.__lwip.resize(width, height, defs.interpolations[inter], function(err) { - that.__release(); - callback(err, that); - }); - }); - } catch (e) { - this.__release(); - throw e; - } - } - - image.prototype.resize = function() { - this.__lock(); - try { - var that = this; - decree(defs.args.resize)(arguments, function(width, height, inter, callback) { - if (!defs.interpolations[inter]) throw Error("Unknown interpolation " + inter); - height = height || width; - that.__lwip.resize(+width, +height, defs.interpolations[inter], function(err) { - that.__release(); - callback(err, that); - }); - }); - } catch (e) { - this.__release(); - throw e; - } - } - - image.prototype.rotate = function() { - this.__lock(); - try { - var that = this; - decree(defs.args.rotate)(arguments, function(degs, color, callback) { - color = normalizeColor(color); - if (color.a < 100) that.__trans = true; - that.__lwip.rotate(+degs, +color.r, +color.g, +color.b, +color.a, function(err) { - that.__release(); - callback(err, that); - }); - }); - } catch (e) { - this.__release(); - throw e; - } - } - - image.prototype.blur = function() { - this.__lock(); - try { - var that = this; - decree(defs.args.blur)(arguments, function(sigma, callback) { - that.__lwip.blur(+sigma, function(err) { - that.__release(); - callback(err, that); - }); - }); - } catch (e) { - this.__release(); - throw e; - } - } - - image.prototype.hslaAdjust = function() { - this.__lock(); - try { - var that = this; - decree(defs.args.hslaAdjust)(arguments, function(hs, sd, ld, ad, callback) { - that.__lwip.hslaAdj(+hs, +sd, +ld, +ad, function(err) { - that.__release(); - callback(err, that); - }); - }); - } catch (e) { - this.__release(); - throw e; - } - } - - image.prototype.saturate = function() { - this.__lock(); - try { - var that = this; - decree(defs.args.saturate)(arguments, function(delta, callback) { - that.__lwip.hslaAdj(0, +delta, 0, 0, function(err) { - that.__release(); - callback(err, that); - }); - }); - } catch (e) { - this.__release(); - throw e; - } - } - - image.prototype.lighten = function() { - this.__lock(); - try { - var that = this; - decree(defs.args.lighten)(arguments, function(delta, callback) { - that.__lwip.hslaAdj(0, 0, +delta, 0, function(err) { - that.__release(); - callback(err, that); - }); - }); - } catch (e) { - this.__release(); - throw e; - } - } - - image.prototype.darken = function() { - this.__lock(); - try { - var that = this; - decree(defs.args.darken)(arguments, function(delta, callback) { - that.__lwip.hslaAdj(0, 0, -delta, 0, function(err) { - that.__release(); - callback(err, that); - }); - }); - } catch (e) { - this.__release(); - throw e; - } - } - - image.prototype.fade = function() { - this.__lock(); - try { - var that = this; - decree(defs.args.fade)(arguments, function(delta, callback) { - that.__lwip.hslaAdj(0, 0, 0, -delta, function(err) { - if (+delta > 0) that.__trans = true; - that.__release(); - callback(err, that); - }); - }); - } catch (e) { - this.__release(); - throw e; - } - } - - image.prototype.opacify = function() { - this.__lock(); - try { - var that = this; - decree(defs.args.opacify)(arguments, function(callback) { - that.__lwip.opacify(function(err) { - that.__trans = false; - that.__release(); - callback(err, that); - }); - }); - } catch (e) { - this.__release(); - throw e; - } - } - - image.prototype.hue = function() { - this.__lock(); - try { - var that = this; - decree(defs.args.hue)(arguments, function(shift, callback) { - that.__lwip.hslaAdj(+shift, 0, 0, 0, function(err) { - that.__release(); - callback(err, that); - }); - }); - } catch (e) { - this.__release(); - throw e; - } - } - - image.prototype.crop = function() { - this.__lock(); - try { - var that = this; - decree(defs.args.crop)(arguments, function(left, top, right, bottom, callback) { - if (!right && !bottom) { - var size = that.size(), - width = left, - height = top; - left = 0 | (size.width - width) / 2; - top = 0 | (size.height - height) / 2; - right = left + width - 1; - bottom = top + height - 1; - } - that.__lwip.crop(left, top, right, bottom, function(err) { - that.__release(); - callback(err, that); - }); - }); - } catch (e) { - this.__release(); - throw e; - } - } - - image.prototype.mirror = function() { - this.__lock(); - try { - var that = this; - decree(defs.args.mirror)(arguments, function(axes, callback) { - var xaxis = false, - yaxis = false; - axes = axes.toLowerCase(); - if ('x' === axes) xaxis = true; - if ('y' === axes) yaxis = true; - if ('xy' === axes || 'yx' === axes) { - xaxis = true; - yaxis = true; - } - if (!(xaxis || yaxis)) throw Error('Invalid axes'); - that.__lwip.mirror(xaxis, yaxis, function(err) { - that.__release(); - callback(err, that); - }); - }); - } catch (e) { - this.__release(); - throw e; - } - } - - // mirror alias: - image.prototype.flip = image.prototype.mirror; - - image.prototype.pad = function() { - this.__lock(); - try { - var that = this; - decree(defs.args.pad)(arguments, function(left, top, right, bottom, color, callback) { - color = normalizeColor(color); - if (color.a < 100) that.__trans = true; - that.__lwip.pad(+left, +top, +right, +bottom, +color.r, +color.g, +color.b, +color.a, function(err) { - that.__release(); - callback(err, that); - }); - }); - } catch (e) { - this.__release(); - throw e; - } - } - - image.prototype.border = function() { - this.__lock(); - try { - var that = this; - decree(defs.args.border)(arguments, function(width, color, callback) { - color = normalizeColor(color); - if (color.a < 100) that.__trans = true; - // we can just use image.pad... - that.__lwip.pad(+width, +width, +width, +width, +color.r, +color.g, +color.b, +color.a, function(err) { - that.__release(); - callback(err, that); - }); - }); - } catch (e) { - this.__release(); - throw e; - } - } - - image.prototype.sharpen = function() { - this.__lock(); - try { - var that = this; - decree(defs.args.sharpen)(arguments, function(amplitude, callback) { - that.__lwip.sharpen(+amplitude, function(err) { - that.__release(); - callback(err, that); - }); - }); - } catch (e) { - this.__release(); - throw e; - } - } - - image.prototype.clone = function() { - // no need to lock the image. we don't modify the memory buffer. - // just copy it. - var that = this; - decree(defs.args.clone)(arguments, function(callback) { - // first we retrieve what we need (buffer, dimensions, ...) - // synchronously so that the original image doesn't have a chance - // to be changed (remember, we don't lock it); and only then call - // the callback asynchronously. - var pixbuff = that.__lwip.buffer(), - width = that.__lwip.width(), - height = that.__lwip.height(), - trans = that.__trans; - setImmediate(function() { - callback(null, new image(pixbuff, width, height, trans)); - }); - }); - } - - image.prototype.extract = function() { - // no need to lock the image. we don't modify the memory buffer. - // just copy it and then crop it. - var that = this; - decree(defs.args.extract)(arguments, function(left, top, right, bottom, callback) { - // first we retrieve what we need (buffer, dimensions, ...) - // synchronously so that the original image doesn't have a chance - // to be changed (remember, we don't lock it); then we crop it and - // only call the callback asynchronously. - var pixbuff = that.__lwip.buffer(), - width = that.__lwip.width(), - height = that.__lwip.height(), - trans = that.__trans, - eximg = new image(pixbuff, width, height, trans); - eximg.__lwip.crop(left, top, right, bottom, function(err) { - callback(err, eximg); - }); - }); - } - - image.prototype.toBuffer = function() { - this.__lock(); - try { - var that = this; - decree(defs.args.toBuffer)(arguments, function(type, params, callback) { - if (type === 'jpg' || type === 'jpeg') { - if (params.quality != 0) - params.quality = params.quality || defs.defaults.DEF_JPEG_QUALITY; - if (params.quality != parseInt(params.quality) || params.quality < 0 || params.quality > 100) - throw Error('Invalid JPEG quality'); - return encoder.jpeg( - that.__lwip.buffer(), - that.__lwip.width(), - that.__lwip.height(), - params.quality, - function(err, buffer) { - that.__release(); - callback(err, buffer); - } - ); - } else if (type === 'png') { - params.compression = params.compression || defs.defaults.PNG_DEF_COMPRESSION; - if (params.compression === 'none') params.compression = 0; - else if (params.compression === 'fast') params.compression = 1; - else if (params.compression === 'high') params.compression = 2; - else throw Error('Invalid PNG compression'); - params.interlaced = params.interlaced || defs.defaults.PNG_DEF_INTERLACED; - if (typeof params.interlaced !== 'boolean') throw Error('PNG \'interlaced\' must be boolean'); - params.transparency = params.transparency || defs.defaults.PNG_DEF_TRANSPARENT; - if (typeof params.transparency !== 'boolean' && params.transparency.toLowerCase() !== 'auto') - throw Error('PNG \'transparency\' must be boolean or \'auto\''); - if (params.transparency.toLowerCase() !== 'auto') params.transparency = that.__trans; - return encoder.png( - that.__lwip.buffer(), - that.__lwip.width(), - that.__lwip.height(), - params.compression, - params.interlaced, - params.transparency, - function(err, buffer) { - that.__release(); - callback(err, buffer); - } - ); - } else throw Error('Unknown type \'' + type + '\''); - }); - } catch (e) { - this.__release(); - throw e; - } - } - - image.prototype.writeFile = function() { - var that = this; - decree(defs.args.writeFile)(arguments, function(outpath, type, params, callback) { - type = type || path.extname(outpath).slice(1).toLowerCase(); - that.toBuffer(type, params, function(err, buffer) { - if (err) return callback(err); - fs.writeFile(outpath, buffer, { - encoding: 'binary' - }, callback); - }); - }); - } - - image.prototype.batch = function() { - return new batch(this); - } - - function batch(image) { - this.__image = image; - this.__queue = []; - this.__running = false; - this.__addOp = function(handle, args) { - this.__queue.push({ - handle: handle, - args: args - }); - }; - } - - batch.prototype.exec = function(callback) { - var that = this; - if (that.__running) throw Error("Batch is already running"); - that.__running = true; - async.eachSeries(this.__queue, function(op, done) { - op.args.push(done); - op.handle.apply(that.__image, op.args); - }, function(err) { - that.__queue.length = 0; // queue is now empty - that.__running = false; - callback(err, that.__image); - }); - } - - batch.prototype.scale = function() { - var that = this, - decs = defs.args.scale.slice(0, -1); // cut callback declaration - decree(decs)(arguments, function(wRatio, hRatio, inter) { - if (!defs.interpolations[inter]) throw Error("Unknown interpolation " + inter); - that.__addOp(that.__image.scale, [wRatio, hRatio, inter].filter(undefinedFilter)); - }); - return this; - } - - batch.prototype.resize = function() { - var that = this, - decs = defs.args.resize.slice(0, -1); // cut callback declaration - decree(decs)(arguments, function(width, height, inter) { - if (!defs.interpolations[inter]) throw Error("Unknown interpolation " + inter); - that.__addOp(that.__image.resize, [width, height, inter].filter(undefinedFilter)); - }); - return this; - } - - batch.prototype.rotate = function() { - var that = this, - decs = defs.args.rotate.slice(0, -1); // cut callback declaration - decree(decs)(arguments, function(degs, color) { - color = normalizeColor(color); - that.__addOp(that.__image.rotate, [degs, color].filter(undefinedFilter)); - }); - return this; - } - - batch.prototype.blur = function() { - var that = this, - decs = defs.args.blur.slice(0, -1); // cut callback declaration - decree(decs)(arguments, function(sigma) { - that.__addOp(that.__image.blur, [sigma].filter(undefinedFilter)); - }); - return this; - } - - batch.prototype.hslaAdjust = function() { - var that = this, - decs = defs.args.hslaAdjust.slice(0, -1); // cut callback declaration - decree(decs)(arguments, function(hs, sd, ld, ad) { - that.__addOp(that.__image.hslaAdjust, [hs, sd, ld, ad].filter(undefinedFilter)); - }); - return this; - } - - batch.prototype.saturate = function() { - var that = this, - decs = defs.args.saturate.slice(0, -1); // cut callback declaration - decree(decs)(arguments, function(delta) { - that.__addOp(that.__image.saturate, [delta].filter(undefinedFilter)); - }); - return this; - } - - batch.prototype.lighten = function() { - var that = this, - decs = defs.args.lighten.slice(0, -1); // cut callback declaration - decree(decs)(arguments, function(delta) { - that.__addOp(that.__image.lighten, [delta].filter(undefinedFilter)); - }); - return this; - } - - batch.prototype.darken = function() { - var that = this, - decs = defs.args.darken.slice(0, -1); // cut callback declaration - decree(decs)(arguments, function(delta) { - that.__addOp(that.__image.darken, [delta].filter(undefinedFilter)); - }); - return this; - } - - batch.prototype.fade = function() { - var that = this, - decs = defs.args.fade.slice(0, -1); // cut callback declaration - decree(decs)(arguments, function(delta) { - that.__addOp(that.__image.fade, [delta].filter(undefinedFilter)); - }); - return this; - } - - batch.prototype.opacify = function() { - this.__addOp(this.__image.opacify, []); - return this; - } - - batch.prototype.hue = function() { - var that = this, - decs = defs.args.hue.slice(0, -1); // cut callback declaration - decree(decs)(arguments, function(shift) { - that.__addOp(that.__image.hue, [shift].filter(undefinedFilter)); - }); - return this; - } - - batch.prototype.crop = function() { - var that = this, - decs = defs.args.crop.slice(0, -1); // cut callback declaration - decree(decs)(arguments, function(left, top, right, bottom) { - that.__addOp(that.__image.crop, [left, top, right, bottom].filter(undefinedFilter)); - }); - return this; - } - - batch.prototype.mirror = function() { - var that = this, - decs = defs.args.mirror.slice(0, -1); // cut callback declaration - decree(decs)(arguments, function(axes) { - axes = axes.toLowerCase(); - if (['x', 'y', 'xy', 'yx'].indexOf(axes) === -1) throw Error('Invalid axes'); - that.__addOp(that.__image.mirror, [axes].filter(undefinedFilter)); - }); - return this; - } - - // mirror alias: - batch.prototype.flip = batch.prototype.mirror; - - batch.prototype.pad = function() { - var that = this, - decs = defs.args.pad.slice(0, -1); // cut callback declaration - decree(decs)(arguments, function(left, top, right, bottom, color) { - color = normalizeColor(color); - that.__addOp(that.__image.pad, [left, top, right, bottom, color].filter(undefinedFilter)); - }); - return this; - } - - batch.prototype.border = function() { - var that = this, - decs = defs.args.border.slice(0, -1); // cut callback declaration - decree(decs)(arguments, function(width, color) { - color = normalizeColor(color); - that.__addOp(that.__image.border, [width, color].filter(undefinedFilter)); - }); - return this; - } - - batch.prototype.sharpen = function() { - var that = this, - decs = defs.args.sharpen.slice(0, -1); // cut callback declaration - decree(decs)(arguments, function(amplitude) { - that.__addOp(that.__image.sharpen, [amplitude].filter(undefinedFilter)); - }); - return this; - } - - batch.prototype.toBuffer = function() { - var that = this; - decree(defs.args.toBuffer)(arguments, function(type, params, callback) { - if (type === 'jpg' || type === 'jpeg') { - if (params.quality != 0) - params.quality = params.quality || defs.defaults.DEF_JPEG_QUALITY; - if (params.quality != parseInt(params.quality) || params.quality < 0 || params.quality > 100) - throw Error('Invalid JPEG quality'); - } else if (type === 'png') { - params.compression = params.compression || defs.defaults.PNG_DEF_COMPRESSION; - if (['none', 'fast', 'high'].indexOf(params.compression) === -1) - throw Error('Invalid PNG compression'); - params.interlaced = params.interlaced || defs.defaults.PNG_DEF_INTERLACED; - if (typeof params.interlaced !== 'boolean') throw Error('PNG \'interlaced\' must be boolean'); - params.transparency = params.transparency || defs.defaults.PNG_DEF_TRANSPARENT; - if (typeof params.transparency !== 'boolean' && params.transparency.toLowerCase() !== 'auto') - throw Error('PNG \'transparency\' must be boolean or \'auto\''); - } else throw Error('Unknown type \'' + type + '\''); - that.exec(function(err, image) { - if (err) return callback(err); - image.toBuffer(type, params, callback); - }); - }); - } - - batch.prototype.writeFile = function(outpath, type, params, callback) { - var that = this; - decree(defs.args.writeFile)(arguments, function(outpath, type, params, callback) { - type = type || path.extname(outpath).slice(1).toLowerCase(); - that.toBuffer(type, params, function(err, buffer) { - if (err) return callback(err); - fs.writeFile(outpath, buffer, { - encoding: 'binary' - }, callback); - }); - }); - } - - function open() { - decree(defs.args.open)(arguments, function(source, type, callback) { - if (typeof source === 'string') { - type = type || path.extname(source).slice(1); - var opener = getOpener(type); - fs.readFile(source, function(err, imbuff) { - if (err) return callback(err); - opener(imbuff, function(err, pixelsBuf, width, height, channels, trans) { - callback(err, err ? undefined : new image(pixelsBuf, width, height, trans)); - }); - }); - } else if (source instanceof Buffer) { - var opener = getOpener(type); - opener(source, function(err, pixelsBuf, width, height, channels, trans) { - callback(err, err ? undefined : new image(pixelsBuf, width, height, trans)); - }); - } else throw Error("Invalid source"); - }); - } - - function getOpener(ext) { - ext = ext.toLowerCase(); - for (var i = 0; i < openers.length; i++) { - var opener = openers[i].opener, - exts = openers[i].exts; - if (exts.indexOf(ext) !== -1) return opener; - } - throw Error('Unknown type \'' + ext + '\''); - } - - function create() { - decree(defs.args.create)(arguments, function(width, height, color, callback) { - color = normalizeColor(color); - var trans = color.a < 100, - c_len = width * height, - pixelsBuf = new Buffer(c_len * 4); - for (var i = 0; i < width * height; i++) { - pixelsBuf[i] = color.r; - pixelsBuf[c_len + i] = color.g; - pixelsBuf[2 * c_len + i] = color.b; - pixelsBuf[3 * c_len + i] = color.a; - } - setImmediate(function() { - callback(null, new image(pixelsBuf, width, height, trans)); - }); - }); - } - - // EXPORTS - // ------- - module.exports = { - open: open, - create: create - }; -})(void 0); +module.exports = require('./lib/obtain'); diff --git a/lib/Batch.js b/lib/Batch.js new file mode 100644 index 00000000..dd351212 --- /dev/null +++ b/lib/Batch.js @@ -0,0 +1,233 @@ +(function(undefined) { + + var path = require('path'), + fs = require('fs'), + async = require('async'), + decree = require('decree'), + defs = require('./defs'), + util = require('./util'), + Image = require('./Image'); + + + var undefinedFilter = util.undefinedFilter, + normalizeColor = util.normalizeColor; + + function Batch(image) { + this.__image = image; + this.__queue = []; + this.__running = false; + this.__addOp = function(handle, args) { + this.__queue.push({ + handle: handle, + args: args + }); + }; + } + + Batch.prototype.exec = function(callback) { + var that = this; + if (that.__running) throw Error("Batch is already running"); + that.__running = true; + async.eachSeries(this.__queue, function(op, done) { + op.args.push(done); + op.handle.apply(that.__image, op.args); + }, function(err) { + that.__queue.length = 0; // queue is now empty + that.__running = false; + callback(err, that.__image); + }); + } + + Batch.prototype.scale = function() { + var that = this, + decs = defs.args.scale.slice(0, -1); // cut callback declaration + decree(decs)(arguments, function(wRatio, hRatio, inter) { + if (!defs.interpolations[inter]) throw Error("Unknown interpolation " + inter); + that.__addOp(that.__image.scale, [wRatio, hRatio, inter].filter(undefinedFilter)); + }); + return this; + } + + Batch.prototype.resize = function() { + var that = this, + decs = defs.args.resize.slice(0, -1); // cut callback declaration + decree(decs)(arguments, function(width, height, inter) { + if (!defs.interpolations[inter]) throw Error("Unknown interpolation " + inter); + that.__addOp(that.__image.resize, [width, height, inter].filter(undefinedFilter)); + }); + return this; + } + + Batch.prototype.rotate = function() { + var that = this, + decs = defs.args.rotate.slice(0, -1); // cut callback declaration + decree(decs)(arguments, function(degs, color) { + color = normalizeColor(color); + that.__addOp(that.__image.rotate, [degs, color].filter(undefinedFilter)); + }); + return this; + } + + Batch.prototype.blur = function() { + var that = this, + decs = defs.args.blur.slice(0, -1); // cut callback declaration + decree(decs)(arguments, function(sigma) { + that.__addOp(that.__image.blur, [sigma].filter(undefinedFilter)); + }); + return this; + } + + Batch.prototype.hslaAdjust = function() { + var that = this, + decs = defs.args.hslaAdjust.slice(0, -1); // cut callback declaration + decree(decs)(arguments, function(hs, sd, ld, ad) { + that.__addOp(that.__image.hslaAdjust, [hs, sd, ld, ad].filter(undefinedFilter)); + }); + return this; + } + + Batch.prototype.saturate = function() { + var that = this, + decs = defs.args.saturate.slice(0, -1); // cut callback declaration + decree(decs)(arguments, function(delta) { + that.__addOp(that.__image.saturate, [delta].filter(undefinedFilter)); + }); + return this; + } + + Batch.prototype.lighten = function() { + var that = this, + decs = defs.args.lighten.slice(0, -1); // cut callback declaration + decree(decs)(arguments, function(delta) { + that.__addOp(that.__image.lighten, [delta].filter(undefinedFilter)); + }); + return this; + } + + Batch.prototype.darken = function() { + var that = this, + decs = defs.args.darken.slice(0, -1); // cut callback declaration + decree(decs)(arguments, function(delta) { + that.__addOp(that.__image.darken, [delta].filter(undefinedFilter)); + }); + return this; + } + + Batch.prototype.fade = function() { + var that = this, + decs = defs.args.fade.slice(0, -1); // cut callback declaration + decree(decs)(arguments, function(delta) { + that.__addOp(that.__image.fade, [delta].filter(undefinedFilter)); + }); + return this; + } + + Batch.prototype.opacify = function() { + this.__addOp(this.__image.opacify, []); + return this; + } + + Batch.prototype.hue = function() { + var that = this, + decs = defs.args.hue.slice(0, -1); // cut callback declaration + decree(decs)(arguments, function(shift) { + that.__addOp(that.__image.hue, [shift].filter(undefinedFilter)); + }); + return this; + } + + Batch.prototype.crop = function() { + var that = this, + decs = defs.args.crop.slice(0, -1); // cut callback declaration + decree(decs)(arguments, function(left, top, right, bottom) { + that.__addOp(that.__image.crop, [left, top, right, bottom].filter(undefinedFilter)); + }); + return this; + } + + Batch.prototype.mirror = function() { + var that = this, + decs = defs.args.mirror.slice(0, -1); // cut callback declaration + decree(decs)(arguments, function(axes) { + axes = axes.toLowerCase(); + if (['x', 'y', 'xy', 'yx'].indexOf(axes) === -1) throw Error('Invalid axes'); + that.__addOp(that.__image.mirror, [axes].filter(undefinedFilter)); + }); + return this; + } + + // mirror alias: + Batch.prototype.flip = Batch.prototype.mirror; + + Batch.prototype.pad = function() { + var that = this, + decs = defs.args.pad.slice(0, -1); // cut callback declaration + decree(decs)(arguments, function(left, top, right, bottom, color) { + color = normalizeColor(color); + that.__addOp(that.__image.pad, [left, top, right, bottom, color].filter(undefinedFilter)); + }); + return this; + } + + Batch.prototype.border = function() { + var that = this, + decs = defs.args.border.slice(0, -1); // cut callback declaration + decree(decs)(arguments, function(width, color) { + color = normalizeColor(color); + that.__addOp(that.__image.border, [width, color].filter(undefinedFilter)); + }); + return this; + } + + Batch.prototype.sharpen = function() { + var that = this, + decs = defs.args.sharpen.slice(0, -1); // cut callback declaration + decree(decs)(arguments, function(amplitude) { + that.__addOp(that.__image.sharpen, [amplitude].filter(undefinedFilter)); + }); + return this; + } + + Batch.prototype.toBuffer = function() { + var that = this; + decree(defs.args.toBuffer)(arguments, function(type, params, callback) { + if (type === 'jpg' || type === 'jpeg') { + if (params.quality != 0) + params.quality = params.quality || defs.defaults.DEF_JPEG_QUALITY; + if (params.quality != parseInt(params.quality) || params.quality < 0 || params.quality > 100) + throw Error('Invalid JPEG quality'); + } else if (type === 'png') { + params.compression = params.compression || defs.defaults.PNG_DEF_COMPRESSION; + if (['none', 'fast', 'high'].indexOf(params.compression) === -1) + throw Error('Invalid PNG compression'); + params.interlaced = params.interlaced || defs.defaults.PNG_DEF_INTERLACED; + if (typeof params.interlaced !== 'boolean') throw Error('PNG \'interlaced\' must be boolean'); + params.transparency = params.transparency || defs.defaults.PNG_DEF_TRANSPARENT; + if (typeof params.transparency !== 'boolean' && params.transparency.toLowerCase() !== 'auto') + throw Error('PNG \'transparency\' must be boolean or \'auto\''); + } else throw Error('Unknown type \'' + type + '\''); + that.exec(function(err, image) { + if (err) return callback(err); + image.toBuffer(type, params, callback); + }); + }); + } + + Batch.prototype.writeFile = function(outpath, type, params, callback) { + var that = this; + decree(defs.args.writeFile)(arguments, function(outpath, type, params, callback) { + type = type || path.extname(outpath).slice(1).toLowerCase(); + that.toBuffer(type, params, function(err, buffer) { + if (err) return callback(err); + fs.writeFile(outpath, buffer, { + encoding: 'binary' + }, callback); + }); + }); + } + + // EXPORTS + // ------- + module.exports = Batch; + +})(void 0); diff --git a/lib/Image.js b/lib/Image.js new file mode 100644 index 00000000..08693c1e --- /dev/null +++ b/lib/Image.js @@ -0,0 +1,450 @@ +(function(undefined) { + + var path = require('path'), + fs = require('fs'), + decree = require('decree'), + defs = require('./defs'), + util = require('./util'), + encoder = require('../build/Release/lwip_encoder'), + lwip_image = require('../build/Release/lwip_image'), + Batch = require('./Batch'); + + var undefinedFilter = util.undefinedFilter, + normalizeColor = util.normalizeColor; + + function Image(pixelsBuf, width, height, trans) { + this.__lwip = new lwip_image.LwipImage(pixelsBuf, width, height); + this.__locked = false; + this.__trans = trans; + } + + Image.prototype.__lock = function() { + if (!this.__locked) this.__locked = true; + else throw Error("Another image operation already in progress"); + }; + + Image.prototype.__release = function() { + this.__locked = false; + }; + + Image.prototype.width = function() { + return this.__lwip.width(); + } + + Image.prototype.height = function() { + return this.__lwip.height(); + } + + Image.prototype.size = function() { + return { + width: this.__lwip.width(), + height: this.__lwip.height() + }; + } + + Image.prototype.scale = function() { + this.__lock(); + try { + var that = this; + decree(defs.args.scale)(arguments, function(wRatio, hRatio, inter, callback) { + if (!defs.interpolations[inter]) throw Error("Unknown interpolation " + inter); + hRatio = hRatio || wRatio; + var width = +wRatio * that.width(), + height = +hRatio * that.height(); + that.__lwip.resize(width, height, defs.interpolations[inter], function(err) { + that.__release(); + callback(err, that); + }); + }); + } catch (e) { + this.__release(); + throw e; + } + } + + Image.prototype.resize = function() { + this.__lock(); + try { + var that = this; + decree(defs.args.resize)(arguments, function(width, height, inter, callback) { + if (!defs.interpolations[inter]) throw Error("Unknown interpolation " + inter); + height = height || width; + that.__lwip.resize(+width, +height, defs.interpolations[inter], function(err) { + that.__release(); + callback(err, that); + }); + }); + } catch (e) { + this.__release(); + throw e; + } + } + + Image.prototype.rotate = function() { + this.__lock(); + try { + var that = this; + decree(defs.args.rotate)(arguments, function(degs, color, callback) { + color = normalizeColor(color); + if (color.a < 100) that.__trans = true; + that.__lwip.rotate(+degs, +color.r, +color.g, +color.b, +color.a, function(err) { + that.__release(); + callback(err, that); + }); + }); + } catch (e) { + this.__release(); + throw e; + } + } + + Image.prototype.blur = function() { + this.__lock(); + try { + var that = this; + decree(defs.args.blur)(arguments, function(sigma, callback) { + that.__lwip.blur(+sigma, function(err) { + that.__release(); + callback(err, that); + }); + }); + } catch (e) { + this.__release(); + throw e; + } + } + + Image.prototype.hslaAdjust = function() { + this.__lock(); + try { + var that = this; + decree(defs.args.hslaAdjust)(arguments, function(hs, sd, ld, ad, callback) { + that.__lwip.hslaAdj(+hs, +sd, +ld, +ad, function(err) { + that.__release(); + callback(err, that); + }); + }); + } catch (e) { + this.__release(); + throw e; + } + } + + Image.prototype.saturate = function() { + this.__lock(); + try { + var that = this; + decree(defs.args.saturate)(arguments, function(delta, callback) { + that.__lwip.hslaAdj(0, +delta, 0, 0, function(err) { + that.__release(); + callback(err, that); + }); + }); + } catch (e) { + this.__release(); + throw e; + } + } + + Image.prototype.lighten = function() { + this.__lock(); + try { + var that = this; + decree(defs.args.lighten)(arguments, function(delta, callback) { + that.__lwip.hslaAdj(0, 0, +delta, 0, function(err) { + that.__release(); + callback(err, that); + }); + }); + } catch (e) { + this.__release(); + throw e; + } + } + + Image.prototype.darken = function() { + this.__lock(); + try { + var that = this; + decree(defs.args.darken)(arguments, function(delta, callback) { + that.__lwip.hslaAdj(0, 0, -delta, 0, function(err) { + that.__release(); + callback(err, that); + }); + }); + } catch (e) { + this.__release(); + throw e; + } + } + + Image.prototype.fade = function() { + this.__lock(); + try { + var that = this; + decree(defs.args.fade)(arguments, function(delta, callback) { + that.__lwip.hslaAdj(0, 0, 0, -delta, function(err) { + if (+delta > 0) that.__trans = true; + that.__release(); + callback(err, that); + }); + }); + } catch (e) { + this.__release(); + throw e; + } + } + + Image.prototype.opacify = function() { + this.__lock(); + try { + var that = this; + decree(defs.args.opacify)(arguments, function(callback) { + that.__lwip.opacify(function(err) { + that.__trans = false; + that.__release(); + callback(err, that); + }); + }); + } catch (e) { + this.__release(); + throw e; + } + } + + Image.prototype.hue = function() { + this.__lock(); + try { + var that = this; + decree(defs.args.hue)(arguments, function(shift, callback) { + that.__lwip.hslaAdj(+shift, 0, 0, 0, function(err) { + that.__release(); + callback(err, that); + }); + }); + } catch (e) { + this.__release(); + throw e; + } + } + + Image.prototype.crop = function() { + this.__lock(); + try { + var that = this; + decree(defs.args.crop)(arguments, function(left, top, right, bottom, callback) { + if (!right && !bottom) { + var size = that.size(), + width = left, + height = top; + left = 0 | (size.width - width) / 2; + top = 0 | (size.height - height) / 2; + right = left + width - 1; + bottom = top + height - 1; + } + that.__lwip.crop(left, top, right, bottom, function(err) { + that.__release(); + callback(err, that); + }); + }); + } catch (e) { + this.__release(); + throw e; + } + } + + Image.prototype.mirror = function() { + this.__lock(); + try { + var that = this; + decree(defs.args.mirror)(arguments, function(axes, callback) { + var xaxis = false, + yaxis = false; + axes = axes.toLowerCase(); + if ('x' === axes) xaxis = true; + if ('y' === axes) yaxis = true; + if ('xy' === axes || 'yx' === axes) { + xaxis = true; + yaxis = true; + } + if (!(xaxis || yaxis)) throw Error('Invalid axes'); + that.__lwip.mirror(xaxis, yaxis, function(err) { + that.__release(); + callback(err, that); + }); + }); + } catch (e) { + this.__release(); + throw e; + } + } + + // mirror alias: + Image.prototype.flip = Image.prototype.mirror; + + Image.prototype.pad = function() { + this.__lock(); + try { + var that = this; + decree(defs.args.pad)(arguments, function(left, top, right, bottom, color, callback) { + color = normalizeColor(color); + if (color.a < 100) that.__trans = true; + that.__lwip.pad(+left, +top, +right, +bottom, +color.r, +color.g, +color.b, +color.a, function(err) { + that.__release(); + callback(err, that); + }); + }); + } catch (e) { + this.__release(); + throw e; + } + } + + Image.prototype.border = function() { + this.__lock(); + try { + var that = this; + decree(defs.args.border)(arguments, function(width, color, callback) { + color = normalizeColor(color); + if (color.a < 100) that.__trans = true; + // we can just use image.pad... + that.__lwip.pad(+width, +width, +width, +width, +color.r, +color.g, +color.b, +color.a, function(err) { + that.__release(); + callback(err, that); + }); + }); + } catch (e) { + this.__release(); + throw e; + } + } + + Image.prototype.sharpen = function() { + this.__lock(); + try { + var that = this; + decree(defs.args.sharpen)(arguments, function(amplitude, callback) { + that.__lwip.sharpen(+amplitude, function(err) { + that.__release(); + callback(err, that); + }); + }); + } catch (e) { + this.__release(); + throw e; + } + } + + Image.prototype.clone = function() { + // no need to lock the image. we don't modify the memory buffer. + // just copy it. + var that = this; + decree(defs.args.clone)(arguments, function(callback) { + // first we retrieve what we need (buffer, dimensions, ...) + // synchronously so that the original image doesn't have a chance + // to be changed (remember, we don't lock it); and only then call + // the callback asynchronously. + var pixbuff = that.__lwip.buffer(), + width = that.__lwip.width(), + height = that.__lwip.height(), + trans = that.__trans; + setImmediate(function() { + callback(null, new Image(pixbuff, width, height, trans)); + }); + }); + } + + Image.prototype.extract = function() { + // no need to lock the image. we don't modify the memory buffer. + // just copy it and then crop it. + var that = this; + decree(defs.args.extract)(arguments, function(left, top, right, bottom, callback) { + // first we retrieve what we need (buffer, dimensions, ...) + // synchronously so that the original image doesn't have a chance + // to be changed (remember, we don't lock it); then we crop it and + // only call the callback asynchronously. + var pixbuff = that.__lwip.buffer(), + width = that.__lwip.width(), + height = that.__lwip.height(), + trans = that.__trans, + eximg = new Image(pixbuff, width, height, trans); + eximg.__lwip.crop(left, top, right, bottom, function(err) { + callback(err, eximg); + }); + }); + } + + Image.prototype.toBuffer = function() { + this.__lock(); + try { + var that = this; + decree(defs.args.toBuffer)(arguments, function(type, params, callback) { + if (type === 'jpg' || type === 'jpeg') { + if (params.quality != 0) + params.quality = params.quality || defs.defaults.DEF_JPEG_QUALITY; + if (params.quality != parseInt(params.quality) || params.quality < 0 || params.quality > 100) + throw Error('Invalid JPEG quality'); + return encoder.jpeg( + that.__lwip.buffer(), + that.__lwip.width(), + that.__lwip.height(), + params.quality, + function(err, buffer) { + that.__release(); + callback(err, buffer); + } + ); + } else if (type === 'png') { + params.compression = params.compression || defs.defaults.PNG_DEF_COMPRESSION; + if (params.compression === 'none') params.compression = 0; + else if (params.compression === 'fast') params.compression = 1; + else if (params.compression === 'high') params.compression = 2; + else throw Error('Invalid PNG compression'); + params.interlaced = params.interlaced || defs.defaults.PNG_DEF_INTERLACED; + if (typeof params.interlaced !== 'boolean') throw Error('PNG \'interlaced\' must be boolean'); + params.transparency = params.transparency || defs.defaults.PNG_DEF_TRANSPARENT; + if (typeof params.transparency !== 'boolean' && params.transparency.toLowerCase() !== 'auto') + throw Error('PNG \'transparency\' must be boolean or \'auto\''); + if (params.transparency.toLowerCase() !== 'auto') params.transparency = that.__trans; + return encoder.png( + that.__lwip.buffer(), + that.__lwip.width(), + that.__lwip.height(), + params.compression, + params.interlaced, + params.transparency, + function(err, buffer) { + that.__release(); + callback(err, buffer); + } + ); + } else throw Error('Unknown type \'' + type + '\''); + }); + } catch (e) { + this.__release(); + throw e; + } + } + + Image.prototype.writeFile = function() { + var that = this; + decree(defs.args.writeFile)(arguments, function(outpath, type, params, callback) { + type = type || path.extname(outpath).slice(1).toLowerCase(); + that.toBuffer(type, params, function(err, buffer) { + if (err) return callback(err); + fs.writeFile(outpath, buffer, { + encoding: 'binary' + }, callback); + }); + }); + } + + Image.prototype.batch = function() { + return new Batch(this); + } + + // EXPORTS + // ------- + module.exports = Image; + +})(void 0); diff --git a/defs.js b/lib/defs.js similarity index 100% rename from defs.js rename to lib/defs.js diff --git a/lib/obtain.js b/lib/obtain.js new file mode 100644 index 00000000..476fb168 --- /dev/null +++ b/lib/obtain.js @@ -0,0 +1,73 @@ +(function(undefined) { + + var path = require('path'), + fs = require('fs'), + decree = require('decree'), + defs = require('./defs'), + util = require('./util'), + decoder = require('../build/Release/lwip_decoder'), + Image = require('./Image'); + + var openers = [{ + exts: ['jpg', 'jpeg'], + opener: decoder.jpeg + }, { + exts: ['png'], + opener: decoder.png + }]; + + function open() { + decree(defs.args.open)(arguments, function(source, type, callback) { + if (typeof source === 'string') { + type = type || path.extname(source).slice(1); + var opener = getOpener(type); + fs.readFile(source, function(err, imbuff) { + if (err) return callback(err); + opener(imbuff, function(err, pixelsBuf, width, height, channels, trans) { + callback(err, err ? null : new Image(pixelsBuf, width, height, trans)); + }); + }); + } else if (source instanceof Buffer) { + var opener = getOpener(type); + opener(source, function(err, pixelsBuf, width, height, channels, trans) { + callback(err, err ? null : new Image(pixelsBuf, width, height, trans)); + }); + } else throw Error("Invalid source"); + }); + } + + function getOpener(ext) { + ext = ext.toLowerCase(); + for (var i = 0; i < openers.length; i++) { + var opener = openers[i].opener, + exts = openers[i].exts; + if (exts.indexOf(ext) !== -1) return opener; + } + throw Error('Unknown type \'' + ext + '\''); + } + + function create() { + decree(defs.args.create)(arguments, function(width, height, color, callback) { + color = util.normalizeColor(color); + var trans = color.a < 100, + c_len = width * height, + pixelsBuf = new Buffer(c_len * 4); + for (var i = 0; i < width * height; i++) { + pixelsBuf[i] = color.r; + pixelsBuf[c_len + i] = color.g; + pixelsBuf[2 * c_len + i] = color.b; + pixelsBuf[3 * c_len + i] = color.a; + } + setImmediate(function() { + callback(null, new Image(pixelsBuf, width, height, trans)); + }); + }); + } + + // EXPORTS + // ------- + module.exports = { + open: open, + create: create + }; +})(void 0); diff --git a/lib/util.js b/lib/util.js new file mode 100644 index 00000000..bc357ae6 --- /dev/null +++ b/lib/util.js @@ -0,0 +1,40 @@ +(function(undefined) { + + var defs = require('./defs'); + + function undefinedFilter(v) { + return v !== undefined; + } + + function normalizeColor(color) { + if (typeof color === 'string') { + if (defs.colors[color]) color = defs.colors[color]; + else throw Error('Unknown color ' + color); + } else { + if (color instanceof Array) { + color = { + r: color[0], + g: color[1], + b: color[2], + a: color[3] + }; + } + if (color.a !== 0) color.a = color.a || defs.defaults.DEF_COLOR_ALPHA; + if (color.r != parseInt(color.r) || color.r < 0 || color.r > 255) + throw Error('\'red\' color component is invalid'); + if (color.g != parseInt(color.g) || color.g < 0 || color.g > 255) + throw Error('\'green\' color component is invalid'); + if (color.b != parseInt(color.b) || color.b < 0 || color.b > 255) + throw Error('\'blue\' color component is invalid'); + if (color.a != parseInt(color.a) || color.a < 0 || color.a > 100) + throw Error('\'alpha\' color component is invalid'); + } + return color; + } + + module.exports = { + undefinedFilter: undefinedFilter, + normalizeColor: normalizeColor + }; + +})(void 0); diff --git a/lib/cimg/CImg.h b/src/lib/cimg/CImg.h similarity index 100% rename from lib/cimg/CImg.h rename to src/lib/cimg/CImg.h diff --git a/lib/cimg/Licence_CeCILL-C_V1-en.txt b/src/lib/cimg/Licence_CeCILL-C_V1-en.txt similarity index 100% rename from lib/cimg/Licence_CeCILL-C_V1-en.txt rename to src/lib/cimg/Licence_CeCILL-C_V1-en.txt diff --git a/lib/cimg/Licence_CeCILL_V2-en.txt b/src/lib/cimg/Licence_CeCILL_V2-en.txt similarity index 100% rename from lib/cimg/Licence_CeCILL_V2-en.txt rename to src/lib/cimg/Licence_CeCILL_V2-en.txt diff --git a/lib/cimg/README.txt b/src/lib/cimg/README.txt similarity index 100% rename from lib/cimg/README.txt rename to src/lib/cimg/README.txt diff --git a/lib/jpeg/README b/src/lib/jpeg/README similarity index 100% rename from lib/jpeg/README rename to src/lib/jpeg/README diff --git a/lib/jpeg/jaricom.c b/src/lib/jpeg/jaricom.c similarity index 100% rename from lib/jpeg/jaricom.c rename to src/lib/jpeg/jaricom.c diff --git a/lib/jpeg/jcapimin.c b/src/lib/jpeg/jcapimin.c similarity index 100% rename from lib/jpeg/jcapimin.c rename to src/lib/jpeg/jcapimin.c diff --git a/lib/jpeg/jcapistd.c b/src/lib/jpeg/jcapistd.c similarity index 100% rename from lib/jpeg/jcapistd.c rename to src/lib/jpeg/jcapistd.c diff --git a/lib/jpeg/jcarith.c b/src/lib/jpeg/jcarith.c similarity index 100% rename from lib/jpeg/jcarith.c rename to src/lib/jpeg/jcarith.c diff --git a/lib/jpeg/jccoefct.c b/src/lib/jpeg/jccoefct.c similarity index 100% rename from lib/jpeg/jccoefct.c rename to src/lib/jpeg/jccoefct.c diff --git a/lib/jpeg/jccolor.c b/src/lib/jpeg/jccolor.c similarity index 100% rename from lib/jpeg/jccolor.c rename to src/lib/jpeg/jccolor.c diff --git a/lib/jpeg/jcdctmgr.c b/src/lib/jpeg/jcdctmgr.c similarity index 100% rename from lib/jpeg/jcdctmgr.c rename to src/lib/jpeg/jcdctmgr.c diff --git a/lib/jpeg/jchuff.c b/src/lib/jpeg/jchuff.c similarity index 100% rename from lib/jpeg/jchuff.c rename to src/lib/jpeg/jchuff.c diff --git a/lib/jpeg/jcinit.c b/src/lib/jpeg/jcinit.c similarity index 100% rename from lib/jpeg/jcinit.c rename to src/lib/jpeg/jcinit.c diff --git a/lib/jpeg/jcmainct.c b/src/lib/jpeg/jcmainct.c similarity index 100% rename from lib/jpeg/jcmainct.c rename to src/lib/jpeg/jcmainct.c diff --git a/lib/jpeg/jcmarker.c b/src/lib/jpeg/jcmarker.c similarity index 100% rename from lib/jpeg/jcmarker.c rename to src/lib/jpeg/jcmarker.c diff --git a/lib/jpeg/jcmaster.c b/src/lib/jpeg/jcmaster.c similarity index 100% rename from lib/jpeg/jcmaster.c rename to src/lib/jpeg/jcmaster.c diff --git a/lib/jpeg/jcomapi.c b/src/lib/jpeg/jcomapi.c similarity index 100% rename from lib/jpeg/jcomapi.c rename to src/lib/jpeg/jcomapi.c diff --git a/lib/jpeg/jconfig.h b/src/lib/jpeg/jconfig.h similarity index 100% rename from lib/jpeg/jconfig.h rename to src/lib/jpeg/jconfig.h diff --git a/lib/jpeg/jcparam.c b/src/lib/jpeg/jcparam.c similarity index 100% rename from lib/jpeg/jcparam.c rename to src/lib/jpeg/jcparam.c diff --git a/lib/jpeg/jcprepct.c b/src/lib/jpeg/jcprepct.c similarity index 100% rename from lib/jpeg/jcprepct.c rename to src/lib/jpeg/jcprepct.c diff --git a/lib/jpeg/jcsample.c b/src/lib/jpeg/jcsample.c similarity index 100% rename from lib/jpeg/jcsample.c rename to src/lib/jpeg/jcsample.c diff --git a/lib/jpeg/jdapimin.c b/src/lib/jpeg/jdapimin.c similarity index 100% rename from lib/jpeg/jdapimin.c rename to src/lib/jpeg/jdapimin.c diff --git a/lib/jpeg/jdapistd.c b/src/lib/jpeg/jdapistd.c similarity index 100% rename from lib/jpeg/jdapistd.c rename to src/lib/jpeg/jdapistd.c diff --git a/lib/jpeg/jdarith.c b/src/lib/jpeg/jdarith.c similarity index 100% rename from lib/jpeg/jdarith.c rename to src/lib/jpeg/jdarith.c diff --git a/lib/jpeg/jdatadst.c b/src/lib/jpeg/jdatadst.c similarity index 100% rename from lib/jpeg/jdatadst.c rename to src/lib/jpeg/jdatadst.c diff --git a/lib/jpeg/jdatasrc.c b/src/lib/jpeg/jdatasrc.c similarity index 100% rename from lib/jpeg/jdatasrc.c rename to src/lib/jpeg/jdatasrc.c diff --git a/lib/jpeg/jdcoefct.c b/src/lib/jpeg/jdcoefct.c similarity index 100% rename from lib/jpeg/jdcoefct.c rename to src/lib/jpeg/jdcoefct.c diff --git a/lib/jpeg/jdcolor.c b/src/lib/jpeg/jdcolor.c similarity index 100% rename from lib/jpeg/jdcolor.c rename to src/lib/jpeg/jdcolor.c diff --git a/lib/jpeg/jdct.h b/src/lib/jpeg/jdct.h similarity index 100% rename from lib/jpeg/jdct.h rename to src/lib/jpeg/jdct.h diff --git a/lib/jpeg/jddctmgr.c b/src/lib/jpeg/jddctmgr.c similarity index 100% rename from lib/jpeg/jddctmgr.c rename to src/lib/jpeg/jddctmgr.c diff --git a/lib/jpeg/jdhuff.c b/src/lib/jpeg/jdhuff.c similarity index 100% rename from lib/jpeg/jdhuff.c rename to src/lib/jpeg/jdhuff.c diff --git a/lib/jpeg/jdinput.c b/src/lib/jpeg/jdinput.c similarity index 100% rename from lib/jpeg/jdinput.c rename to src/lib/jpeg/jdinput.c diff --git a/lib/jpeg/jdmainct.c b/src/lib/jpeg/jdmainct.c similarity index 100% rename from lib/jpeg/jdmainct.c rename to src/lib/jpeg/jdmainct.c diff --git a/lib/jpeg/jdmarker.c b/src/lib/jpeg/jdmarker.c similarity index 100% rename from lib/jpeg/jdmarker.c rename to src/lib/jpeg/jdmarker.c diff --git a/lib/jpeg/jdmaster.c b/src/lib/jpeg/jdmaster.c similarity index 100% rename from lib/jpeg/jdmaster.c rename to src/lib/jpeg/jdmaster.c diff --git a/lib/jpeg/jdmerge.c b/src/lib/jpeg/jdmerge.c similarity index 100% rename from lib/jpeg/jdmerge.c rename to src/lib/jpeg/jdmerge.c diff --git a/lib/jpeg/jdpostct.c b/src/lib/jpeg/jdpostct.c similarity index 100% rename from lib/jpeg/jdpostct.c rename to src/lib/jpeg/jdpostct.c diff --git a/lib/jpeg/jdsample.c b/src/lib/jpeg/jdsample.c similarity index 100% rename from lib/jpeg/jdsample.c rename to src/lib/jpeg/jdsample.c diff --git a/lib/jpeg/jerror.c b/src/lib/jpeg/jerror.c similarity index 100% rename from lib/jpeg/jerror.c rename to src/lib/jpeg/jerror.c diff --git a/lib/jpeg/jerror.h b/src/lib/jpeg/jerror.h similarity index 100% rename from lib/jpeg/jerror.h rename to src/lib/jpeg/jerror.h diff --git a/lib/jpeg/jfdctflt.c b/src/lib/jpeg/jfdctflt.c similarity index 100% rename from lib/jpeg/jfdctflt.c rename to src/lib/jpeg/jfdctflt.c diff --git a/lib/jpeg/jfdctfst.c b/src/lib/jpeg/jfdctfst.c similarity index 100% rename from lib/jpeg/jfdctfst.c rename to src/lib/jpeg/jfdctfst.c diff --git a/lib/jpeg/jfdctint.c b/src/lib/jpeg/jfdctint.c similarity index 100% rename from lib/jpeg/jfdctint.c rename to src/lib/jpeg/jfdctint.c diff --git a/lib/jpeg/jidctflt.c b/src/lib/jpeg/jidctflt.c similarity index 100% rename from lib/jpeg/jidctflt.c rename to src/lib/jpeg/jidctflt.c diff --git a/lib/jpeg/jidctfst.c b/src/lib/jpeg/jidctfst.c similarity index 100% rename from lib/jpeg/jidctfst.c rename to src/lib/jpeg/jidctfst.c diff --git a/lib/jpeg/jidctint.c b/src/lib/jpeg/jidctint.c similarity index 100% rename from lib/jpeg/jidctint.c rename to src/lib/jpeg/jidctint.c diff --git a/lib/jpeg/jinclude.h b/src/lib/jpeg/jinclude.h similarity index 100% rename from lib/jpeg/jinclude.h rename to src/lib/jpeg/jinclude.h diff --git a/lib/jpeg/jmemmgr.c b/src/lib/jpeg/jmemmgr.c similarity index 100% rename from lib/jpeg/jmemmgr.c rename to src/lib/jpeg/jmemmgr.c diff --git a/lib/jpeg/jmemnobs.c b/src/lib/jpeg/jmemnobs.c similarity index 100% rename from lib/jpeg/jmemnobs.c rename to src/lib/jpeg/jmemnobs.c diff --git a/lib/jpeg/jmemsys.h b/src/lib/jpeg/jmemsys.h similarity index 100% rename from lib/jpeg/jmemsys.h rename to src/lib/jpeg/jmemsys.h diff --git a/lib/jpeg/jmorecfg.h b/src/lib/jpeg/jmorecfg.h similarity index 100% rename from lib/jpeg/jmorecfg.h rename to src/lib/jpeg/jmorecfg.h diff --git a/lib/jpeg/jpegint.h b/src/lib/jpeg/jpegint.h similarity index 100% rename from lib/jpeg/jpegint.h rename to src/lib/jpeg/jpegint.h diff --git a/lib/jpeg/jpeglib.h b/src/lib/jpeg/jpeglib.h similarity index 100% rename from lib/jpeg/jpeglib.h rename to src/lib/jpeg/jpeglib.h diff --git a/lib/jpeg/jquant1.c b/src/lib/jpeg/jquant1.c similarity index 100% rename from lib/jpeg/jquant1.c rename to src/lib/jpeg/jquant1.c diff --git a/lib/jpeg/jquant2.c b/src/lib/jpeg/jquant2.c similarity index 100% rename from lib/jpeg/jquant2.c rename to src/lib/jpeg/jquant2.c diff --git a/lib/jpeg/jutils.c b/src/lib/jpeg/jutils.c similarity index 100% rename from lib/jpeg/jutils.c rename to src/lib/jpeg/jutils.c diff --git a/lib/jpeg/jversion.h b/src/lib/jpeg/jversion.h similarity index 100% rename from lib/jpeg/jversion.h rename to src/lib/jpeg/jversion.h diff --git a/lib/jpeg/libjpeg.txt b/src/lib/jpeg/libjpeg.txt similarity index 100% rename from lib/jpeg/libjpeg.txt rename to src/lib/jpeg/libjpeg.txt diff --git a/lib/png/LICENSE b/src/lib/png/LICENSE similarity index 100% rename from lib/png/LICENSE rename to src/lib/png/LICENSE diff --git a/lib/png/README b/src/lib/png/README similarity index 100% rename from lib/png/README rename to src/lib/png/README diff --git a/lib/png/png.c b/src/lib/png/png.c similarity index 100% rename from lib/png/png.c rename to src/lib/png/png.c diff --git a/lib/png/png.h b/src/lib/png/png.h similarity index 100% rename from lib/png/png.h rename to src/lib/png/png.h diff --git a/lib/png/pngconf.h b/src/lib/png/pngconf.h similarity index 100% rename from lib/png/pngconf.h rename to src/lib/png/pngconf.h diff --git a/lib/png/pngdebug.h b/src/lib/png/pngdebug.h similarity index 100% rename from lib/png/pngdebug.h rename to src/lib/png/pngdebug.h diff --git a/lib/png/pngerror.c b/src/lib/png/pngerror.c similarity index 100% rename from lib/png/pngerror.c rename to src/lib/png/pngerror.c diff --git a/lib/png/pngget.c b/src/lib/png/pngget.c similarity index 100% rename from lib/png/pngget.c rename to src/lib/png/pngget.c diff --git a/lib/png/pnginfo.h b/src/lib/png/pnginfo.h similarity index 100% rename from lib/png/pnginfo.h rename to src/lib/png/pnginfo.h diff --git a/lib/png/pngmem.c b/src/lib/png/pngmem.c similarity index 100% rename from lib/png/pngmem.c rename to src/lib/png/pngmem.c diff --git a/lib/png/pngpread.c b/src/lib/png/pngpread.c similarity index 100% rename from lib/png/pngpread.c rename to src/lib/png/pngpread.c diff --git a/lib/png/pngpriv.h b/src/lib/png/pngpriv.h similarity index 100% rename from lib/png/pngpriv.h rename to src/lib/png/pngpriv.h diff --git a/lib/png/pngread.c b/src/lib/png/pngread.c similarity index 100% rename from lib/png/pngread.c rename to src/lib/png/pngread.c diff --git a/lib/png/pngrio.c b/src/lib/png/pngrio.c similarity index 100% rename from lib/png/pngrio.c rename to src/lib/png/pngrio.c diff --git a/lib/png/pngrtran.c b/src/lib/png/pngrtran.c similarity index 100% rename from lib/png/pngrtran.c rename to src/lib/png/pngrtran.c diff --git a/lib/png/pngrutil.c b/src/lib/png/pngrutil.c similarity index 100% rename from lib/png/pngrutil.c rename to src/lib/png/pngrutil.c diff --git a/lib/png/pngset.c b/src/lib/png/pngset.c similarity index 100% rename from lib/png/pngset.c rename to src/lib/png/pngset.c diff --git a/lib/png/pngstruct.h b/src/lib/png/pngstruct.h similarity index 100% rename from lib/png/pngstruct.h rename to src/lib/png/pngstruct.h diff --git a/lib/png/pngtrans.c b/src/lib/png/pngtrans.c similarity index 100% rename from lib/png/pngtrans.c rename to src/lib/png/pngtrans.c diff --git a/lib/png/pngwio.c b/src/lib/png/pngwio.c similarity index 100% rename from lib/png/pngwio.c rename to src/lib/png/pngwio.c diff --git a/lib/png/pngwrite.c b/src/lib/png/pngwrite.c similarity index 100% rename from lib/png/pngwrite.c rename to src/lib/png/pngwrite.c diff --git a/lib/png/pngwtran.c b/src/lib/png/pngwtran.c similarity index 100% rename from lib/png/pngwtran.c rename to src/lib/png/pngwtran.c diff --git a/lib/png/pngwutil.c b/src/lib/png/pngwutil.c similarity index 100% rename from lib/png/pngwutil.c rename to src/lib/png/pngwutil.c diff --git a/lib/zlib/README b/src/lib/zlib/README similarity index 100% rename from lib/zlib/README rename to src/lib/zlib/README diff --git a/lib/zlib/adler32.c b/src/lib/zlib/adler32.c similarity index 100% rename from lib/zlib/adler32.c rename to src/lib/zlib/adler32.c diff --git a/lib/zlib/compress.c b/src/lib/zlib/compress.c similarity index 100% rename from lib/zlib/compress.c rename to src/lib/zlib/compress.c diff --git a/lib/zlib/crc32.c b/src/lib/zlib/crc32.c similarity index 100% rename from lib/zlib/crc32.c rename to src/lib/zlib/crc32.c diff --git a/lib/zlib/crc32.h b/src/lib/zlib/crc32.h similarity index 100% rename from lib/zlib/crc32.h rename to src/lib/zlib/crc32.h diff --git a/lib/zlib/deflate.c b/src/lib/zlib/deflate.c similarity index 100% rename from lib/zlib/deflate.c rename to src/lib/zlib/deflate.c diff --git a/lib/zlib/deflate.h b/src/lib/zlib/deflate.h similarity index 100% rename from lib/zlib/deflate.h rename to src/lib/zlib/deflate.h diff --git a/lib/zlib/gzclose.c b/src/lib/zlib/gzclose.c similarity index 100% rename from lib/zlib/gzclose.c rename to src/lib/zlib/gzclose.c diff --git a/lib/zlib/gzguts.h b/src/lib/zlib/gzguts.h similarity index 100% rename from lib/zlib/gzguts.h rename to src/lib/zlib/gzguts.h diff --git a/lib/zlib/gzlib.c b/src/lib/zlib/gzlib.c similarity index 100% rename from lib/zlib/gzlib.c rename to src/lib/zlib/gzlib.c diff --git a/lib/zlib/gzread.c b/src/lib/zlib/gzread.c similarity index 100% rename from lib/zlib/gzread.c rename to src/lib/zlib/gzread.c diff --git a/lib/zlib/gzwrite.c b/src/lib/zlib/gzwrite.c similarity index 100% rename from lib/zlib/gzwrite.c rename to src/lib/zlib/gzwrite.c diff --git a/lib/zlib/infback.c b/src/lib/zlib/infback.c similarity index 100% rename from lib/zlib/infback.c rename to src/lib/zlib/infback.c diff --git a/lib/zlib/inffast.c b/src/lib/zlib/inffast.c similarity index 100% rename from lib/zlib/inffast.c rename to src/lib/zlib/inffast.c diff --git a/lib/zlib/inffast.h b/src/lib/zlib/inffast.h similarity index 100% rename from lib/zlib/inffast.h rename to src/lib/zlib/inffast.h diff --git a/lib/zlib/inffixed.h b/src/lib/zlib/inffixed.h similarity index 100% rename from lib/zlib/inffixed.h rename to src/lib/zlib/inffixed.h diff --git a/lib/zlib/inflate.c b/src/lib/zlib/inflate.c similarity index 100% rename from lib/zlib/inflate.c rename to src/lib/zlib/inflate.c diff --git a/lib/zlib/inflate.h b/src/lib/zlib/inflate.h similarity index 100% rename from lib/zlib/inflate.h rename to src/lib/zlib/inflate.h diff --git a/lib/zlib/inftrees.c b/src/lib/zlib/inftrees.c similarity index 100% rename from lib/zlib/inftrees.c rename to src/lib/zlib/inftrees.c diff --git a/lib/zlib/inftrees.h b/src/lib/zlib/inftrees.h similarity index 100% rename from lib/zlib/inftrees.h rename to src/lib/zlib/inftrees.h diff --git a/lib/zlib/trees.c b/src/lib/zlib/trees.c similarity index 100% rename from lib/zlib/trees.c rename to src/lib/zlib/trees.c diff --git a/lib/zlib/trees.h b/src/lib/zlib/trees.h similarity index 100% rename from lib/zlib/trees.h rename to src/lib/zlib/trees.h diff --git a/lib/zlib/uncompr.c b/src/lib/zlib/uncompr.c similarity index 100% rename from lib/zlib/uncompr.c rename to src/lib/zlib/uncompr.c diff --git a/lib/zlib/zconf.h b/src/lib/zlib/zconf.h similarity index 100% rename from lib/zlib/zconf.h rename to src/lib/zlib/zconf.h diff --git a/lib/zlib/zlib.h b/src/lib/zlib/zlib.h similarity index 100% rename from lib/zlib/zlib.h rename to src/lib/zlib/zlib.h diff --git a/lib/zlib/zutil.c b/src/lib/zlib/zutil.c similarity index 100% rename from lib/zlib/zutil.c rename to src/lib/zlib/zutil.c diff --git a/lib/zlib/zutil.h b/src/lib/zlib/zutil.h similarity index 100% rename from lib/zlib/zutil.h rename to src/lib/zlib/zutil.h From e7a2b4d33c7aeeec5bfc6dc39f096aa8dcecf9a0 Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Tue, 30 Sep 2014 14:54:07 +0300 Subject: [PATCH 17/21] implements image.paste (#36) todo: batch, tests --- README.md | 14 ++++++++ binding.gyp | 1 + examples/paste_blend.js | 25 ++++++++++++++ examples/paste_mosiac.js | 68 ++++++++++++++++++++++++++++++++++++++ lib/Batch.js | 4 +-- lib/Image.js | 24 ++++++++++++++ lib/defs.js | 13 ++++++++ src/image/image.cpp | 36 ++++++++++++++++++++ src/image/image.h | 24 ++++++++++++++ src/image/paste_worker.cpp | 55 ++++++++++++++++++++++++++++++ 10 files changed, 261 insertions(+), 3 deletions(-) create mode 100644 examples/paste_blend.js create mode 100644 examples/paste_mosiac.js create mode 100644 src/image/paste_worker.cpp diff --git a/README.md b/README.md index 871b7707..74556b84 100644 --- a/README.md +++ b/README.md @@ -440,6 +440,20 @@ Make image completely opaque. 0. `callback {Function(err, image)}` +#### Paste + +Paste an image on top of this image. + +`image.paste(left, top, img, callback)` + +0. `left, top {Integer}`: Coordinates of the top-left corner of the pasted + image. +0. `img {Image object}`: The image object to paste. +0. `callback {Function(err, image)}` + +**Note:** If the pasted image exceeds the bounds of the base image, an exception +is thrown. + ### Getters #### Width diff --git a/binding.gyp b/binding.gyp index 25d309e4..432a0a37 100644 --- a/binding.gyp +++ b/binding.gyp @@ -208,6 +208,7 @@ "src/image/sharpen_worker.cpp", "src/image/hsla_worker.cpp", "src/image/opacify_worker.cpp", + "src/image/paste_worker.cpp", ], 'include_dirs': [ '= COLS) batch.mirror('y'); + batch.hue(h); + batch.exec(function(err, clone) { + if (err) return done(err); + clones[i] = clone; + done(); + }); + }); + }, function(err) { + if (err) return console.log(err); + var r = 0, + c = 0; + async.eachSeries(clones, function(clone, next) { + canvas.paste(c * width, r * height, clone, function(err) { + c++; + if (c === COLS) { + c = 0; + r++; + } + next(err); + }); + }, function(err) { + if (err) return console.log(err); + canvas.writeFile('lena_paste_mosiac.jpg', function(err) { + if (err) return console.log(err); + console.log('done'); + }); + }); + }); + }); + + }); + +}); diff --git a/lib/Batch.js b/lib/Batch.js index dd351212..bcb318d5 100644 --- a/lib/Batch.js +++ b/lib/Batch.js @@ -5,9 +5,7 @@ async = require('async'), decree = require('decree'), defs = require('./defs'), - util = require('./util'), - Image = require('./Image'); - + util = require('./util'); var undefinedFilter = util.undefinedFilter, normalizeColor = util.normalizeColor; diff --git a/lib/Image.js b/lib/Image.js index 08693c1e..de3fb92d 100644 --- a/lib/Image.js +++ b/lib/Image.js @@ -335,6 +335,30 @@ } } + Image.prototype.paste = function() { + this.__lock(); + try { + var that = this; + decree(defs.args.paste)(arguments, function(left, top, img, callback) { + // first we retrieve what we need (buffer, dimensions, ...) + // synchronously so that the pasted image doesn't have a chance + // to be changed + var pixbuff = img.__lwip.buffer(), + width = img.__lwip.width(), + height = img.__lwip.height(); + if (left + width > that.__lwip.width() || top + height > that.__lwip.height()) + throw Error("Pasted image exceeds dimensions of base image"); + that.__lwip.paste(+left, +top, pixbuff, +width, +height, function(err) { + that.__release(); + callback(err, that); + }); + }); + } catch (e) { + this.__release(); + throw e; + } + } + Image.prototype.clone = function() { // no need to lock the image. we don't modify the memory buffer. // just copy it. diff --git a/lib/defs.js b/lib/defs.js index ea6dcef8..a2aef989 100644 --- a/lib/defs.js +++ b/lib/defs.js @@ -293,6 +293,19 @@ name: 'callback', type: 'function' }], + paste: [{ + name: 'left', + type: 'nn-number' + }, { + name: 'top', + type: 'nn-number' + }, { + name: 'image', + type: '*' + }, { + name: 'callback', + type: 'function' + }], extract: [{ name: 'left', type: 'nn-number' diff --git a/src/image/image.cpp b/src/image/image.cpp index 60ae090b..d06cacd4 100644 --- a/src/image/image.cpp +++ b/src/image/image.cpp @@ -19,6 +19,7 @@ void LwipImage::Init(Handle exports) { NODE_SET_PROTOTYPE_METHOD(tpl, "sharpen", sharpen); NODE_SET_PROTOTYPE_METHOD(tpl, "hslaAdj", hslaAdj); NODE_SET_PROTOTYPE_METHOD(tpl, "opacify", opacify); + NODE_SET_PROTOTYPE_METHOD(tpl, "paste", paste); NanAssignPersistent(constructor, tpl); exports->Set( NanNew("LwipImage"), @@ -348,3 +349,38 @@ NAN_METHOD(LwipImage::opacify) { NanReturnUndefined(); } + +// image.paste(callback): +// ------------------------------------ + +// args[0] - left +// args[1] - top +// args[2] - buffer +// args[3] - width +// args[4] - height +// args[5] - callback +NAN_METHOD(LwipImage::paste) { + NanScope(); + + size_t left = (size_t) args[0].As()->Value(); + size_t top = (size_t) args[1].As()->Value(); + Local pixBuff = args[2].As(); + size_t width = (size_t) args[3].As()->Value(); + size_t height = (size_t) args[4].As()->Value(); + NanCallback * callback = new NanCallback(args[5].As()); + CImg * cimg = ObjectWrap::Unwrap(args.This())->_cimg; + + NanAsyncQueueWorker( + new PasteWorker( + left, + top, + pixBuff, + width, + height, + cimg, + callback + ) + ); + + NanReturnUndefined(); +} diff --git a/src/image/image.h b/src/image/image.h index 54c443c1..d7cb9a13 100644 --- a/src/image/image.h +++ b/src/image/image.h @@ -39,6 +39,7 @@ class LwipImage : public node::ObjectWrap { static NAN_METHOD(sharpen); static NAN_METHOD(hslaAdj); static NAN_METHOD(opacify); + static NAN_METHOD(paste); static NAN_METHOD(width); static NAN_METHOD(height); static NAN_METHOD(buffer); @@ -222,6 +223,29 @@ class OpacifyWorker : public NanAsyncWorker { CImg * _cimg; }; +class PasteWorker : public NanAsyncWorker { +public: + PasteWorker( + size_t left, + size_t top, + Local & pixbuf, + size_t width, + size_t height, + CImg * cimg, + NanCallback * callback + ); + ~PasteWorker(); + void Execute (); + void HandleOKCallback (); +private: + size_t _left; + size_t _top; + size_t _width; + size_t _height; + unsigned char * _pixels; + CImg * _cimg; +}; + void rgb_to_hsl(unsigned char r, unsigned char g, unsigned char b, float * h, float * s, float * l); void hsl_to_rgb(float h, float s, float l, unsigned char * r, unsigned char * g, diff --git a/src/image/paste_worker.cpp b/src/image/paste_worker.cpp new file mode 100644 index 00000000..e8e1c53d --- /dev/null +++ b/src/image/paste_worker.cpp @@ -0,0 +1,55 @@ +#include "image.h" + +PasteWorker::PasteWorker( + size_t left, + size_t top, + Local & pixbuf, + size_t width, + size_t height, + CImg * cimg, + NanCallback * callback +): NanAsyncWorker(callback), _left(left), _top(top), _width(width), + _height(height), _cimg(cimg) { + SaveToPersistent("pixbuf", pixbuf); + _pixels = (unsigned char *) Buffer::Data(pixbuf); +} + +PasteWorker::~PasteWorker() {} + +void PasteWorker::Execute () { + try { + CImg pst(_pixels, _width, _height, 1, 4, true); + for (size_t x = _left ; x < _left + pst.width() ; x++) { + for (size_t y = _top ; y < _top + pst.height() ; y++) { + float a = ((float) (*_cimg)(x, y, 0, 3)) / 100.0, + pa = ((float) pst(x - _left, y - _top, 0, 3)) / 100.0, + acpa = a * (1 - pa), + na = pa + acpa; + unsigned char r = (*_cimg)(x, y, 0, 0), + g = (*_cimg)(x, y, 0, 1), + b = (*_cimg)(x, y, 0, 2), + pr = pst(x - _left, y - _top, 0, 0), + pg = pst(x - _left, y - _top, 0, 1), + pb = pst(x - _left, y - _top, 0, 2), + nr = 0, ng = 0, nb = 0; + if (na != 0) { + nr = (unsigned char) ((((float) pr) * pa + ((float) r) * acpa) / na); + ng = (unsigned char) ((((float) pg) * pa + ((float) g) * acpa) / na); + nb = (unsigned char) ((((float) pb) * pa + ((float) b) * acpa) / na); + } + _cimg->fillC(x, y, 0, nr, ng, nb, (unsigned char) (na * 100.0)); + } + } + } catch (CImgException e) { + SetErrorMessage("Unable to paste image"); + return; + } +} + +void PasteWorker::HandleOKCallback () { + NanScope(); + Local argv[] = { + NanNull() + }; + callback->Call(1, argv); +} From 67908017d3d3f3f21acda338bba4db70f33b07b1 Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Thu, 2 Oct 2014 01:29:32 +0300 Subject: [PATCH 18/21] finishes implementation of 'paste'. closes #36 --- README.md | 15 ++- examples/paste_blend.js | 46 ++++++--- examples/paste_mosiac.js | 4 +- index.js | 1 + lib/Batch.js | 28 +++++- lib/Image.js | 9 +- tests/00.argsValidation/109.image.paste.js | 83 ++++++++++++++++ tests/00.argsValidation/209.batch.paste.js | 104 ++++++++++++++++++++ tests/02.operations/117.paste.js | 107 +++++++++++++++++++++ 9 files changed, 370 insertions(+), 27 deletions(-) create mode 100644 tests/00.argsValidation/109.image.paste.js create mode 100644 tests/00.argsValidation/209.batch.paste.js create mode 100644 tests/02.operations/117.paste.js diff --git a/README.md b/README.md index 74556b84..3469092f 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ 0. [Adjust hue](#hue) 0. [Fade (adjust transparency)](#fade) 0. [Opacify](#opacify) + 0. [Paste](#paste) 0. [Getters](#getters) 0. [Width](#width) 0. [Height](#height) @@ -448,11 +449,19 @@ Paste an image on top of this image. 0. `left, top {Integer}`: Coordinates of the top-left corner of the pasted image. -0. `img {Image object}`: The image object to paste. +0. `img {Image object}`: The image to paste. 0. `callback {Function(err, image)}` -**Note:** If the pasted image exceeds the bounds of the base image, an exception -is thrown. +**Notes:** + +0. If the pasted image exceeds the bounds of the base image, an exception + is thrown. +0. `img` is pasted in the state it was at the time `image.paste( ... )` was + called, eventhough `callback` is called asynchronously. +0. For transparent images, alpha blending is done according to the equations + described [here](http://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending). +0. Extra caution is required when using this method in batch mode, as the images + may change by the time this operation is called. ### Getters diff --git a/examples/paste_blend.js b/examples/paste_blend.js index a1523674..c0fa3ee1 100644 --- a/examples/paste_blend.js +++ b/examples/paste_blend.js @@ -1,25 +1,41 @@ /** - * Example for using LWIP to extract parts of an image. + * Example for using LWIP to blend two images. */ var path = require('path'), + async = require('async'), lwip = require('../'); -lwip.open('lena.jpg', function(err, image) { - if (err) return console.log(err); +async.waterfall([ - image.clone(function(err, flipped) { - if (err) return console.log(err); - flipped.batch().flip('x').fade(0.5).exec(function(err, flipped) { - if (err) return console.log(err); - image.paste(0, 0, flipped, function(err, image) { - if (err) return console.log(err); - image.writeFile('lena_paste_blend.jpg', function(err) { - if (err) return console.log(err); - console.log('done'); - }); - }); + function(next) { + lwip.open('lena.jpg', next); + }, + + function(image, next) { + image.clone(function(err, clone) { + next(err, clone, image); }); - }); + }, + + function(clone, image, next) { + clone.batch() + .flip('x') + .fade(0.5) + .exec(function(err, clone) { + next(err, clone, image); + }); + }, + + function(clone, image, next) { + image.paste(0, 0, clone, next); + }, + function(image, next) { + image.writeFile('lena_paste_blend.jpg', next); + } + +], function(err) { + if (err) return console.log(err); + console.log('done'); }); diff --git a/examples/paste_mosiac.js b/examples/paste_mosiac.js index 494eada6..d9c0c669 100644 --- a/examples/paste_mosiac.js +++ b/examples/paste_mosiac.js @@ -1,5 +1,5 @@ /** - * Example for using LWIP to extract parts of an image. + * Example for using LWIP to create a hue gradient mosiac. */ var path = require('path'), @@ -25,6 +25,7 @@ lwip.open('lena.jpg', function(err, image) { lwip.create(width * COLS, height * ROWS, function(err, canvas) { if (err) return console.log(err); + var clones = []; async.each(HUES, function(h, done) { var i = HUES.indexOf(h); @@ -61,6 +62,7 @@ lwip.open('lena.jpg', function(err, image) { }); }); }); + }); }); diff --git a/index.js b/index.js index b6f805a6..93ce339a 100644 --- a/index.js +++ b/index.js @@ -1 +1,2 @@ +require('./lib/Batch'); // Extend Image object with .batch() function module.exports = require('./lib/obtain'); diff --git a/lib/Batch.js b/lib/Batch.js index bcb318d5..ccc8f2f9 100644 --- a/lib/Batch.js +++ b/lib/Batch.js @@ -5,7 +5,13 @@ async = require('async'), decree = require('decree'), defs = require('./defs'), - util = require('./util'); + util = require('./util'), + Image = require('./Image'); + + // Extend Image with image.batch() + Image.prototype.batch = function() { + return new Batch(this); + } var undefinedFilter = util.undefinedFilter, normalizeColor = util.normalizeColor; @@ -28,7 +34,14 @@ that.__running = true; async.eachSeries(this.__queue, function(op, done) { op.args.push(done); - op.handle.apply(that.__image, op.args); + // if an exception is thrown here, it should be caught (because we + // are in the middle of async process) and translated to an 'err' + // parameter. + try { + op.handle.apply(that.__image, op.args); + } catch (e) { + done(e); + } }, function(err) { that.__queue.length = 0; // queue is now empty that.__running = false; @@ -186,6 +199,17 @@ return this; } + Batch.prototype.paste = function() { + var that = this, + decs = defs.args.paste.slice(0, -1); // cut callback declaration + decree(decs)(arguments, function(left, top, img) { + if (!(img instanceof Image)) + throw Error("Pasted image is not a valid Image object"); + that.__addOp(that.__image.paste, [left, top, img].filter(undefinedFilter)); + }); + return this; + } + Batch.prototype.toBuffer = function() { var that = this; decree(defs.args.toBuffer)(arguments, function(type, params, callback) { diff --git a/lib/Image.js b/lib/Image.js index de3fb92d..108ec1a0 100644 --- a/lib/Image.js +++ b/lib/Image.js @@ -6,8 +6,7 @@ defs = require('./defs'), util = require('./util'), encoder = require('../build/Release/lwip_encoder'), - lwip_image = require('../build/Release/lwip_image'), - Batch = require('./Batch'); + lwip_image = require('../build/Release/lwip_image'); var undefinedFilter = util.undefinedFilter, normalizeColor = util.normalizeColor; @@ -340,6 +339,8 @@ try { var that = this; decree(defs.args.paste)(arguments, function(left, top, img, callback) { + if (!(img instanceof Image)) + throw Error("Pasted image is not a valid Image object"); // first we retrieve what we need (buffer, dimensions, ...) // synchronously so that the pasted image doesn't have a chance // to be changed @@ -463,10 +464,6 @@ }); } - Image.prototype.batch = function() { - return new Batch(this); - } - // EXPORTS // ------- module.exports = Image; diff --git a/tests/00.argsValidation/109.image.paste.js b/tests/00.argsValidation/109.image.paste.js new file mode 100644 index 00000000..5ba6cb9d --- /dev/null +++ b/tests/00.argsValidation/109.image.paste.js @@ -0,0 +1,83 @@ +// methods should throw errors when arguments are invalid + +var should = require("should"), + assert = require('assert'), + lwip = require('../../'), + imgs = require('../imgs'); + +describe('image.paste arguments validation', function() { + + var image; + before(function(done) { + lwip.open(imgs.jpg.rgb, function(err, img) { + image = img; + done(err); + }); + }); + + describe('invalid pasted image (1)', function() { + it('should throw an error', function() { + image.paste.bind(image, 0, 0, {}, function() {}).should.throwError(); + }); + }); + + describe('invalid pasted image (2)', function() { + it('should throw an error', function() { + image.paste.bind(image, 0, 0, null, function() {}).should.throwError(); + }); + }); + + describe('invalid pasted image (3)', function() { + it('should throw an error', function() { + image.paste.bind(image, 0, 0, function() {}).should.throwError(); + }); + }); + + describe('invalid pasted image (4)', function() { + it('should throw an error', function() { + image.paste.bind(image, 0, 0, function() {}, function() {}).should.throwError(); + }); + }); + + describe('invalid pasted image (5)', function() { + it('should throw an error', function() { + image.paste.bind(image, 0, 0, undefined, function() {}).should.throwError(); + }); + }); + + describe('invalid pasted image (6)', function() { + it('should throw an error', function() { + image.paste.bind(image, 0, 0, 0, function() {}).should.throwError(); + }); + }); + + describe('invalid pasted image (7)', function() { + it('should throw an error', function() { + image.paste.bind(image, 0, 0, "foo", function() {}).should.throwError(); + }); + }); + + describe('invalid pasted image (8)', function() { + it('should throw an error', function() { + image.paste.bind(image, 0, 0, [1, 2, 3], function() {}).should.throwError(); + }); + }); + + describe('pasted image exceeds dimensions', function() { + + var Image = require('../../lib/Image'); + + var clone; + before(function(done) { + image.clone(function(err, img) { + clone = img; + done(err); + }); + }); + + it('should throw an error', function() { + assert(clone instanceof Image); + image.paste.bind(image, 10, 10, clone, function() {}).should.throwError(); + }); + }); +}); diff --git a/tests/00.argsValidation/209.batch.paste.js b/tests/00.argsValidation/209.batch.paste.js new file mode 100644 index 00000000..ea1bdf5f --- /dev/null +++ b/tests/00.argsValidation/209.batch.paste.js @@ -0,0 +1,104 @@ +// methods should throw errors when arguments are invalid + +var should = require("should"), + assert = require('assert'), + lwip = require('../../'), + imgs = require('../imgs'); + +describe('batch.paste arguments validation', function() { + + var batch; + beforeEach(function(done) { + lwip.open(imgs.jpg.rgb, function(err, img) { + batch = img.batch(); + done(err); + }); + }); + + describe('invalid pasted image (1)', function() { + it('should throw an error', function() { + batch.paste.bind(batch, 0, 0, {}, function() {}).should.throwError(); + }); + }); + + describe('invalid pasted image (2)', function() { + it('should throw an error', function() { + batch.paste.bind(batch, 0, 0, null, function() {}).should.throwError(); + }); + }); + + describe('invalid pasted image (3)', function() { + it('should throw an error', function() { + batch.paste.bind(batch, 0, 0, function() {}).should.throwError(); + }); + }); + + describe('invalid pasted image (4)', function() { + it('should throw an error', function() { + batch.paste.bind(batch, 0, 0, function() {}, function() {}).should.throwError(); + }); + }); + + describe('invalid pasted image (5)', function() { + it('should throw an error', function() { + batch.paste.bind(batch, 0, 0, undefined, function() {}).should.throwError(); + }); + }); + + describe('invalid pasted image (6)', function() { + it('should throw an error', function() { + batch.paste.bind(batch, 0, 0, 0, function() {}).should.throwError(); + }); + }); + + describe('invalid pasted image (7)', function() { + it('should throw an error', function() { + batch.paste.bind(batch, 0, 0, "foo", function() {}).should.throwError(); + }); + }); + + describe('invalid pasted image (8)', function() { + it('should throw an error', function() { + batch.paste.bind(batch, 0, 0, [1, 2, 3], function() {}).should.throwError(); + }); + }); + + describe('pasted image exceeds dimensions', function() { + + var Image = require('../../lib/Image'); + + var pst; + beforeEach(function(done) { + lwip.open(imgs.png.rgb, function(err, img) { + pst = img; + done(err); + }); + }); + + describe('at the time of exec', function() { + it('should return an error', function(done) { + assert(pst instanceof Image); + batch.paste.bind(batch, 10, 10, pst).should.not.throwError(); + batch.exec(function(err) { + // there should be an error message + assert(!!err); + done(); + }); + }); + }); + + describe('before exec', function() { + it('should not return an error', function(done) { + assert(pst instanceof Image); + batch.paste.bind(batch, 10, 10, pst).should.not.throwError(); + pst.scale(0.1, function(err, pst) { + batch.exec(function(err) { + // there should not be an error message + assert(!err); + done(); + }); + }); + }); + }); + }); +}); diff --git a/tests/02.operations/117.paste.js b/tests/02.operations/117.paste.js new file mode 100644 index 00000000..5b4840af --- /dev/null +++ b/tests/02.operations/117.paste.js @@ -0,0 +1,107 @@ +var join = require('path').join, + should = require('should'), + assert = require('assert'), + mkdirp = require('mkdirp'), + lwip = require('../../'), + imgs = require('../imgs'); + +var tmpDir = join(__dirname, '../results'), + basename = 'paste', + current; + +describe('lwip.paste', function() { + + var back, front; + // back is the regular image. front is flipped on 'y' axis. + + before(function(done) { + mkdirp(tmpDir, done); + }); + + beforeEach(function(done) { + lwip.open(imgs.png.rgb, function(err, img) { + if (err) return done(err); + back = img; + img.clone(function(err, clone) { + if (err) return done(err); + clone.mirror('y', function(err, clone) { + if (err) return done(err); + front = clone; + done(); + }); + }); + }); + }); + + beforeEach(function() { + current = [basename]; + }); + + afterEach(function(done) { + back.writeFile(join(tmpDir, current.join('_') + '.png'), done); + }); + + describe('background transparent, front opaque', function() { + beforeEach(function(done) { + back.fade(1, done); + }); + it('should see flipped image', function(done) { + current.push('bgTrans_frontOpaque_resultFlipped'); + back.paste(0, 0, front, done); + }); + }); + + describe('background opaque, front transparent', function() { + beforeEach(function(done) { + front.fade(1, done); + }); + it('should see normal image', function(done) { + current.push('bgOpaque_frontTrans_resultNormal'); + back.paste(0, 0, front, done); + }); + }); + + describe('background opaque, front 50%', function() { + beforeEach(function(done) { + front.fade(0.5, done); + }); + it('should see blended image', function(done) { + current.push('bgOpaque_front50_resultBlend'); + back.paste(0, 0, front, done); + }); + }); + + describe('background 50%, front opaque', function() { + beforeEach(function(done) { + back.fade(0.5, done); + }); + it('should see flipped image', function(done) { + current.push('bg50_frontOpaque_resultFlipped'); + back.paste(0, 0, front, done); + }); + }); + + describe('background 50%, front 50%', function() { + beforeEach(function(done) { + back.fade(0.5, done); + }); + beforeEach(function(done) { + front.fade(0.5, done); + }); + it('should see blended transparent image', function(done) { + current.push('bg50_front50_resultBlendTrans'); + back.paste(0, 0, front, done); + }); + }); + + describe('front scaled 50% in middle', function() { + beforeEach(function(done) { + front.scale(0.5, done); + }); + it('should see small front in middle of back', function(done) { + current.push('front_in_middle'); + back.paste(back.width() / 4, back.height() / 4, front, done); + }); + }); + +}); From 97b8d8f1ae9dfaefe89dbfe09e843db6b883ca7e Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Thu, 2 Oct 2014 10:25:43 +0300 Subject: [PATCH 19/21] removes redundant buffer copies. closes #55 --- src/decoder/buffer_worker.cpp | 22 ++++++---------------- src/decoder/decoder.h | 3 +-- src/decoder/init.cpp | 8 ++------ src/encoder/encoder.h | 4 ++-- src/encoder/init.cpp | 6 ++---- src/encoder/jpeg_worker.cpp | 19 +++---------------- src/encoder/png_worker.cpp | 34 +++------------------------------- 7 files changed, 19 insertions(+), 77 deletions(-) diff --git a/src/decoder/buffer_worker.cpp b/src/decoder/buffer_worker.cpp index 70c3814e..534862e9 100644 --- a/src/decoder/buffer_worker.cpp +++ b/src/decoder/buffer_worker.cpp @@ -2,22 +2,13 @@ DecodeBufferWorker::DecodeBufferWorker( NanCallback * callback, - char * buffer, - size_t buffsize, + Local & buff, buf_dec_f_t decoder -): NanAsyncWorker(callback), _buffsize(buffsize), - _decoder(decoder), _pixbuf(NULL), _width(0), _height(0), _channels(0), - _trans(false) { - // buffer needs to be copied, because the buffer may be gc'ed by - // V8 at any time. - // !!! _buffer still needs to be freed by us when no longer needed (see Execute) - _buffer = (char *) malloc(_buffsize); - if (_buffer == NULL) { - // TODO: check - can I use SetErrorMessage here? - SetErrorMessage("Out of memory"); - return; - } - memcpy(_buffer, buffer, _buffsize); +): NanAsyncWorker(callback), _decoder(decoder), _pixbuf(NULL), _width(0), + _height(0), _channels(0), _trans(false) { + SaveToPersistent("buff", buff); // make sure buff isn't GC'ed + _buffer = Buffer::Data(buff); + _buffsize = Buffer::Length(buff); } DecodeBufferWorker::~DecodeBufferWorker() {} @@ -26,7 +17,6 @@ void DecodeBufferWorker::Execute () { CImg * img = NULL; string err; err = _decoder(_buffer, _buffsize, &img); - free(_buffer); if (img == NULL) { SetErrorMessage(err.c_str()); return; diff --git a/src/decoder/decoder.h b/src/decoder/decoder.h index c5da4d8f..983e6bc6 100644 --- a/src/decoder/decoder.h +++ b/src/decoder/decoder.h @@ -30,8 +30,7 @@ class DecodeBufferWorker : public NanAsyncWorker { public: DecodeBufferWorker( NanCallback * callback, - char * buffer, - size_t buffsize, + Local & buff, buf_dec_f_t decoder ); ~DecodeBufferWorker(); diff --git a/src/decoder/init.cpp b/src/decoder/init.cpp index d012c578..e85e8103 100644 --- a/src/decoder/init.cpp +++ b/src/decoder/init.cpp @@ -4,11 +4,9 @@ NAN_METHOD(decodeJpegBuffer) { NanScope(); Local jpegBuff = args[0].As(); - char * buffer = Buffer::Data(jpegBuff); - size_t buffsize = Buffer::Length(jpegBuff); NanCallback * callback = new NanCallback(args[1].As()); - NanAsyncQueueWorker(new DecodeBufferWorker(callback, buffer, buffsize, decode_jpeg_buffer)); + NanAsyncQueueWorker(new DecodeBufferWorker(callback, jpegBuff, decode_jpeg_buffer)); NanReturnUndefined(); } @@ -16,11 +14,9 @@ NAN_METHOD(decodePngBuffer) { NanScope(); Local pngBuff = args[0].As(); - char * buffer = Buffer::Data(pngBuff); - size_t buffsize = Buffer::Length(pngBuff); NanCallback * callback = new NanCallback(args[1].As()); - NanAsyncQueueWorker(new DecodeBufferWorker(callback, buffer, buffsize, decode_png_buffer)); + NanAsyncQueueWorker(new DecodeBufferWorker(callback, pngBuff, decode_png_buffer)); NanReturnUndefined(); } diff --git a/src/encoder/encoder.h b/src/encoder/encoder.h index a09823a3..29cd6ce6 100644 --- a/src/encoder/encoder.h +++ b/src/encoder/encoder.h @@ -25,7 +25,7 @@ using namespace std; class EncodeToJpegBufferWorker : public NanAsyncWorker { public: EncodeToJpegBufferWorker( - unsigned char * pixbuf, + Local & buff, size_t width, size_t height, int quality, @@ -46,7 +46,7 @@ class EncodeToJpegBufferWorker : public NanAsyncWorker { class EncodeToPngBufferWorker : public NanAsyncWorker { public: EncodeToPngBufferWorker( - unsigned char * pixbuf, + Local & buff, size_t width, size_t height, int compression, diff --git a/src/encoder/init.cpp b/src/encoder/init.cpp index 75087cb4..760b6b3a 100644 --- a/src/encoder/init.cpp +++ b/src/encoder/init.cpp @@ -5,7 +5,6 @@ NAN_METHOD(encodeToJpegBuffer) { NanScope(); Local buff = args[0].As(); - unsigned char * pixbuf = (unsigned char *) Buffer::Data(buff); size_t width = args[1].As()->Value(); size_t height = args[2].As()->Value(); int quality = args[3].As()->Value(); @@ -13,7 +12,7 @@ NAN_METHOD(encodeToJpegBuffer) { NanAsyncQueueWorker( new EncodeToJpegBufferWorker( - pixbuf, + buff, width, height, quality, @@ -27,7 +26,6 @@ NAN_METHOD(encodeToPngBuffer) { NanScope(); Local buff = args[0].As(); - unsigned char * pixbuf = (unsigned char *) Buffer::Data(buff); size_t width = args[1].As()->Value(); size_t height = args[2].As()->Value(); int compression = args[3].As()->Value(); @@ -37,7 +35,7 @@ NAN_METHOD(encodeToPngBuffer) { NanAsyncQueueWorker( new EncodeToPngBufferWorker( - pixbuf, + buff, width, height, compression, diff --git a/src/encoder/jpeg_worker.cpp b/src/encoder/jpeg_worker.cpp index d6c05062..9b3ca061 100644 --- a/src/encoder/jpeg_worker.cpp +++ b/src/encoder/jpeg_worker.cpp @@ -3,25 +3,15 @@ #define RGB_N_CHANNELS 3 EncodeToJpegBufferWorker::EncodeToJpegBufferWorker( - unsigned char * pixbuf, + Local & buff, size_t width, size_t height, int quality, NanCallback * callback ): NanAsyncWorker(callback), _width(width), _height(height), _quality(quality), _jpegbuf(NULL), _jpegbufsize(0) { - // pixbuf needs to be copied, because the buffer may be gc'ed by - // V8 at any time. - // !!! _pixbuf still needs to be freed by us when no longer needed (see Execute) - _pixbuf = (unsigned char *) malloc(width * height * RGB_N_CHANNELS * sizeof(unsigned char)); - if (_pixbuf == NULL) { - // TODO: check - can I use SetErrorMessage here? - SetErrorMessage("Out of memory"); - return; - } - // pixbuf is actually RGBA (4 channels), but we discard the A channel - // for jpeg, which is at the end of the buffer. - memcpy(_pixbuf, pixbuf, width * height * RGB_N_CHANNELS * sizeof(unsigned char)); + SaveToPersistent("buff", buff); // make sure buff isn't GC'ed + _pixbuf = (unsigned char *) Buffer::Data(buff); } EncodeToJpegBufferWorker::~EncodeToJpegBufferWorker() {} @@ -39,14 +29,12 @@ void EncodeToJpegBufferWorker::Execute () { if (setjmp(jerr.setjmp_buffer)) { jpeg_destroy_compress(&cinfo); if (tmp) free(tmp); - free(_pixbuf); SetErrorMessage("JPEG compression error"); return; } tmp = (unsigned char *) malloc(_width * dimbuf); if (tmp == NULL) { - free(_pixbuf); SetErrorMessage("Out of memory"); return; } @@ -78,7 +66,6 @@ void EncodeToJpegBufferWorker::Execute () { jpeg_write_scanlines(&cinfo, row_pointer, 1); } free(tmp); - free(_pixbuf); jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); diff --git a/src/encoder/png_worker.cpp b/src/encoder/png_worker.cpp index 35e08bd7..1cfe527e 100644 --- a/src/encoder/png_worker.cpp +++ b/src/encoder/png_worker.cpp @@ -4,7 +4,7 @@ #define RGBA_N_CHANNELS 4 EncodeToPngBufferWorker::EncodeToPngBufferWorker( - unsigned char * pixbuf, + Local & buff, size_t width, size_t height, int compression, @@ -14,30 +14,8 @@ EncodeToPngBufferWorker::EncodeToPngBufferWorker( ): NanAsyncWorker(callback), _width(width), _height(height), _compression(compression), _interlaced(interlaced), _trans(trans), _pngbuf(NULL), _pngbufsize(0) { - // pixbuf needs to be copied, because the buffer may be gc'ed by - // V8 at any time. - // !!! _pixbuf still needs to be freed by us when no longer needed (see Execute) - _pixbuf = (unsigned char *) malloc( - width * - height * - (_trans ? RGBA_N_CHANNELS : RGB_N_CHANNELS) * - sizeof(unsigned char) - ); - if (_pixbuf == NULL) { - // TODO: check - can I use SetErrorMessage here? - SetErrorMessage("Out of memory"); - return; - } - // pixbuf is actually RGBA (4 channels), but we copy the A channel (which is - // at the end of the buffer) only if the image is marked as transparent. - memcpy( - _pixbuf, - pixbuf, - width * - height * - (_trans ? RGBA_N_CHANNELS : RGB_N_CHANNELS) * - sizeof(unsigned char) - ); + SaveToPersistent("buff", buff); // make sure buff isn't GC'ed + _pixbuf = (unsigned char *) Buffer::Data(buff); } EncodeToPngBufferWorker::~EncodeToPngBufferWorker() {} @@ -71,7 +49,6 @@ void EncodeToPngBufferWorker::Execute () { NULL, NULL, NULL); if (!png_ptr) { - free(_pixbuf); SetErrorMessage("Out of memory"); return; } @@ -79,14 +56,12 @@ void EncodeToPngBufferWorker::Execute () { png_infop info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_write_struct(&png_ptr, (png_infopp) NULL); - free(_pixbuf); SetErrorMessage("Out of memory"); return; } if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_write_struct(&png_ptr, &info_ptr); - free(_pixbuf); SetErrorMessage("PNG compression error"); return; } @@ -96,7 +71,6 @@ void EncodeToPngBufferWorker::Execute () { ); if (!rowPnts) { png_destroy_write_struct(&png_ptr, &info_ptr); - free(_pixbuf); SetErrorMessage("Out of memory"); return; } @@ -107,7 +81,6 @@ void EncodeToPngBufferWorker::Execute () { for (unsigned int p = 0 ; p < r ; p++) free(rowPnts[p]); free(rowPnts); png_destroy_write_struct(&png_ptr, &info_ptr); - free(_pixbuf); SetErrorMessage("Out of memory"); return; } @@ -155,7 +128,6 @@ void EncodeToPngBufferWorker::Execute () { png_destroy_write_struct(&png_ptr, &info_ptr); for (unsigned int r = 0; r < _height; r++) free(rowPnts[r]); free(rowPnts); - free(_pixbuf); _pngbuf = (char *) buffinf.buff; _pngbufsize = buffinf.buffsize; From 4ceedbe646e1168e686cd10226cfbd568957ed1c Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Thu, 2 Oct 2014 10:52:22 +0300 Subject: [PATCH 20/21] updates package metadata --- README.md | 4 ++-- package.json | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3469092f..868cf362 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![Version](http://img.shields.io/npm/v/lwip.svg)](https://www.npmjs.org/package/lwip) -[![Build Status](https://api.travis-ci.org/EyalAr/lwip.svg?branch=v0.0.5)](https://travis-ci.org/EyalAr/lwip) -[![Coverage Status](https://img.shields.io/coveralls/EyalAr/lwip/v0.0.5.svg)](https://coveralls.io/r/EyalAr/lwip) +[![Build Status](https://api.travis-ci.org/EyalAr/lwip.svg?branch=master)](https://travis-ci.org/EyalAr/lwip) +[![Coverage Status](https://img.shields.io/coveralls/EyalAr/lwip/master.svg)](https://coveralls.io/r/EyalAr/lwip) # Light-weight image processor for NodeJS diff --git a/package.json b/package.json index 1d5b108c..bae64c20 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,11 @@ "padding", "hue", "saturation", - "lightness" + "lightness", + "alpha", + "transparency", + "fade", + "opacity" ], "author": "Eyal Arubas ", "license": "MIT", From a0bb116332b3839512bf17abf2efab21420a5dd2 Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Thu, 2 Oct 2014 12:12:27 +0300 Subject: [PATCH 21/21] fixes stress tests --- tests/05.stress/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/05.stress/index.js b/tests/05.stress/index.js index 32a47533..371e1d4f 100644 --- a/tests/05.stress/index.js +++ b/tests/05.stress/index.js @@ -84,7 +84,7 @@ describe('stress tests', function() { var a = 3; lwip.open(imgs.jpg.rgb, 'jpeg', function(err, image) { if (err) return done(err); - async.timesSeries(50, function(i, done) { + async.timesSeries(90 / a, function(i, done) { image.rotate(a, utils.getRandomColor(), done); }, function(err) { if (err) return done(err); @@ -101,7 +101,7 @@ describe('stress tests', function() { var a = 3; lwip.open(imgs.png.trans, 'png', function(err, image) { if (err) return done(err); - async.timesSeries(50, function(i, done) { + async.timesSeries(90 / a, function(i, done) { image.rotate(a, utils.getRandomColor(), done); }, function(err) { if (err) return done(err);