B™±T>^YCo,ªeUP66»ã+—\Uà”zázµ1tŒU6Åàxؾ.\P½ªN6A¶)É:ânàŒU
+½I Î÷ä
=<»åKZàq’t*•ðês‚¬¾ŒÉ ߉™èMâ/7yêñšMlœJ®Ûv;f^˜ yÊ"^QþÖ¹óã^÷…y›Q†<ácÓ
+ »žÎâ;›g1ÍŒ»F
+ÖìeÓµ 䯕Å4]çU¹H£B܈ ¹')Ï)²‘؃ý‹,ÇŠG`ŒÐÚþhhF̺rZ‚ŸXz·»:òŸ-tØ’óU@Î!‡úÿ Ó¼«åm•€k¶v½U|~xs¬Ú]]X¼6;JÔiU®ùV\¼|&¨½:Ìù'’f¨‚F;ú»¬w¢á½·XÓE‹e@´ ð˜5eG×9ãyg\U? ߈qŠYkz–›p¶·àð_…ƒˆ÷ʸ·÷ïpáÚ3Q†X %ÒþÆ]}¢i·Õi¡^gö×fûÆDï|¡t²ÒÎ@ñÕÏ>{o’–Õ¬¡Œ<󪎴¨Àñë¶7s‹{w.ç
+¦æê1hò‘Æc“µ$oïHÉÓˆ [Áë/‡ï¯ðÈF±eªéLÌ…¹'÷ˆGãòÎÇ4ñÚÂÓLx¢Š’r-5áóæÎeXÆæwܪøíãჄ>ŸŠ¸åþLŽ.?w½ç#WÖœ%¼,î>ÐQ†ƒË^a‚aq$/Åv
+¿ÈgO°Òìô¸6‘„Y©»€µ‹BÝ)ene$nà‡ý‰Ã_̘1™Ã·ÓñUôHt•¾²>´¨ÑBGÆoî;Qó¿Ââ4é˾FõÍgZ©=»ÔšŽdÓ
+m‚<· Þët¾ÔX¥¸?
+‹|½°Ý ä*Ë…Y¸81ÈÆò°fOÃGéÚuÖ¬åÐñŽ¿ÏZ}8>ïË3Á©o/®Ë»''ýŽçÚîÃKT´Z!ðƾM¨ë·%·>š
ýðv!̧&-&pä&yÞ“¸?wÍ-²Õ®t²h~
+üHÝ+ƒ.|ánÀb‡’ý’ç‘,q4à›ˆÀ‘¾Ó§ÃËýa¸Â–Ñ,‹kO
°Æ2«Ð¸øõ Àå˜p÷òLÎ/s2Á´f4ýØ }ç
RâHLR¿*õ'0–"ÎÞOZ*‡;ÿ -Ö5›2P«eaU&¸ñN2ŽÜ~†U=FAO9’FüR«ïOd]± †EâèÂ;T½Ôhâƒbæ´Á"ßPIƒz¼’»îVU-äNÑò
g±HÂs”z^ÿ bÛ5€ñ>¦]Mwhô ŠdÇ_YÅrŸÜwÆ3è[´Ú®”qÄ÷óJtíQ§$Zö®E$±(*yFz£tþÌ*µŽÖÕèFþ'
VDqU;a‘£`#>Aœxb`9Úù I£õ`ÿ d½ÁÂþH
FøuÌÄüÐÐ÷ð#ß½‚;´,ƒ‹Êÿ L²/bîû?µ„„qçØòïc⸢¸*â3;SaEË]ÝÚ
+H”
+Ó‚¶/#Óá8ZŒU7´5…׎G-æEpÖ+¥¾)VšvWÛ¦ZÎ
+ï…×
+NÇB”®*¾òNDÓ ec\$¢½pÌ ¨ÅP®)ˆ1Å]±¾*Ñ9Yy±V³W6lUºæÊx«uÚ™³fÅW‘\°òÀÌN*°ãN8åb«lqTÅ\1êq˜àqTLrÓ7˜“Š£R{æi
02¿l¶lU¶rs·þI$‚ÊþvbPG@½·9ÃUYØ"Š“¶z3òëO/”}Gøf¼4½ þÜ¿ ŽI…ó-9§ÕžV~‚ê¾V¼ó7™nÖ4cɺ($ä£J²Ñ|¥(…¹KxÇ ]’£¢‘Gykap–0¥%›â4o݉¦ËolèZhQÙÅ*Ê 9šg$Ìù_+î:l‡&k1Ê8¡.D\cå]þiU¿˜MýÊÛY[³ŽîO£ÄõÉ«*¨
~#×#¯y¥ù~(õ[~ö˜ûœa¨ÜKgõËÅVUðNÄ× º|¤¹òW˜ëLgŽWä¯'ÏFO…½÷Â/0Yý~Ù-íT<ÈâŒiP:Ÿ
+ºÕÞ³}õKI=p y«?”8·±[86`! ïSVñoÄΘÎ0ÀÜLïrGHÒ‘ÙùN£ê—nñ§Â¿S†¢çBÑ…hãaÕSw?>o7§šly¼rI,Gv™9qùPl3›¾¥©K0I¤rkÒ§ ˜é°í/1˜Ý™ž)ü¿k×õ¯0þS°+ ûE¾ÑÁ¾W»±HM¿Ù¹$–¯íxSä0ƒBòÕÍÕ’L÷A ý‚9øQ‰ÞYÞi3ÖBj»£oÚ#껫¹×œ™ñ橨÷wy=IT¹û#r}°üÏ¥‡1ó$ŽÁzü°ºËÌÉqŠíCFGqÔý
+îÓB»`ËÊ:õ¥ãøäÀ'é¢äæ×Ø£ç m+÷™×ú5äEf)2ªË_ÀŒjê6±F#µâ¨‚Šª:–FšÆÚŠÞ踔ãjF[V.@Šp²vŽQ.Xî¹ÇŸÎËw×{£)55댔‚´®i"U£òͽoc\
#…ÜÐ
ÉÊç30G×ÈwN¹Jɳv}å+¿ÔÛM” #/}’0e•Ñ»ˆKLj=0ÖÓd¤Qš†ïþg
R4‰B
+ª( è2‚rß«“›ÃŽ8Ç‚²ÿ ¾>+û`;ë/ãÎ6SÈÔ`ºœ¬ïa¢”$%Dr!Nc·b‰B¢ì Å3WÌ=\ŠîMÉB^ÞIfUøòˆì}Ž>;ˆ/b"6ë۸ř#™
+°ÔaLº{Ù?¯lIJî;Œ˜£·"ߌc”xO¢c‘è}ê÷6Kéе[±Âد$µ“Ó~ž8p¤Ïbj{á6©°Ø®ã'âäéÏ8²ogäS%¸õAË0 Ž£"ÐjÍÀÛá…®ª$`욳ò3R¶.È6=pžO„Päš)ÒUàÛ©Œ Õàú¼„ðž™d%{;^ËÖqÄiòýq_ñ±èN—áéŽ,k\Bg©ß&íœ$>8¨¹jR¸ÉLa›£dž½ñ=;àFœacŠ¢šrO\M¤Ä9f®*¹š¸ÊæÊÅ]—•›vlÙ±VÆ8hÅR˜«\sb”ÍŠ¹‡†Q]±Í”tÅT÷Ë\q׸ªÑséŠôßf®*¢Â™YlwÊÅW˜ŒªÓ1jâc…XÐu8ÚáÆ¦µíÊ’>pBÚóeŽ,rÉ3´E§~TÐYVêðIMiÜûì6³Jof´ê Aöa„tQî|rIk|!Féwíe+ÎÀÈJñ Ÿ‘ÈÃ'¢¹Ù÷‘ŸhÏ. Ï!1‰ mÒ(íjé-õdxZ%PÞÄi÷`[Ÿ2^ÊJ,ØSíá‘ýNîòk‚‘ÞÎK4ž;â–vâØ‘½Y;±þYÈH C[ÓFL“yÌYDóó,‡JÒe½‘o/÷UåFÝžŸÃ$:²É.™¦½~œM êqÌ#x˜ò!U†êkî2c£i?¢à!˜4OP·È2¡½ñ›‘¨Á¢ÓbÅ“ÄÉ/§{ý“+khí£
+¿Iñ8ö;æ©Æ’ ©Êå0@ {Ý9²I&Éo6j~eŠÕÌ6ª%lXý‘ò¦Ò®î/-„÷‘øiµF—"zLÐÄ3N<1—+æ~i57³»0Î*•ëìza²:JÐòVèp&¥§¥ü$ÆUûü¶F´íNçOº6³Ê‹)ì}²T$6æá§Ž£–²c¨÷ŽðÉdv²~G{v;ÿ ’pZºH¡†SÜb$WnŽ:d<ê×zMÓĨ
8ž”ÄG‹ÞƒI=H†Ù!Ðÿ ýlà*¨ }kÈAë„Ñy\|iCíŽmx¿ÙS„B@¦:V9Ù‰‰R›Ež‰º±ë‡Vj ¹ø°7éE'“ ´×Pž
’˜&¹‰ËN¸ª²-zã¸S2WR*q27Á-@1oЍ2q¼i‚)+Ѝ•‹'Àâ« iåX׹Γ¢éñØÚ«RŒG\ŠyvÈ4ž»…r\÷aUcNBNÁç{g<²LiñŸL~¯z=%M6¡=ñej
b×èº8Š1uz;
+¤mÐ{œ1¸£-#j7ìÐÐaί!%A¦¾¥-~Ö<$›AÒê3ä9fw<‡@;‘·-|Qˆäãâ?ˆÂ)于B²rVî
Aû
NEjÖƒÛÝž¤²qYPu?µ’¹‡+s§ŸL1áë(ïÜÅc–sП£Þ‘P¯÷5ÂÛTE@G€Àë©\áø9|s˜â†(æ¢æâ1ñòQ5Ä¡+@Näá£Ü ¢*ûÔnÀ¾Œ¤øck%Â~‘µ2M”@F ·w=q/QÑÞ¸Y$ìýN&à§:[õH“#½”ç÷¦ž8èØ‚1Ó çÈåŽ.ŽÒÇ ÚöLu„€NØv$[»w^¼”þApHv8wb ¡« ]>®8Ǫ Æ@°{¨Ìs:‘Ðâ8yæ?Bè¸nÓ-ýÓåqC êÜÔË9†×S52éŽT'iWD•Æ,d`ˆÁb²Pb.¤`–$
ð4\UI‰Æ×1leqUäâg/(âS6_lت ŒÕãÓ|Ucœes¡Š®Œâ@cÔŠ£âaMðTtꬄm‹,个:W
+6;â)1ä)ãZRNhž²(÷ÅŒ¹s'ü·ùÙ'ŽKvŒŸˆn1Ó
+Eú˜PìCœ©ÐbÆ'åÂoíR”îqX¤†¸‘ÉkŒl´œ
/!¸&ª»õ>øœŒÒ
+Sn‰£Ëf¢¹ZQñít‹öp:YÜHi>¸¿è}C/Aéò8vY ú¤‘.K…sÄâ¾Ã IПJ‘Šà‚}ø×rL`45Åg`5ÌduË0?P*2-Âý[Þ§ËlL}ºxâ†7F"àƒ\-‘®U1ˆÜ~˜>ÎT”pq¾k›4;¡ú0_BÕã3 ¯52ðñ¨ÜøcGò“ÆÃÅC‚šÞ£le(Dðñú¡d”¹ð€Æ OL{ÄTãƒ²Ž S` À˜ŠW4POцV–Ëm4’`|(7ûÎ õß1’Fíá‰kÉ“6":_#Õ‰ÀîÄš³Ë)6`Ol[cßH¡°(fÚ»Ó+ë2
ÄÚf}»e¤%cÐcïjáy@µŽÌ£|loÃsׇñüUìqlW äÕ` Á³Çl± ë!…8ò邚g„p×0:¶^§lz°k‘“¹ÌW””ðÅ‘á;wÓ R¸k\5i‘jpŽLñADG°pÎÂჅ®*ø*Ö« #quŒ£.©®¯iõ»"Ôýä_«!L¥XƒÛ: £!º0*~‘}F/NêE‰Æ˜cÙOï0Kø}CãÍFV;,±Ü9pZGJJÃR»SR¥
+†Ú»œ^87ÅŠ”ªâalÃî$$Óåª ²—®>A¾'ÓVâ)‰6X'¸ªžlwÊ™±TYmˆ={à°¸UðÅP§,f#,Uz{â¼q4÷Å+Š"ƒÈå“ÛŠ¯ã£4‘~c®)j¾¥Â'‰–3 FDô–M¼0·Štùa4Õpæíx¬h:"÷ï„÷ ‚FT&“Ÿ¾Ô|HqJíÉ;!²Ñ'~˜ehÖVà™aYnUý]01Ûl2@å&D#IÕ¾ $š z*Š“ØÝÁ*qæµð9‰ŠGQÔæ[‰ÕXŒ‰¸ŽÍ¬Dð×&o©i6wñ1
+¢JlÃ9Ég+)‡6ú½ÂÑY‰a$)¨Ã¸i×¾1¸ó6ÇJsèeÖ\xÏØÃb¸xˆÃ{]B"(øÿ J’݉Q¶JiÐäènc‡Qï¨eKyl *Dza2Ï*ôlwÖ¤ÁÃæ×!ô̦‘„’ùR\ûáY¸“Çiºœ<-Ÿ—\R6SDœrqf½zQFÂÄ8ß”Tb@c“‹q¸™Ž0Í7†+ÇÛð¨Ü`ø"â9@!Ó¯UǦ üJ3IÀöÀ¯8vf!ŽCxÒ8ÞÀÃuÄ‹«š¯LbðÅíÔŽ4áB ˜ßÍ:õ‡e Øf[Vs°Ãm/áæûS!ÇÍŸ@2ÜŽIQ¨8 <—~Øa=¬}@û°)„ÐñS‚ØÇ<&E0è1HÁmÛŽÖVr@Ã$ 1+šxà6"Ê‹¶25 r=pd–P¯€Ì-€ÀãŒ±á«æ‚rofßÍH‹£G·L7³dr^3öôŽôŽ×–¬Zbºl-#o¾´)ÔŠŸ‰.». Â\d¡mÔ§úND5b
ì§ß%óJ°Fó¶ÁAærq/3¹îrPçngeDœ¹2tªø¨‘ß2æ¦Zœ±Ý"" 8co"‚+…hqtzwÅS£2\-߆i«ßi+Š¢nF¸G[lM·ÅTÛs–+¾*¤S|UËÕÅV*ãCS×¾*³ÑÍŽõ“Ô§lØ«\Ç|BMñçlMqU;ã‚å•®<)ªÂ(6Æò¦(Ã8«e«ŽøÁ«DàÝ9ßF=ð\1ÐÅ/“ç€ò-:“X2è–QrŠK3xíôaÉ_Pá½Ô•棨'õáÜ‹¬:m æz)9Û)§êk‰r‘NÙ'j#bD±Üã•‹
ñÁwÅG§›dÑ@Æcø–=0@‡Ò^N1`gù“Ñ ¸ÁV÷’@~sjÿ káùe4P~˧J3ø„ÝnẋŒ€WÇ
+îô¥jºûc•û-Š,ò¨ã\yrqáŽX‰–P=
+\tð:µ1§OÚªØ.^D׫•—:3ÈEñ–Ò'¾$Apʼn#ºTtÂtdO42špÚÑ–@+…EpM¤Þ›Pý‘²3Dʹ§!¡NªOÑåo)âOô®),«šK4&½2·V%H‘”O}©=ˆ¥PÔ`Y-™{a‚A,eË
--šái$`Ó¸Û
ÒËVq2"Å
+ŠÄ‡ ÉCèHí°¦
+‡C†%äÔøÃö¶¶z%Ö0ü ‘¿Ëý1î´_Žåmi²£IO·ã™µkf 8áN†D“Ð:¼“É’\P#Ÿà4špqR:â‹£ÁÔ¨úr¤ÔÆLRb)Œ·Ô}ABßê0zšÿ ÂH$<•?FD›ŠcAb‹¹ð©O]ñ¦U¦Øî,ŸÄIA›sÈ»u8Ã,·,I軜•·Frê”ê;qŒ|Î'kl_¨ÁM žbì>C`·Xã.Ä(Î6åK8ÇŒ@sý%Öi¬M,„*ާÝyŠÉ ‘âp¿\ÕEúµ¹øGR;äq«’Œ/räi;4e6£ˆr-¼Ó-KY–÷à_…;…C®^6»äÀ®Nã(bˆ†1¯V8ÁÄ•üqdp0³s€¸ÐÕÛ.F®1'mª3Ç•c°ãD´l~Œ"2¶ÉbdöËnJh„v8Þ¸ªeTãé\°•8ªÔ'©¦Øá1Aتz×6ôwÍŠ¬lHðC®8««Lz°ÄŽ]qUÏCÓeÅ+›RrúTPbo×h¶yx¾Zá5p^›uõ[¤“µwÀyLðdŒy˜šdº§îÑ™~Ñ&¸D&5ßï××fMÑ÷?%hAjdÑJ#Kš‘n[å®(Ü#ë”fŽ”Âævá¨0#qŽŽä.Î1üã~ùFÙdèF>öDƪb‘°ÜZñ>,
ywËá]ð;ÀcïŽ2ç|hsaļK2÷¨RF5®XI|pÒ+u¨Æ½”‰½:aâgùœwÃa‰!ý¬YmîHä»ÓÃ+ƒ/lUUW¢s5qáø©†O†A_ž/¶—¯ÂqKIöÅp#¡ªjü”cø‘Ù3k0~Ã1 mŒcz`1w2lqâw˜npÑLqå‰Þ@rÆ@N$Ñ‘¸ÅÈÊ=0·‚ºÚõ¢4n˜ukwÀ
+Ôøde·c‚-Ë!vÀbÓŸM€žG½šÚۤǘdóYÙÆ˜/ü£s_£"k¨Ê¶â(Iý¦Å¬ZÏ©;Õ»(Üåf=î6Šfå’FÚ nSè幓â/é§eP?_\»‰ÄH^F4ɩ—ÔÝÜ$a–>ä}£ý0A¸Y…OÒ0SIÓÈeè9¡_S’GãmâqV¤‹ñ–¨ˆöÉ7ž
¸#ÃI|Ñ´UxØí‰Áræ@Ý~#Í‘
+ŒA4÷â¤
+0ÅÈŽLfN¯•¦QKÍAÅ9cŒµ·e÷íგ§Æh0]’P‰5¸òAzÒÈxB´ÌqOE†ÄòsöðÅÞöÆÚ¡œ)ûÏá…Óë°¯Ãl•?ÌßÓýa¹?»Ä@ïý©¤ƒ©ÂmRDCk ßöŽùr;ÍfôB™X7Øë‚ÿ åWkšû~å’:õ>‘‡Nr. (õÙÊÐéµY½B«—sÌ‹58øn.$1–'Àgs²ü¢Ñì_V¼Š6iIþf‘~]ùeLÁ…Ô©ÐlFeGM²3òˆý%ßñžê÷¼b!ë×ßXKvãò8]såbÒ¦[v {ë·¿[\l-ã[UÛ€QLgù«åÍN‘jV1Û øež3· ÿ 6~¯’8Øð'·ƒFÁ¼)†:~…©ß8H`c_cèÙþ[j4¿æ" »G±®#qç¿&h
+SJ´G‘~˵Di
ðÌùHø•ã=ãïa?屨F%LkâÛ“ÚþNiöÔkûØ’A`Ur/¯~mkƒ¶scì…ØˆÜùÃ\¹'Ô¹mýÎK6¸GÊ1â?4Q=çÞií)å iƒý*ñd#¯_×Tü³…‚ˆ“Jü=ó‚6¥{9¬“1úr½Y‰›T{äN¦ÎÈ~QOîg×ÿ *ôÝ^¾òôË*‘Ë‚ý¡ôg Ö|»¨h³´W10
+iZaç—¼ï«ès¡IXƧ¥s[ë¾SóÅMX%½Õ 2€7>ã$D2‹ú¿¤®>ñÕcååÑó²ŠâÊžÛ¯(´ËÕ2iqËQUU;Ÿ£9¦¹å=OC¸h牸ƒÖ™-9¢`Dë˜å/“.>ñL}T×|¨Ôå¬Mßú³ñß(d†økí›ôO.9±J
Áßaƒ^!ˆ´Xª“”+‹<}±œiŠ´¸êeR™E銴Ƙ“å±®0œUØ¥´fY‘s‰`Ý,®Ç_O&%à Ht²¹¾¯vËû ó8 7"NÔ…n[çü02še]VˆHs–ç↸Rǘ[Äàù78Ñ9 \èO†!é¸èr¹Ì˜˜$ÓúñŸ„†T7ò)ã/Ä=ðâkupv›»^ˆ¤aË‹ àœE÷¢xÇ"sNøÄ"n(ãã€íç15ì÷ÁÓ@³ÇÉ:फ़1 pÎÌO#ܽÚÁ…A®"ÇÒÜbOm*·|^rv8yunŒ!ˆq†^D žÝ³C
0Õí’(Ë¿Ð0¬¹õ*¸A¶üy¼@xyª1# SÇWÜæ™TR¸ÚR$"RïF›ã€¦+¦Æ¸ƒ[µza¶c06 ªR.xÐbY£5©Ã8,ùšuÕŠ¤dœl1üÆ!.v—Ç2ô&¸¸Ôgm™É^žø$GQ„€Ù,X¹˜’k±`z‘Ôø†¦ ÿ ÙýÑÿ ‚Ó#rÆW¦Ó-žfÈ?2ÉŸÌpþÄ#é?ÙßÌS¶È~Cúá
2èqá ŽƒMà¿y¿½3“Y¼“¬òŸ«[ë†ý¶ûðF˜%
¾(Šˆø/Híñá¡’Œz`À0ëOqNuäÊ~þ899[‰«™„==ò¯@XÜßRîe
+lV5ßþï¼áæ¯3i"H#ž©SBv?†
+ü¹ÕÏ(Ñ€#þdéAÑäQ±ç›,8£‹'†@6,:¶à‡×>fº’ðSÍšÝìë\5{îp–[Ë™«êJÍ^µ8+T·1\0÷Âíó<ò‰ÊFåÉʈ`6½wÇV1™‰Ê+‹Û¤R‹+=«ˆ™Y‰>øÓ›$g"(ÈŸyZ
+‚NÙ«S‰ŒQäU^1ƒ¢‹—lFÞ0i\5‚1N˜ªÀ¦hýxw‰ŠŸl30Õq&ˆ×¦#q$$U¢´Ÿ3ë:eÂH“±
+zTçXÒüç¡ùžÕlüÁ‘ÄMAËéñÎ7éoÓ[++m·Ëã¨?å=UÊCi‹í¼º=/Xü²Še7º‹qÞ‰Ô|ÆC.¼½¨ZËèI&0ÓEó¦`¸ŽÕÉ”_˜PI;û(æ¸O²å{ûå§ÃɾÒóú%ñFãËí;ÿ jžÖ¾®Ü§C›'?ò³/~² ŸW¡—ÇîÍ/æÃ»êün·/?“ÄŸ58«î1-ÁÌ&kxåÁ8·Šã({bª2ELÊkƒŸ¦ø€'¡ŠãÁ^q)˜ª†±“Ó¹½ñ´4`|(â‰x!”߯)=AÑ”0û©…¬ä5'Õ²úìTœ*‘O,¬:)ØÀÿ ß-—(å¾SHTÒ˜¤ Ť~ÑÆÛ%’"U!a^SÓ0yF#8æHFØÛÌDmÀ„Šñ“í¥Ýºâ-fŽ*¸ìöqÙ‰lœî%4KØå‡¦+,BUùá,°=wûK•‘@'4gÁáÔñ›éEݳDÕ펲¼ô›ƒýœ:žÙfJaÍŒ‘1 m„Ø·áÏ4<<¦Št`I—šPûŒñ4B n0•ìÖ¬·NàáìRAvœŠ÷Î6XäÀwõC¡c·X™¨Õ§†)žÍB˜zÖÈ7¦"êæÃlÒcÃŒ ͺD¸OzÌÌBtÃÉ#wøiŒ]/Ô7Â
3ÁžÏCe ŠY£=ðj^íIpÁ´IÙÄ_Eœ~ÎAr§K“™òn-F×ìïZå¨6_U´¹×ªœu¾™+°qÙü¬ È$ ó6•¼aßÁ TdŠ)ÞÝÂóÚÇê,"²
+Šô'ás°íD1@Ê•ø’hy±É€àÝѮ佬CŠÓMeÄôÉzX,‘ò]ü0Í‘Mœ}9'h*»òb†
+vƘðöK@•à7´uýœ—°Ç«„ºÒ^±Ó(í‹-¬Œv\) ° ĕɨÆò
+Vö!®Øéâ:Jz׳± ¦+u•¹jÐÓámÔgÕO,ü,{Ùª ·•µsm«¥¬;³¸Qí«ÍV[Ò©É•ŸpÏÊÝ9µo1%ʪµjs»ÙëV···št¤Ã”_j
+Ú™HÇëxÆÏ¹Ý`‡1Œž@‹æ3iÒÃrç‰ ‘£ùèß7yÜ»ÉUNàŽùÉu¯)ÏbYж¸c›÷˜ÈºÜ6F\>™0—ZbtÁ—”bÔ`p»æ0ÚPã‚âF_
+¦˜¼KS ¾,€
+SVá4Ã;g$€0ps ᥔi\U3w kÓmàR¦.ð/lU)ÞØå· ôÃ^Ûe1NÛb¨xª†°VÇ®!ð“×Ò«|SŸÑ›}ayõÍŠ±YF"ԦؼãT¸¡r¹˜Ú1;eÔ`„QƸªXØ
+àr(pÂF¡ ©Zâ«FØÉ 8âã&¸¥ëC³HŒU‘hó‹g¶?h|IóœëÅ·W§ÜµµÂ¸é]òCwÈdû2
+Ÿ|®B‹ªÏPOðäÜ{ú c5éÝŠÐeªqq¥N³0$$wK¢yU·0CÂòK‚
+½qëq
+í¦YIšÓëÞ5F6Ï¢uÃúz„¶°®xN´ÿ (a’zRŽHAeÒn˜ê×5c¨ùaÛ£l±iòoŠ|¸òdËw†ää 0ž)u&øj~dQôË©þ)d'4œ J²å ynÝΰÇà·Í‰[jWÇ©ñ/q‚ ÒBˆr8%4±]—¥“K˜ß™æ‰†KyÔTnp\6¨»O|žSا’c5'bÙ{‹®Ë!uŽFØœiS+95(Û÷kp‘Säµ'éä1msÈ–º¦Ÿ7˜t
+è=[«
+mOÚxHéþ¯Ýá„HðÃ%¤lB8ê:¦ çFü¶œK–ïñ+£)SÜÓ3±FÓïÎïw]šqæÃàÑcÖÍîOn·0¯
]ÔŽP,ƒ®HuÈ!ÑõéôÉþœú–Òuøu¯Ëáòk:T×v7|ï#^qÀPp:¯.F‡Ã*ɦÉU\zH~§i5',¯ð“d½w<ñ!§m±æ¦à0ú¬°–†D*èJ²°¡ue}S(·çÜÙª@¥²×áZ`¤·UÜᆞ÷·QÙÀUeÐs4Ç+Í6RùVSáY$vŽ¥kìX˜Ç’BãE~A1†|±â„%(Ý_š[us¤%ÞŠAãKP’úc¿ÁZ›SÕ¥¾s½°À1š2Ÿq’„7ÕÞv~€a&As?ì^÷ùM§®—¢^kŠpŒñ'ÄŠd^Óͯo¬ÝMÏíJHÉ·—Íùqt©±P+O‘Î1t¼—s^G6’È1q,qš›\Uî·Òžb»U†r$
µ|æÛR±úÕ²Ž,+AØçÑuG¶•±Û;o•|Ùm-°¶º`êÀ ÁÂ(fÃñ ¿á“Ä<ÁåÛˆ&r€Æ™– "j8¦z¿Vò¾«ÀÓZQª*W¾q5y>KYªl+ÛcÇœLÔ
ŽáæÂ”ß,œZâÒH«KŽÙ8˜“
+!°Ý`&¸¼[°…)‹@hÃ"”úÕ† 8Yk!Øa”U'|U6¶bS9 rÀ–ò@0KJ¥1V¹òÀ“c‹Tƒ˜Ç϶*‡QS\Ò_
+qÛ7ÕkŠ¥¾å›ý1˶lU„\U†Ø ¹W5'kuiŠH
+…ÅhsqQˆÈÁwª¼üp¾cLQ¥'®"渪ƒ18à*1¥wÛŠ2Ø¥KsN
X(w¿¤¼qT §‡:eò2}VãìþËx4í•;`"Ú³áŽhpËn ÷L`(A4*z0èqÓGûª©ß íµK‹qÄükàpÆ
Z ÈŠXÂrÚ«¶@ĺŒºmF3fÇÇ®!ÍÂD¢$*”Ù©ˆ°-‚Ä$Œa޲VßÄrAúdwÇ/1½pAQŒã\m³Š×%Ì©Ðà˜õY”Püð'µ}ñÙ®XñKêˆ)”z¢·ÛZ†÷Šäq—è8J-IÞ˜õµ’»dH\yG‡í¾Þ.ô>ø·ÕCvÈÅ›ÍÓ‘¦}Qà¦äý9GU—KN±ÈèZ*îqÆ3Ða*kÄH8&=R'?o‰iµy›*7(©ñÅ#‰Óâ‘ð¹uþ|FïYXÐ*nͰÈð–ŸËæ‘្2áýi€ðNÞø´#â¯a€,ÞI›âzá¬P•?v2Ûde¨à‚¸<³1í’˽{мUåAË!Þc½[xL*hÍ×
+ü·«5Ò5{æ~‹”á-¸ßdbÇ,Çn*Üùŧ•ÔôJÜ|FËQìyø–I,îåeHœìEÁZq²ó–‰Íx’=kJúF'åÛeÑõ_A‡ƒ3±ª.ÜýB]_Ì%¶´º’àFØlOÏ9uß›cލ‹ñë_™vH²H7qžyÔ hî£jæ>|–äÔÒáäìí6lÇ&HïåµûÓ{6^Zê‘Þ†§Îšoæ_—€IÄ”íVÛõçš0ûËþiÔt •í¤* Ô€p`Ê ‘᯦]ÞGÉÌŽc8€;‚/Ì^KÕt)Ýe…¸w¦F÷SB(GlôG–üï£ùÚÜišÚ'®ËÅg T|ç˜>B¹Ðnžâånß°èFO& w€©s¡¼d;âÈK¿—ã›8ü©Ö-uM&/NB™–Škû]³›ùÏB—BÕæ…äi…W׿Ðu(îP…Fw‹›mó+Fõá⺒'Ä»U¨?^Nã–žR®/èÌu÷4AÛ§/0ð¸)B0ëMÖç¶`Uˆ¦Ö´‰t{ù,åꄌ
+‹¶Ù™0LÇ»˜èÈ!okò]]GûÎOµk=sLk¸s§ÄëÏ8é7ŠÀô9Ý|Å5¸·¾H9•"'›ÄsFÇ„¼_ÍzKZÜ¿ÃMÎDŠÒµÏCy×Ê^°yÑy+nÎ'¬i2YÊÀ)‘ÏňË
È…‰á<% e±ð§Ä1Á7ßD€°¦`6&±ŽF´ ÷ÀVÊB°zì1J.ß®ôÀVÌ+†±(plUGêõÇ tßöÛ•aŠ©*‚wÅT.eøzãÊ :b¨?¼¯lØ;êý©¾lU„´`tÄ¥*3<ê®’äW®([+•8IsMÈW3â•å±¼«ã‚A\ # Àè‡BJâ«‹R\Äz¢‘\P¡-L
+È0\´±U*¾%êq‘[Àâ²l0#uÅH±]ìšxÅͼw½Eæ0¿®ãaŠh·èµœü
°'¶
¹¶ô÷ìz0èFVv4éîX2K»ý'¼ UkòÆÈ b¬xì1Éaɰ2£ÄM B{ezgÞ˜‘Œƒ¸Ãm£P
+Ò8匃S‚Ömñâ^˜Ú¡h‰"€z÷Á¾ŒJ*vÂôWìâ73ËГLÇ,“ôÊD;ĬB§ÌÜ›rpk\]*ãrF¥v±f±JoŽCC²3<5Í
+RœN):%c'LJ“?¶]ËUž›âãQ3çÐòe:\`ÇϵK|©¦Újº²ÛÝ·ÀªXF?i©ðƒí^¹‡Rú¥‰”öØaÿ åeôÚŸ™’RHPÛžŸÉ™rˆ'äâi42˪ñ2Dp–àõî;óÖK=rx›eäh2-‚O¿7¥Wó P|G¦s°s+9áËÄ6$ñ§¦Ç!ÂoMò—œ›IâÜ·3²Þúz…½—˜,Eb¹E”ñìHø‡ßžRI]zì_“þxe”ySS¬¶÷ýTø·òý9`Î$A¤9ÿ HuG
mÑê·öy‚À(5‘VŒ½öÎ+æß'IlîÊ›oÛ;L–ÙIõÍ=ýH ¯Ãür¯,íuËvVP³Óâ_·ÄE}P=;A>D>M»³’ÙʰÀÔΩç_,¥‹¹œÆX¸9_˜úœ8}2e ^ǘVÓon,.Rh©tÏCŸÌŸ—O$ÇÔžöŽæ„gœ•sºþRj‘jm΃9þù
+¨?ÍÛ%§™á#ù„H{¿‰f7÷ì𻨌W2'ò±ÎÓù#žá‰!#b+ìsÿ 8èRišä°q g }ù×¼™jž[òUÎ¥ ã$©Á ë¸ß'f2ËÝ*Œ|ø·A•ˆùn~$óÌæo0\µhþ¼"‰‰Ûj×F÷Pšv5,Çõâ1.cj%ye]
|™Gé ´œiN¹+е™l¥B\‰[\5€Pƒ’Á˜ã4w‰æQ±æ÷ý^µÕí¥Ñ¢€žÇ#sòj•y£Z©Ü0ÈN«Ëg:joŸL¸MoFh\ò-V¿Žeá‘’Ó.a‡Õé<ÃæMKO6“²‘J
+ÕÎçM
+H®$eZ
+œ€IÄÔ¦SªÅDdˆÚI„º‰´´Á±¨ë„ÖìÕ
â ÌFÄBÈPaÅ‹r &UaµžÀU7@:cŒ*wÆDãl]™iPqJd¡ è•FÙr°ÜàF˜.õÅ ›¾º8Ò¹±W—É1•À%qIØÖ¸ª§©]±¾4cÂ×r×ÁÑ aŠ«%µm‹¢q¦UR¢¸Ò«]±T9ޏÓQˆÎÀ)ÅRù¶8ƒ0*ihNø¥®*ºG
Øe—Æ“\Uhb¦ ÐŒ“h׋ÆÂsñ£cØäf˜µ´Ïo2L†Œ¦¸b"d8¹^þæFž9¡Âv#xž ³MsËz†‡$-yô®œ3.èþ>Ø=Å3®yRêÓÎÞU“K¼çÆ[~ƒ|æwÖ gw-ªÈ9FiÄŸãŽlÆIæçäy:½F3Š®ÌO#çÜP|+üN7Ñì>œ¶ïà>ñŠ»oðôùeÌ"‚X}±U‡òúm‰Ür€ E+ÓaâñÊÐ(÷ÀrÂ
N .[Bp·@˜ïiLÐãí‘‹R›a‘ƒŸUÇÖÖÊ5õ…ú{m¾Z’cÀ‘; ùT+PŒL
ðmükE’=Ñ…TàšbËâˆ?Q(M†É”Uwa\Jà~ðb± 'ï0œòÒZ½ðÉä•?MžD
+—Ó@©¨ ΡùA凰µ»š¢ ªûž¹—¤²K§ ɳCË$Û-çÿ š€ÿ ˆ§¯ó‚’Ï>ëk:ÜÓÀ(G¸9é?½>@9ðú\£&¿•ñWÍ–ó–ñË1?ê#7ðÈX;äûòé>¯kæ
\ÿ Ç„ˆ§ü©iümÂ.cÜS.LóÊz›Ö[gzx°;ŠgD¹Rxuoîe¡ t¸Ï1h·ímt¬zç¢<™©®«¦9Æ©ó—.Xò;I€»á?›Ïúxž•EC
+§8§mé\0§|õ·mõ5•‡Ä•Sž}óE¡tûw99GNGX¢ê~ö(«“ïÊïXy‚•…iJrkùq©E¦ë°É(¨,:æ&›ûÏ|HûÏ—Ä2?ÍÇCæ%
+ !…ióÉ]ïúGå¬gùkú†þkhÌÚŒ:¤d´W]Omð÷S#Oü¹†3±–§îš>œgÎ5ðµr÷€ºRVùœZ6U8œ¦²1ñ'½s[/¨ûËpä™G*-)ƒâŸáÂhÁ;àøZ´"”ʈ5zgMò_™>¬é6Ý7Îa¨Á–wÆÖ`Êԡ̽>@AÅ>G“\ÁúƒÝõý
+
rÓë6ª añØç(Ö<—<Ç öÉ&ƒç¦· >Ãn¹>·¸´ó‹Š%ãPG|ºçˆT‡;Ñ´·Î2Øi
+‘B1h‰-óV‹%µÃñS×!SÈКŽQ¨ÃGŽÒ{™B]4_Ä’† d`OÈÖ¸ikr€ùŠÍ’, ¸Ù.¨(-Ã×óßL$œ²õÂÉ®Î_®B‘€®°ÅWýd×®l QÇß6*Å^½1;à†¥1"qU=ǧ\N»âˆ1TR @ÁvôäSLZ'£W'¾ HpTÔ $¿4¼¸8¬z…Èý³£PÝpTq(ß0()ÁˆóÇ’%5;±ÕއQõ§qñ)ü0P|±â11
SÑiå8O|v!/9ŸT™c7ºÝÎí —Î¸¦rWçMMÌwÖ®*!’&
+?ÄÈùM¶ÌL‚§!ç÷¹@ìahs¢~_¯«åß5Ããfîu9ÏÜS:'“Ð|‡æ}LìeT¶Cþ±©ýYf›ë÷Ò>L 8=)WÈ`6³F9wǹ£$ÞU½hîPW¡vŽBFX¥È˜äêOÝG¡§½Å¸ÝÅ]GŽp:i3¬îÅO|ì~\¾—ôwª¦¼@${c5½3NÖôù®Ñ8ÉøÖ›|ÆY xfP¸“V‚8¨Žo–¦£j6Ø"Âv·&SB¤6ó=ˆ·ºp¢€„P‡f¢¬zÅÉ(à÷†@Üwø¾†Ò&¶ó¯•Ùˆk»1Ér\&üÉÔc²òõŽ–‡tŒrç+ò×I¿Ñ¬$Ö®ÓHn‡Û
|áåË?8é©é. ¬‘ºÿ ffò•rÇtrHr,9Æà>f
”ªÄâ÷vRZ\½¼Ÿi
dŽƒ|ÖJ&21—1Í´Ü*ÀÀlzà„~-€IâqU—mð*cõ3–Þ»àŸõ¼*›A}$r
ö®uO#ùmŠ ƒm³Œ 6ë¾
µÔ§µ`ÊÄS2ðf‰S±äK Dß_O]Xižb€É+
+•ñùg5ó'‘e‘Ý…\óĶÅCÈ~üêÚOšôý^%†ï‹ÔS—|¾²bÞ>¸1¸ËžÅóõîqhä2‘L
³Nꬻ³›òúÛËòjˆúýèŠ`AºHj|*Ç9~l˜þüý7ÁÓëý\N.š½Uàóÿ 'Š}ù©s®¶YÙpÑÂÒUã*GWðÎ!m_Y=?ï94ëZáNl†~X¹õåïÿ uÞçGøžÃù†UôíM@õŸ«º‹«pݳ¨Ý}òðS®G3eZï> ‡$ânë“í+ÿ %>±^Ÿ\‹ÜÙÊsaÓó—õHYt÷£°ß@¯ÖÖž#õäk6'÷ÑYý%õ¿”iú!ù¾ûàÝ‘·¾YE âjç§¶xï6_“ü§¼1ðûžµçX´ñu!‚jvâßÓy"×Ë2jQ^ùa¶R’ÿ À©Ì³eŸÅW_çÿ ›Ñ‡¿áñ}+çûoô*Å¡YÑ QgGr¡¹~WùBú¯:ÝÄÆÐ£z®ÄP-74®yû6?ä¥Î¨ÿ Wã׉?Ä?/'£yÝ,“]¸úœ‚Eäz?Xs/a‘üÙ‡©þôû‡ÝÕœ>”í‹ÓqŒ¬žO›(f›1`åá…™±Tæ2þ¡-N˜E›O¡7!‡¤ É~.¾®¾„ÞÖs™fÍŽ’øã\-9>¥õß“¦×YG×Ùb§ÅVR)ô6F¿0#ÒùÈVaË}¨ßÓ<Ó›?¿<¹/ŠŸ£õ³Ù9+‘ä¾=?^Zëör›0sy.\ú6Çzdf~;®ßF8Wö…3˜æÊÒõ
©›9~lUÿÙ
\ No newline at end of file
diff --git a/imageio/imageio-jpeg/src/test/resources/broken-jpeg/broken-adobe-segment-length-beyond-eof.jpg b/imageio/imageio-jpeg/src/test/resources/broken-jpeg/broken-adobe-segment-length-beyond-eof.jpg
new file mode 100755
index 00000000..3835f9ae
--- /dev/null
+++ b/imageio/imageio-jpeg/src/test/resources/broken-jpeg/broken-adobe-segment-length-beyond-eof.jpg
@@ -0,0 +1,13 @@
+ÿØÿî Adobe d€ ÿÛ „
+
+
#"""#''''''''''
+
!! !!''''''''''ÿÀ 5 )" ÿÄ¢
+
+ s !1AQa"q2‘¡±B#ÁRÑá3bð$r‚ñ%C4S’¢²csÂ5D'“£³6TdtÃÒâ&ƒ
+„”EF¤´VÓU(òãóÄÔäôeu…•¥µÅÕåõfv†–¦¶ÆÖæö7GWgw‡—§·Ç×ç÷8HXhxˆ˜¨¸ÈØèø)9IYiy‰™©¹ÉÙéù*:JZjzŠšªºÊÚêú m !1AQa"q‘2¡±ðÁÑá#BRbrñ3$4C‚’S%¢c²ÂsÒ5âDƒT“
+&6E'dtU7ò£³Ã()Óã󄔤´ÄÔäôeu…•¥µÅÕåõFVfv†–¦¶ÆÖæöGWgw‡—§·Ç×ç÷8HXhxˆ˜¨¸ÈØèø9IYiy‰™©¹ÉÙéù*:JZjzŠšªºÊÚêúÿÚ ? áÚF“}êiš|f[™Ø*/aâÄö¾z#@òO—¼·e žÝ.æ‚0g½›‹Çê´"Zxô¨È?åFs§‹|ˆVoB¦b¡Õ”òT*ƒ¸Éǘtëûý$
2x£Ä$6–Å&b*¥˜u>Ùƒ©Èrê±haªQ1–cûÒr}8¡ÜH¢OŸÎ¹“Y2œ2È#.ØýÖßTæzÑÚ‘)åí/PºW»ÒZ´6ñ¢Ñ¨ÄêŠøEI«yʾcYc’Ùlî8ýk~*êA¦ñE;í„·^aóG—m,/OÖŠñ¶H8.Óo¿Žo/\yŽûV“U¿±fH˜z±
+¬èGÂÞ‡{3´à3f=¡O凉âC(¿Drqú}\®îÜÌà:UþòUÃ!üã‡}¹Õ¿ƒ‡‹‡ÝÓ½ôo‘/´Í/ÊZ}œÓGª9˜wi¥M”w®Ù –êøÜÛ/ªBK›ÖuR8ú-EG":áEŸ’t/¨Ùꙣ•-âw•_> ví„w>b½ž ,^éî! óQ—‘]èFÙ¯ÇÙíNÒÔeÓ›Çæ9¥pÔw 9š|˜(ÏGŠ#,åitË“]¨hî#$¨Ú¿Æ™°óÔlßG¹dK°ÍlªS¡ý¾¿O3ɧéGD–»[ÈÁà¼èHëí™úþÂÏ©†A¦ÃàKKš8°ÄO÷sÓ}w(¬Hñ8šÑðr‰ÊG/NY\}qÌ=;wì OõÝ=ü©©Û½Ì·" _Q½ONE Ÿ‹²øçÿ
+Ÿùo·ÿ ƒÏAØ-æ£f`ÔdHEÂpXÀƒUy7.GâùÊ¿åY/ùðKšAâÒ ÃÆ†¬c–ã†òDÊ;Õs÷mùÂ2Q„½Pc.ø`eèÿ :&ÞŸ¢IkæO/Ú\\8ºˆÇÁ£CÄ5Ú ùd6/&]ß›”WœâW"BåY˜¯*m‘ÉÝ~
3P¹Óð ìA"£·ígcu–ÒêKÛ‚©coQRYË©
ë™íV»³²äæÉ!‹V|\Qæ!33Çû?"lzM>{9ˆ‰ÓYåÇA>ïÐ^TÖˆm:ÏC<‡ì:7„Rµ£m¾N5O+Û\iöúP$ê/9§cð€´æõ¥íˆZÜØù›X
r‘1¢FäÑ) =iÞ™-¶/”\GR¼—÷ÊU€Ø·ß—ö¯löŽ—S¥,s†Ë&/à3œxb%__Râè;?lYòãœ1ÉU â°ÒHBXzº6'×ç3¤”ÏPv‰Íhh3ƒÿ ÊÁÔß÷ä¿óCÏ6Éb¾_Ñåyêß[˜
+'vˆt3Œd†
hÀuƒ?7,ãTpðmp‰„cÁüÿ Q~—'‡
xFRðD|!’÷à'ˆËú»û¼‘ZwÖ¾½oõ:ýcÔ_KkÊ»}ó°ùþV‡øb?ˆ½?é<ÞŸöYijfÛ~,5áÿ yüuÇÿ Z÷þSô0Ë[ÝrëìëøKÕÿ -ÿ KV/«ˆÍÇ«'§ë•>{©ôäßÎgÎßSoª,?Tâ}o©—çOÚçÈW<3Î9²½Oç°‰qpCëâüýµÿ ÿ 7«N/«/Õõÿ ÖÐþóñIޝê}q¹P.Ü8Ö”ÿ e€1¹²Ã×ôÿ k³ßÇé|^\?«‡ô?ÿÙ
\ No newline at end of file
diff --git a/imageio/imageio-jpeg/src/test/resources/broken-jpeg/broken-bogus-data-prepended-real-jfif-start-at-4801.jpg b/imageio/imageio-jpeg/src/test/resources/broken-jpeg/broken-bogus-data-prepended-real-jfif-start-at-4801.jpg
new file mode 100644
index 00000000..43bc0f36
Binary files /dev/null and b/imageio/imageio-jpeg/src/test/resources/broken-jpeg/broken-bogus-data-prepended-real-jfif-start-at-4801.jpg differ
diff --git a/imageio/imageio-jpeg/src/test/resources/broken-jpeg/broken-bogus-segment-length.jpg b/imageio/imageio-jpeg/src/test/resources/broken-jpeg/broken-bogus-segment-length.jpg
new file mode 100644
index 00000000..448f364e
Binary files /dev/null and b/imageio/imageio-jpeg/src/test/resources/broken-jpeg/broken-bogus-segment-length.jpg differ
diff --git a/imageio/imageio-jpeg/src/test/resources/broken-jpeg/broken-invalid-adobe-ycc-gray.jpg b/imageio/imageio-jpeg/src/test/resources/broken-jpeg/broken-invalid-adobe-ycc-gray.jpg
new file mode 100644
index 00000000..e14599c1
Binary files /dev/null and b/imageio/imageio-jpeg/src/test/resources/broken-jpeg/broken-invalid-adobe-ycc-gray.jpg differ
diff --git a/imageio/imageio-jpeg/src/test/resources/broken-jpeg/broken-no-sof-ascii-transfer-mode.jpg b/imageio/imageio-jpeg/src/test/resources/broken-jpeg/broken-no-sof-ascii-transfer-mode.jpg
new file mode 100755
index 00000000..3835f9ae
--- /dev/null
+++ b/imageio/imageio-jpeg/src/test/resources/broken-jpeg/broken-no-sof-ascii-transfer-mode.jpg
@@ -0,0 +1,13 @@
+ÿØÿî Adobe d€ ÿÛ „
+
+
#"""#''''''''''
+
!! !!''''''''''ÿÀ 5 )" ÿÄ¢
+
+ s !1AQa"q2‘¡±B#ÁRÑá3bð$r‚ñ%C4S’¢²csÂ5D'“£³6TdtÃÒâ&ƒ
+„”EF¤´VÓU(òãóÄÔäôeu…•¥µÅÕåõfv†–¦¶ÆÖæö7GWgw‡—§·Ç×ç÷8HXhxˆ˜¨¸ÈØèø)9IYiy‰™©¹ÉÙéù*:JZjzŠšªºÊÚêú m !1AQa"q‘2¡±ðÁÑá#BRbrñ3$4C‚’S%¢c²ÂsÒ5âDƒT“
+&6E'dtU7ò£³Ã()Óã󄔤´ÄÔäôeu…•¥µÅÕåõFVfv†–¦¶ÆÖæöGWgw‡—§·Ç×ç÷8HXhxˆ˜¨¸ÈØèø9IYiy‰™©¹ÉÙéù*:JZjzŠšªºÊÚêúÿÚ ? áÚF“}êiš|f[™Ø*/aâÄö¾z#@òO—¼·e žÝ.æ‚0g½›‹Çê´"Zxô¨È?åFs§‹|ˆVoB¦b¡Õ”òT*ƒ¸Éǘtëûý$
2x£Ä$6–Å&b*¥˜u>Ùƒ©Èrê±haªQ1–cûÒr}8¡ÜH¢OŸÎ¹“Y2œ2È#.ØýÖßTæzÑÚ‘)åí/PºW»ÒZ´6ñ¢Ñ¨ÄêŠøEI«yʾcYc’Ùlî8ýk~*êA¦ñE;í„·^aóG—m,/OÖŠñ¶H8.Óo¿Žo/\yŽûV“U¿±fH˜z±
+¬èGÂÞ‡{3´à3f=¡O凉âC(¿Drqú}\®îÜÌà:UþòUÃ!üã‡}¹Õ¿ƒ‡‹‡ÝÓ½ôo‘/´Í/ÊZ}œÓGª9˜wi¥M”w®Ù –êøÜÛ/ªBK›ÖuR8ú-EG":áEŸ’t/¨Ùꙣ•-âw•_> ví„w>b½ž ,^éî! óQ—‘]èFÙ¯ÇÙíNÒÔeÓ›Çæ9¥pÔw 9š|˜(ÏGŠ#,åitË“]¨hî#$¨Ú¿Æ™°óÔlßG¹dK°ÍlªS¡ý¾¿O3ɧéGD–»[ÈÁà¼èHëí™úþÂÏ©†A¦ÃàKKš8°ÄO÷sÓ}w(¬Hñ8šÑðr‰ÊG/NY\}qÌ=;wì OõÝ=ü©©Û½Ì·" _Q½ONE Ÿ‹²øçÿ
+Ÿùo·ÿ ƒÏAØ-æ£f`ÔdHEÂpXÀƒUy7.GâùÊ¿åY/ùðKšAâÒ ÃÆ†¬c–ã†òDÊ;Õs÷mùÂ2Q„½Pc.ø`eèÿ :&ÞŸ¢IkæO/Ú\\8ºˆÇÁ£CÄ5Ú ùd6/&]ß›”WœâW"BåY˜¯*m‘ÉÝ~
3P¹Óð ìA"£·ígcu–ÒêKÛ‚©coQRYË©
ë™íV»³²äæÉ!‹V|\Qæ!33Çû?"lzM>{9ˆ‰ÓYåÇA>ïÐ^TÖˆm:ÏC<‡ì:7„Rµ£m¾N5O+Û\iöúP$ê/9§cð€´æõ¥íˆZÜØù›X
r‘1¢FäÑ) =iÞ™-¶/”\GR¼—÷ÊU€Ø·ß—ö¯löŽ—S¥,s†Ë&/à3œxb%__Râè;?lYòãœ1ÉU â°ÒHBXzº6'×ç3¤”ÏPv‰Íhh3ƒÿ ÊÁÔß÷ä¿óCÏ6Éb¾_Ñåyêß[˜
+'vˆt3Œd†
hÀuƒ?7,ãTpðmp‰„cÁüÿ Q~—'‡
xFRðD|!’÷à'ˆËú»û¼‘ZwÖ¾½oõ:ýcÔ_KkÊ»}ó°ùþV‡øb?ˆ½?é<ÞŸöYijfÛ~,5áÿ yüuÇÿ Z÷þSô0Ë[ÝrëìëøKÕÿ -ÿ KV/«ˆÍÇ«'§ë•>{©ôäßÎgÎßSoª,?Tâ}o©—çOÚçÈW<3Î9²½Oç°‰qpCëâüýµÿ ÿ 7«N/«/Õõÿ ÖÐþóñIޝê}q¹P.Ü8Ö”ÿ e€1¹²Ã×ôÿ k³ßÇé|^\?«‡ô?ÿÙ
\ No newline at end of file
diff --git a/imageio/imageio-jpeg/src/test/resources/broken-jpeg/broken-sos-before-sof.jpg b/imageio/imageio-jpeg/src/test/resources/broken-jpeg/broken-sos-before-sof.jpg
new file mode 100755
index 00000000..02d8062d
Binary files /dev/null and b/imageio/imageio-jpeg/src/test/resources/broken-jpeg/broken-sos-before-sof.jpg differ
diff --git a/imageio/imageio-jpeg/src/test/resources/jpeg/invalid-adobe-ycc-gray-with-metadata.jpg b/imageio/imageio-jpeg/src/test/resources/jpeg/invalid-adobe-ycc-gray-with-metadata.jpg
new file mode 100644
index 00000000..9d444a64
Binary files /dev/null and b/imageio/imageio-jpeg/src/test/resources/jpeg/invalid-adobe-ycc-gray-with-metadata.jpg differ
diff --git a/imageio/imageio-jpeg/src/test/resources/jpeg/jfif-16bit-dqt.jpg b/imageio/imageio-jpeg/src/test/resources/jpeg/jfif-16bit-dqt.jpg
new file mode 100644
index 00000000..23a00c70
Binary files /dev/null and b/imageio/imageio-jpeg/src/test/resources/jpeg/jfif-16bit-dqt.jpg differ
diff --git a/imageio/imageio-jpeg/src/test/resources/jpeg/jfif-bogus-empty-jfif-segment.jpg b/imageio/imageio-jpeg/src/test/resources/jpeg/jfif-bogus-empty-jfif-segment.jpg
new file mode 100644
index 00000000..270bcd43
Binary files /dev/null and b/imageio/imageio-jpeg/src/test/resources/jpeg/jfif-bogus-empty-jfif-segment.jpg differ
diff --git a/imageio/imageio-jpeg/src/test/resources/jpeg/jfif-component-id-out-of-range.jpg b/imageio/imageio-jpeg/src/test/resources/jpeg/jfif-component-id-out-of-range.jpg
new file mode 100644
index 00000000..92729cf4
Binary files /dev/null and b/imageio/imageio-jpeg/src/test/resources/jpeg/jfif-component-id-out-of-range.jpg differ
diff --git a/imageio/imageio-jpeg/src/test/resources/jpeg/jfif-exif-xmp-adobe-progressive-negative-component-count.jpg b/imageio/imageio-jpeg/src/test/resources/jpeg/jfif-exif-xmp-adobe-progressive-negative-component-count.jpg
new file mode 100644
index 00000000..7e8f6bf8
Binary files /dev/null and b/imageio/imageio-jpeg/src/test/resources/jpeg/jfif-exif-xmp-adobe-progressive-negative-component-count.jpg differ
diff --git a/imageio/imageio-jpeg/src/test/resources/jpeg/jfif-progressive-invalid-dht.jpg b/imageio/imageio-jpeg/src/test/resources/jpeg/jfif-progressive-invalid-dht.jpg
new file mode 100644
index 00000000..e7ab3271
Binary files /dev/null and b/imageio/imageio-jpeg/src/test/resources/jpeg/jfif-progressive-invalid-dht.jpg differ
diff --git a/imageio/imageio-jpeg/src/test/resources/jpeg/progressive-adobe-sof-bands-dont-match-sos-band-count.jpg b/imageio/imageio-jpeg/src/test/resources/jpeg/progressive-adobe-sof-bands-dont-match-sos-band-count.jpg
new file mode 100644
index 00000000..63adb6db
Binary files /dev/null and b/imageio/imageio-jpeg/src/test/resources/jpeg/progressive-adobe-sof-bands-dont-match-sos-band-count.jpg differ
diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractEntry.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractEntry.java
index 99f6b524..1b761e5a 100644
--- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractEntry.java
+++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractEntry.java
@@ -174,10 +174,57 @@ public abstract class AbstractEntry implements Entry {
AbstractEntry other = (AbstractEntry) pOther;
return identifier.equals(other.identifier) && (
- value == null && other.value == null || value != null && value.equals(other.value)
+ value == null && other.value == null || value != null && valueEquals(other)
);
}
+ private boolean valueEquals(final AbstractEntry other) {
+ return value.getClass().isArray() ? arrayEquals(value, other.value) : value.equals(other.value);
+ }
+
+ static boolean arrayEquals(final Object thisArray, final Object otherArray) {
+ // TODO: This is likely a utility method, and should be extracted
+ if (thisArray == otherArray) {
+ return true;
+ }
+ if (otherArray == null || thisArray == null || thisArray.getClass() != otherArray.getClass()) {
+ return false;
+ }
+
+ Class> componentType = thisArray.getClass().getComponentType();
+
+ if (componentType.isPrimitive()) {
+ if (thisArray instanceof byte[]) {
+ return Arrays.equals((byte[]) thisArray, (byte[]) otherArray);
+ }
+ if (thisArray instanceof char[]) {
+ return Arrays.equals((char[]) thisArray, (char[]) otherArray);
+ }
+ if (thisArray instanceof short[]) {
+ return Arrays.equals((short[]) thisArray, (short[]) otherArray);
+ }
+ if (thisArray instanceof int[]) {
+ return Arrays.equals((int[]) thisArray, (int[]) otherArray);
+ }
+ if (thisArray instanceof long[]) {
+ return Arrays.equals((long[]) thisArray, (long[]) otherArray);
+ }
+ if (thisArray instanceof boolean[]) {
+ return Arrays.equals((boolean[]) thisArray, (boolean[]) otherArray);
+ }
+ if (thisArray instanceof float[]) {
+ return Arrays.equals((float[]) thisArray, (float[]) otherArray);
+ }
+ if (thisArray instanceof double[]) {
+ return Arrays.equals((double[]) thisArray, (double[]) otherArray);
+ }
+
+ throw new AssertionError("Unsupported type:" + componentType);
+ }
+
+ return Arrays.equals((Object[]) thisArray, (Object[]) otherArray);
+ }
+
@Override
public String toString() {
String name = getFieldName();
diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java
index ae0e23d8..ac13be42 100644
--- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java
+++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java
@@ -38,6 +38,7 @@ import com.twelvemonkeys.lang.Validate;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
+import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.nio.ByteOrder;
@@ -52,6 +53,9 @@ import java.util.*;
* @version $Id: EXIFReader.java,v 1.0 Nov 13, 2009 5:42:51 PM haraldk Exp$
*/
public final class EXIFReader extends MetadataReader {
+
+ final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.metadata.exif.debug"));
+
static final Collection KNOWN_IFDS = Collections.unmodifiableCollection(Arrays.asList(TIFF.TAG_EXIF_IFD, TIFF.TAG_GPS_IFD, TIFF.TAG_INTEROP_IFD, TIFF.TAG_SUB_IFD));
@Override
@@ -80,16 +84,25 @@ public final class EXIFReader extends MetadataReader {
long directoryOffset = input.readUnsignedInt();
- return readDirectory(input, directoryOffset);
+ return readDirectory(input, directoryOffset, true);
}
- public Directory readDirectory(final ImageInputStream pInput, final long pOffset) throws IOException {
+ // TODO: Consider re-writing so that the linked IFD parsing is done externally to the method
+ protected Directory readDirectory(final ImageInputStream pInput, final long pOffset, final boolean readLinked) throws IOException {
List ifds = new ArrayList();
List entries = new ArrayList();
pInput.seek(pOffset);
long nextOffset = -1;
- int entryCount = pInput.readUnsignedShort();
+
+ int entryCount;
+ try {
+ entryCount = pInput.readUnsignedShort();
+ }
+ catch (EOFException e) {
+ // Treat EOF here as empty Sub-IFD
+ entryCount = 0;
+ }
for (int i = 0; i < entryCount; i++) {
EXIFEntry entry = readEntry(pInput);
@@ -104,27 +117,24 @@ public final class EXIFReader extends MetadataReader {
entries.add(entry);
}
- if (nextOffset == -1) {
- nextOffset = pInput.readUnsignedInt();
- }
+ if (readLinked) {
+ if (nextOffset == -1) {
+ nextOffset = pInput.readUnsignedInt();
+ }
- // Read linked IFDs
- if (nextOffset != 0) {
- CompoundDirectory next = (CompoundDirectory) readDirectory(pInput, nextOffset);
- for (int i = 0; i < next.directoryCount(); i++) {
- ifds.add((IFD) next.getDirectory(i));
+ // Read linked IFDs
+ if (nextOffset != 0) {
+ CompoundDirectory next = (CompoundDirectory) readDirectory(pInput, nextOffset, true);
+
+ for (int i = 0; i < next.directoryCount(); i++) {
+ ifds.add((IFD) next.getDirectory(i));
+ }
}
}
- // TODO: Make what sub-IFDs to parse optional? Or leave this to client code? At least skip the non-TIFF data?
- // TODO: Put it in the constructor?
+ // TODO: Consider leaving to client code what sub-IFDs to parse (but always parse TAG_SUB_IFD).
readSubdirectories(pInput, entries,
- Arrays.asList(TIFF.TAG_EXIF_IFD, TIFF.TAG_GPS_IFD, TIFF.TAG_INTEROP_IFD, TIFF.TAG_SUB_IFD
-// , TIFF.TAG_IPTC, TIFF.TAG_XMP
-// , TIFF.TAG_ICC_PROFILE
-// , TIFF.TAG_PHOTOSHOP
-// ,TIFF.TAG_MODI_OLE_PROPERTY_SET
- )
+ Arrays.asList(TIFF.TAG_EXIF_IFD, TIFF.TAG_GPS_IFD, TIFF.TAG_INTEROP_IFD, TIFF.TAG_SUB_IFD)
);
ifds.add(0, new IFD(entries));
@@ -149,7 +159,7 @@ public final class EXIFReader extends MetadataReader {
List subIFDs = new ArrayList(pointerOffsets.length);
for (long pointerOffset : pointerOffsets) {
- CompoundDirectory subDirectory = (CompoundDirectory) readDirectory(input, pointerOffset);
+ CompoundDirectory subDirectory = (CompoundDirectory) readDirectory(input, pointerOffset, false);
for (int j = 0; j < subDirectory.directoryCount(); j++) {
subIFDs.add((IFD) subDirectory.getDirectory(j));
@@ -221,20 +231,24 @@ public final class EXIFReader extends MetadataReader {
// Invalid tag, this is just for debugging
long offset = pInput.getStreamPosition() - 8l;
- System.err.printf("Bad EXIF");
- System.err.println("tagId: " + tagId + (tagId <= 0 ? " (INVALID)" : ""));
- System.err.println("type: " + type + " (INVALID)");
- System.err.println("count: " + count);
+ if (DEBUG) {
+ System.err.printf("Bad EXIF data @%08x\n", pInput.getStreamPosition());
+ System.err.println("tagId: " + tagId + (tagId <= 0 ? " (INVALID)" : ""));
+ System.err.println("type: " + type + " (INVALID)");
+ System.err.println("count: " + count);
+ }
pInput.mark();
pInput.seek(offset);
try {
- byte[] bytes = new byte[8 + Math.max(20, count)];
+ byte[] bytes = new byte[8 + Math.min(120, Math.max(24, count))];
int len = pInput.read(bytes);
- System.err.print(HexDump.dump(offset, bytes, 0, len));
- System.err.println(len < count ? "[...]" : "");
+ if (DEBUG) {
+ System.err.print(HexDump.dump(offset, bytes, 0, len));
+ System.err.println(len < count ? "[...]" : "");
+ }
}
finally {
pInput.reset();
@@ -276,6 +290,8 @@ public final class EXIFReader extends MetadataReader {
private static Object readValue(final ImageInputStream pInput, final short pType, final int pCount) throws IOException {
// TODO: Review value "widening" for the unsigned types. Right now it's inconsistent. Should we leave it to client code?
+ // TODO: New strategy: Leave data as is, instead perform the widening in EXIFEntry.getValue.
+ // TODO: Add getValueByte/getValueUnsignedByte/getValueShort/getValueUnsignedShort/getValueInt/etc... in API.
long pos = pInput.getStreamPosition();
@@ -461,7 +477,7 @@ public final class EXIFReader extends MetadataReader {
Directory directory;
if (args.length > 1) {
- directory = reader.readDirectory(stream, pos);
+ directory = reader.readDirectory(stream, pos, false);
}
else {
directory = reader.read(stream);
diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFWriter.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFWriter.java
new file mode 100644
index 00000000..1983ac04
--- /dev/null
+++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFWriter.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright (c) 2013, Harald Kuhr
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name "TwelveMonkeys" nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.twelvemonkeys.imageio.metadata.exif;
+
+import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
+import com.twelvemonkeys.imageio.metadata.Directory;
+import com.twelvemonkeys.imageio.metadata.Entry;
+import com.twelvemonkeys.lang.Validate;
+
+import javax.imageio.IIOException;
+import javax.imageio.stream.ImageOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Array;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.util.*;
+
+/**
+ * EXIFWriter
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: EXIFWriter.java,v 1.0 17.07.13 10:20 haraldk Exp$
+ */
+public class EXIFWriter {
+
+ static final int WORD_LENGTH = 2;
+ static final int LONGWORD_LENGTH = 4;
+ static final int ENTRY_LENGTH = 12;
+
+ public boolean write(final Collection entries, final ImageOutputStream stream) throws IOException {
+ return write(new IFD(entries), stream);
+ }
+
+ public boolean write(final Directory directory, final ImageOutputStream stream) throws IOException {
+ Validate.notNull(directory);
+ Validate.notNull(stream);
+
+ // TODO: Should probably validate that the directory contains only valid TIFF entries...
+ // the writer will crash on non-Integer ids and unsupported types
+ // TODO: Implement the above validation in IFD constructor?
+
+ writeTIFFHeader(stream);
+
+ if (directory instanceof CompoundDirectory) {
+ CompoundDirectory compoundDirectory = (CompoundDirectory) directory;
+
+ for (int i = 0; i < compoundDirectory.directoryCount(); i++) {
+ writeIFD(compoundDirectory.getDirectory(i), stream, false);
+ }
+ }
+ else {
+ writeIFD(directory, stream, false);
+ }
+
+ // Offset to next IFD (EOF)
+ stream.writeInt(0);
+
+ return true;
+ }
+
+ public void writeTIFFHeader(final ImageOutputStream stream) throws IOException {
+ // Header
+ ByteOrder byteOrder = stream.getByteOrder();
+ stream.writeShort(byteOrder == ByteOrder.BIG_ENDIAN ? TIFF.BYTE_ORDER_MARK_BIG_ENDIAN : TIFF.BYTE_ORDER_MARK_LITTLE_ENDIAN);
+ stream.writeShort(42);
+ }
+
+ public long writeIFD(final Collection entries, ImageOutputStream stream) throws IOException {
+ return writeIFD(new IFD(entries), stream, false);
+ }
+
+ private long writeIFD(final Directory original, ImageOutputStream stream, boolean isSubIFD) throws IOException {
+ // TIFF spec says tags should be in increasing order, enforce that when writing
+ Directory ordered = ensureOrderedDirectory(original);
+
+ // Compute space needed for extra storage first, then write the offset to the IFD, so that the layout is:
+ // IFD offset
+ //
+ // IFD entries (values/offsets)
+ long dataOffset = stream.getStreamPosition();
+ long dataSize = computeDataSize(ordered);
+
+ // Offset to this IFD
+ final long ifdOffset = stream.getStreamPosition() + dataSize + LONGWORD_LENGTH;
+
+ if (!isSubIFD) {
+ stream.writeInt(assertIntegerOffset(ifdOffset));
+ dataOffset += LONGWORD_LENGTH;
+
+ // Seek to offset
+ stream.seek(ifdOffset);
+ }
+ else {
+ dataOffset += WORD_LENGTH + ordered.size() * ENTRY_LENGTH;
+ }
+
+ // Write directory
+ stream.writeShort(ordered.size());
+
+ for (Entry entry : ordered) {
+ // Write tag id
+ stream.writeShort((Integer) entry.getIdentifier());
+ // Write tag type
+ stream.writeShort(getType(entry));
+ // Write value count
+ stream.writeInt(getCount(entry));
+
+ // Write value
+ if (entry.getValue() instanceof Directory) {
+ // TODO: This could possibly be a compound directory, in which case the count should be > 1
+ stream.writeInt(assertIntegerOffset(dataOffset));
+ long streamPosition = stream.getStreamPosition();
+ stream.seek(dataOffset);
+ Directory subIFD = (Directory) entry.getValue();
+ writeIFD(subIFD, stream, true);
+ dataOffset += computeDataSize(subIFD);
+ stream.seek(streamPosition);
+ }
+ else {
+ dataOffset += writeValue(entry, dataOffset, stream);
+ }
+ }
+
+ return ifdOffset;
+ }
+
+ public long computeIFDSize(final Collection directory) {
+ return WORD_LENGTH + computeDataSize(new IFD(directory)) + directory.size() * ENTRY_LENGTH;
+ }
+
+ private long computeDataSize(final Directory directory) {
+ long dataSize = 0;
+
+ for (Entry entry : directory) {
+ int length = EXIFReader.getValueLength(getType(entry), getCount(entry));
+
+ if (length < 0) {
+ throw new IllegalArgumentException(String.format("Unknown size for entry %s", entry));
+ }
+
+ if (length > LONGWORD_LENGTH) {
+ dataSize += length;
+ }
+
+ if (entry.getValue() instanceof Directory) {
+ Directory subIFD = (Directory) entry.getValue();
+ long subIFDSize = WORD_LENGTH + subIFD.size() * ENTRY_LENGTH + computeDataSize(subIFD);
+ dataSize += subIFDSize;
+ }
+ }
+
+ return dataSize;
+ }
+
+ private Directory ensureOrderedDirectory(final Directory directory) {
+ if (!isSorted(directory)) {
+ List entries = new ArrayList(directory.size());
+
+ for (Entry entry : directory) {
+ entries.add(entry);
+ }
+
+ Collections.sort(entries, new Comparator() {
+ public int compare(Entry left, Entry right) {
+ return (Integer) left.getIdentifier() - (Integer) right.getIdentifier();
+ }
+ });
+
+ return new IFD(entries);
+ }
+
+ return directory;
+ }
+
+ private boolean isSorted(final Directory directory) {
+ int lastTag = 0;
+
+ for (Entry entry : directory) {
+ int tag = ((Integer) entry.getIdentifier()) & 0xffff;
+
+ if (tag < lastTag) {
+ return false;
+ }
+
+ lastTag = tag;
+ }
+
+ return true;
+ }
+
+ private long writeValue(Entry entry, long dataOffset, ImageOutputStream stream) throws IOException {
+ short type = getType(entry);
+ int valueLength = EXIFReader.getValueLength(type, getCount(entry));
+
+ if (valueLength <= LONGWORD_LENGTH) {
+ writeValueInline(entry.getValue(), type, stream);
+
+ // Pad
+ for (int i = valueLength; i < LONGWORD_LENGTH; i++) {
+ stream.write(0);
+ }
+
+ return 0;
+ }
+ else {
+ writeValueAt(dataOffset, entry.getValue(), type, stream);
+
+ return valueLength;
+ }
+ }
+
+ private int getCount(Entry entry) {
+ Object value = entry.getValue();
+ return value instanceof String ? ((String) value).getBytes(Charset.forName("UTF-8")).length + 1 : entry.valueCount();
+ }
+
+ private void writeValueInline(Object value, short type, ImageOutputStream stream) throws IOException {
+ if (value.getClass().isArray()) {
+ switch (type) {
+ case TIFF.TYPE_BYTE:
+ stream.write((byte[]) value);
+ break;
+ case TIFF.TYPE_SHORT:
+ short[] shorts;
+
+ if (value instanceof short[]) {
+ shorts = (short[]) value;
+ }
+ else if (value instanceof int[]) {
+ int[] ints = (int[]) value;
+ shorts = new short[ints.length];
+
+ for (int i = 0; i < ints.length; i++) {
+ shorts[i] = (short) ints[i];
+ }
+
+ }
+ else if (value instanceof long[]) {
+ long[] longs = (long[]) value;
+ shorts = new short[longs.length];
+
+ for (int i = 0; i < longs.length; i++) {
+ shorts[i] = (short) longs[i];
+ }
+ }
+ else {
+ throw new IllegalArgumentException("Unsupported type for TIFF SHORT: " + value.getClass());
+ }
+
+ stream.writeShorts(shorts, 0, shorts.length);
+ break;
+ case TIFF.TYPE_LONG:
+ int[] ints;
+
+ if (value instanceof int[]) {
+ ints = (int[]) value;
+ }
+ else if (value instanceof long[]) {
+ long[] longs = (long[]) value;
+ ints = new int[longs.length];
+
+ for (int i = 0; i < longs.length; i++) {
+ ints[i] = (int) longs[i];
+ }
+ }
+ else {
+ throw new IllegalArgumentException("Unsupported type for TIFF SHORT: " + value.getClass());
+ }
+
+ stream.writeInts(ints, 0, ints.length);
+
+ break;
+
+ case TIFF.TYPE_RATIONAL:
+ Rational[] rationals = (Rational[]) value;
+ for (Rational rational : rationals) {
+ stream.writeInt((int) rational.numerator());
+ stream.writeInt((int) rational.denominator());
+ }
+
+ // TODO: More types
+
+ default:
+ throw new IllegalArgumentException("Unsupported TIFF type: " + type);
+ }
+ }
+// else if (value instanceof Directory) {
+// writeIFD((Directory) value, stream, false);
+// }
+ else {
+ switch (type) {
+ case TIFF.TYPE_BYTE:
+ stream.writeByte((Integer) value);
+ break;
+ case TIFF.TYPE_ASCII:
+ byte[] bytes = ((String) value).getBytes(Charset.forName("UTF-8"));
+ stream.write(bytes);
+ stream.write(0);
+ break;
+ case TIFF.TYPE_SHORT:
+ stream.writeShort((Integer) value);
+ break;
+ case TIFF.TYPE_LONG:
+ stream.writeInt(((Number) value).intValue());
+ break;
+ case TIFF.TYPE_RATIONAL:
+ Rational rational = (Rational) value;
+ stream.writeInt((int) rational.numerator());
+ stream.writeInt((int) rational.denominator());
+ break;
+ // TODO: More types
+
+ default:
+ throw new IllegalArgumentException("Unsupported TIFF type: " + type);
+ }
+ }
+ }
+
+ private void writeValueAt(long dataOffset, Object value, short type, ImageOutputStream stream) throws IOException {
+ stream.writeInt(assertIntegerOffset(dataOffset));
+ long position = stream.getStreamPosition();
+ stream.seek(dataOffset);
+ writeValueInline(value, type, stream);
+ stream.seek(position);
+ }
+
+ private short getType(Entry entry) {
+ if (entry instanceof EXIFEntry) {
+ EXIFEntry exifEntry = (EXIFEntry) entry;
+ return exifEntry.getType();
+ }
+
+ Object value = Validate.notNull(entry.getValue());
+
+ boolean array = value.getClass().isArray();
+ if (array) {
+ value = Array.get(value, 0);
+ }
+
+ // Note: This "narrowing" is to keep data consistent between read/write.
+ // TODO: Check for negative values and use signed types?
+ if (value instanceof Byte) {
+ return TIFF.TYPE_BYTE;
+ }
+ if (value instanceof Short) {
+ if (!array && (Short) value < Byte.MAX_VALUE) {
+ return TIFF.TYPE_BYTE;
+ }
+
+ return TIFF.TYPE_SHORT;
+ }
+ if (value instanceof Integer) {
+ if (!array && (Integer) value < Short.MAX_VALUE) {
+ return TIFF.TYPE_SHORT;
+ }
+
+ return TIFF.TYPE_LONG;
+ }
+ if (value instanceof Long) {
+ if (!array && (Long) value < Integer.MAX_VALUE) {
+ return TIFF.TYPE_LONG;
+ }
+ }
+
+ if (value instanceof Rational) {
+ return TIFF.TYPE_RATIONAL;
+ }
+
+ if (value instanceof String) {
+ return TIFF.TYPE_ASCII;
+ }
+
+ // TODO: More types
+
+ throw new UnsupportedOperationException(String.format("Method getType not implemented for entry of type %s/value of type %s", entry.getClass(), value.getClass()));
+ }
+
+ private int assertIntegerOffset(long offset) throws IIOException {
+ if (offset > Integer.MAX_VALUE - (long) Integer.MIN_VALUE) {
+ throw new IIOException("Integer overflow for TIFF stream");
+ }
+
+ return (int) offset;
+ }
+}
diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java
index c6904e76..d169e690 100644
--- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java
+++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java
@@ -76,7 +76,6 @@ public interface TIFF {
11 = FLOAT Single precision (4-byte) IEEE format.
12 = DOUBLE Double precision (8-byte) IEEE format.
- TODO: Verify IFD type
See http://www.awaresystems.be/imaging/tiff/tifftags/subifds.html
13 = IFD, same as LONG
diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEG.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEG.java
index 265e8fc2..5bd34e15 100644
--- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEG.java
+++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEG.java
@@ -68,13 +68,13 @@ public interface JPEG {
int APP15 = 0xFFEF;
// Start of Frame segment markers (SOFn).
- /** SOF0: Baseline DCT, Huffman encoded. */
+ /** SOF0: Baseline DCT, Huffman coding. */
int SOF0 = 0xFFC0;
- /** SOF0: Extended DCT, Huffman encoded. */
+ /** SOF0: Extended DCT, Huffman coding. */
int SOF1 = 0xFFC1;
- /** SOF2: Progressive DCT, Huffman encoded. */
+ /** SOF2: Progressive DCT, Huffman coding. */
int SOF2 = 0xFFC2;
- /** SOF3: Lossless sequential, Huffman encoded. */
+ /** SOF3: Lossless sequential, Huffman coding. */
int SOF3 = 0xFFC3;
/** SOF5: Sequential DCT, differential Huffman coding. */
int SOF5 = 0xFFC5;
@@ -86,7 +86,7 @@ public interface JPEG {
int SOF9 = 0xFFC9;
/** SOF10: Progressive DCT, arithmetic coding. */
int SOF10 = 0xFFCA;
- /** SOF11: Lossless sequential, arithmetic encoded. */
+ /** SOF11: Lossless sequential, arithmetic coding. */
int SOF11 = 0xFFCB;
/** SOF13: Sequential DCT, differential arithmetic coding. */
int SOF13 = 0xFFCD;
diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGQuality.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGQuality.java
index f4d0ae29..623c0ef9 100644
--- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGQuality.java
+++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGQuality.java
@@ -232,7 +232,7 @@ public final class JPEGQuality {
throw new IIOException("Duplicate DQT table index: " + num);
}
- if (bits > 1) {
+ if (bits < 0 || bits > 1) {
throw new IIOException("Bad DQT bit info: " + bits);
}
@@ -247,11 +247,13 @@ public final class JPEGQuality {
for (int j = 0, qtDataLength = qtData.length; j < qtDataLength; j++) {
tables[num][j] = (short) (qtData[j] & 0xff);
}
+
break;
case 1:
for (int j = 0, qtDataLength = qtData.length; j < qtDataLength; j += 2) {
tables[num][j / 2] = (short) ((qtData[j] & 0xff) << 8 | (qtData[j + 1] & 0xff));
}
+
break;
}
}
diff --git a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/MetadataReaderAbstractTest.java b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/MetadataReaderAbstractTest.java
index 6f220006..189d6c28 100644
--- a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/MetadataReaderAbstractTest.java
+++ b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/MetadataReaderAbstractTest.java
@@ -40,9 +40,8 @@ import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
-import java.util.Arrays;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertNotNull;
/**
* ReaderAbstractTest
@@ -54,6 +53,7 @@ import static org.junit.Assert.*;
public abstract class MetadataReaderAbstractTest {
static {
IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi());
+ ImageIO.setUseCache(false);
}
protected final URL getResource(final String name) throws IOException {
@@ -96,46 +96,7 @@ public abstract class MetadataReaderAbstractTest {
}
private static boolean valueEquals(final Object expected, final Object actual) {
- return expected.getClass().isArray() ? arrayEquals(expected, actual) : expected.equals(actual);
- }
-
- private static boolean arrayEquals(final Object expected, final Object actual) {
- Class> componentType = expected.getClass().getComponentType();
-
- if (actual == null || !actual.getClass().isArray() || actual.getClass().getComponentType() != componentType) {
- return false;
- }
-
- return componentType.isPrimitive() ? primitiveArrayEquals(componentType, expected, actual) : Arrays.equals((Object[]) expected, (Object[]) actual);
- }
-
- private static boolean primitiveArrayEquals(Class> componentType, Object expected, Object actual) {
- if (componentType == boolean.class) {
- return Arrays.equals((boolean[]) expected, (boolean[]) actual);
- }
- else if (componentType == byte.class) {
- return Arrays.equals((byte[]) expected, (byte[]) actual);
- }
- else if (componentType == char.class) {
- return Arrays.equals((char[]) expected, (char[]) actual);
- }
- else if (componentType == double.class) {
- return Arrays.equals((double[]) expected, (double[]) actual);
- }
- else if (componentType == float.class) {
- return Arrays.equals((float[]) expected, (float[]) actual);
- }
- else if (componentType == int.class) {
- return Arrays.equals((int[]) expected, (int[]) actual);
- }
- else if (componentType == long.class) {
- return Arrays.equals((long[]) expected, (long[]) actual);
- }
- else if (componentType == short.class) {
- return Arrays.equals((short[]) expected, (short[]) actual);
- }
-
- throw new AssertionError("Unsupported type:" + componentType);
+ return expected.getClass().isArray() ? AbstractEntry.arrayEquals(expected, actual) : expected.equals(actual);
}
public void describeTo(final Description description) {
diff --git a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReaderTest.java b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReaderTest.java
index 5e184bd4..1249de6c 100644
--- a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReaderTest.java
+++ b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReaderTest.java
@@ -190,4 +190,90 @@ public class EXIFReaderTest extends MetadataReaderAbstractTest {
assertNotNull(exif);
assertEquals(0, exif.size()); // EXIFTool reports "Warning: Bad ExifIFD directory"
}
+
+ @Test
+ public void testReadExifJPEGWithInteropSubDirR98() throws IOException {
+ ImageInputStream stream = ImageIO.createImageInputStream(getResource("/jpeg/exif-with-interop-subdir-R98.jpg"));
+ stream.seek(30);
+
+ CompoundDirectory directory = (CompoundDirectory) createReader().read(new SubImageInputStream(stream, 1360));
+ assertEquals(17, directory.size());
+ assertEquals(2, directory.directoryCount());
+
+ Directory exif = (Directory) directory.getEntryById(TIFF.TAG_EXIF_IFD).getValue();
+ assertNotNull(exif);
+ assertEquals(23, exif.size());
+
+ // The interop IFD is empty (entry count is 0)
+ Directory interop = (Directory) exif.getEntryById(TIFF.TAG_INTEROP_IFD).getValue();
+ assertNotNull(interop);
+ assertEquals(2, interop.size());
+
+ assertNotNull(interop.getEntryById(1)); // InteropIndex
+ assertEquals("ASCII", interop.getEntryById(1).getTypeName());
+ assertEquals("R98", interop.getEntryById(1).getValue()); // Known values: R98, THM or R03
+
+ assertNotNull(interop.getEntryById(2)); // InteropVersion
+ assertEquals("UNDEFINED", interop.getEntryById(2).getTypeName());
+ assertArrayEquals(new byte[] {'0', '1', '0', '0'}, (byte[]) interop.getEntryById(2).getValue());
+ }
+
+ @Test
+ public void testReadExifJPEGWithInteropSubDirEmpty() throws IOException {
+ ImageInputStream stream = ImageIO.createImageInputStream(getResource("/jpeg/exif-with-interop-subdir-empty.jpg"));
+ stream.seek(30);
+
+ CompoundDirectory directory = (CompoundDirectory) createReader().read(new SubImageInputStream(stream, 1360));
+ assertEquals(11, directory.size());
+ assertEquals(1, directory.directoryCount());
+
+ Directory exif = (Directory) directory.getEntryById(TIFF.TAG_EXIF_IFD).getValue();
+ assertNotNull(exif);
+ assertEquals(24, exif.size());
+
+ // The interop IFD is empty (entry count is 0)
+ Directory interop = (Directory) exif.getEntryById(TIFF.TAG_INTEROP_IFD).getValue();
+ assertNotNull(interop);
+ assertEquals(0, interop.size());
+ }
+
+ @Test
+ public void testReadExifJPEGWithInteropSubDirEOF() throws IOException {
+ ImageInputStream stream = ImageIO.createImageInputStream(getResource("/jpeg/exif-with-interop-subdir-eof.jpg"));
+ stream.seek(30);
+
+ CompoundDirectory directory = (CompoundDirectory) createReader().read(new SubImageInputStream(stream, 236));
+ assertEquals(8, directory.size());
+ assertEquals(1, directory.directoryCount());
+
+ Directory exif = (Directory) directory.getEntryById(TIFF.TAG_EXIF_IFD).getValue();
+ assertNotNull(exif);
+ assertEquals(5, exif.size());
+
+ // The interop IFD isn't there (offset points to outside the TIFF structure)...
+ // Have double-checked using ExifTool, which says "Warning : Bad InteropOffset SubDirectory start"
+ Directory interop = (Directory) exif.getEntryById(TIFF.TAG_INTEROP_IFD).getValue();
+ assertNotNull(interop);
+ assertEquals(0, interop.size());
+ }
+
+ @Test
+ public void testReadExifJPEGWithInteropSubDirBad() throws IOException {
+ ImageInputStream stream = ImageIO.createImageInputStream(getResource("/jpeg/exif-with-interop-subdir-bad.jpg"));
+ stream.seek(30);
+
+ CompoundDirectory directory = (CompoundDirectory) createReader().read(new SubImageInputStream(stream, 12185));
+ assertEquals(16, directory.size());
+ assertEquals(2, directory.directoryCount());
+
+ Directory exif = (Directory) directory.getEntryById(TIFF.TAG_EXIF_IFD).getValue();
+ assertNotNull(exif);
+ assertEquals(26, exif.size());
+
+ // JPEG starts at offset 1666 and length 10519, interop IFD points to offset 1900...
+ // Have double-checked using ExifTool, which says "Warning : Bad InteropIFD directory"
+ Directory interop = (Directory) exif.getEntryById(TIFF.TAG_INTEROP_IFD).getValue();
+ assertNotNull(interop);
+ assertEquals(0, interop.size());
+ }
}
diff --git a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/exif/EXIFWriterTest.java b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/exif/EXIFWriterTest.java
new file mode 100644
index 00000000..a425a7c7
--- /dev/null
+++ b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/exif/EXIFWriterTest.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (c) 2013, Harald Kuhr
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name "TwelveMonkeys" nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.twelvemonkeys.imageio.metadata.exif;
+
+import com.twelvemonkeys.imageio.metadata.AbstractDirectory;
+import com.twelvemonkeys.imageio.metadata.AbstractEntry;
+import com.twelvemonkeys.imageio.metadata.Directory;
+import com.twelvemonkeys.imageio.metadata.Entry;
+import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
+import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
+import com.twelvemonkeys.io.FastByteArrayOutputStream;
+import org.junit.Test;
+
+import javax.imageio.ImageIO;
+import javax.imageio.spi.IIORegistry;
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.stream.ImageOutputStream;
+import javax.imageio.stream.ImageOutputStreamImpl;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * EXIFWriterTest
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: EXIFWriterTest.java,v 1.0 18.07.13 09:53 haraldk Exp$
+ */
+public class EXIFWriterTest {
+ static {
+ IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi());
+ ImageIO.setUseCache(false);
+ }
+
+ protected final URL getResource(final String name) throws IOException {
+ return getClass().getResource(name);
+ }
+
+ protected final ImageInputStream getDataAsIIS() throws IOException {
+ return ImageIO.createImageInputStream(getData());
+ }
+
+ // @Override
+ protected InputStream getData() throws IOException {
+ return getResource("/exif/exif-jpeg-segment.bin").openStream();
+ }
+
+// @Override
+ protected EXIFReader createReader() {
+ return new EXIFReader();
+ }
+
+ protected EXIFWriter createWriter() {
+ return new EXIFWriter();
+ }
+
+ @Test
+ public void testWriteReadSimple() throws IOException {
+ ArrayList entries = new ArrayList();
+ entries.add(new EXIFEntry(TIFF.TAG_ORIENTATION, 1, TIFF.TYPE_SHORT));
+ entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, 1600, TIFF.TYPE_SHORT));
+ entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {});
+ entries.add(new EXIFEntry(TIFF.TAG_ARTIST, "Harald K.", TIFF.TYPE_ASCII));
+ entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
+ Directory directory = new AbstractDirectory(entries) {};
+
+ ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
+ ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
+ new EXIFWriter().write(directory, imageStream);
+ imageStream.flush();
+
+ assertEquals(output.size(), imageStream.getStreamPosition());
+
+ byte[] data = output.toByteArray();
+
+ assertEquals(106, data.length);
+ assertEquals('M', data[0]);
+ assertEquals('M', data[1]);
+ assertEquals(0, data[2]);
+ assertEquals(42, data[3]);
+
+ Directory read = new EXIFReader().read(new ByteArrayImageInputStream(data));
+
+ assertNotNull(read);
+ assertEquals(5, read.size());
+
+ // TODO: Assert that the tags are written in ascending order (don't test the read directory, but the file structure)!
+
+ assertNotNull(read.getEntryById(TIFF.TAG_SOFTWARE));
+ assertEquals("TwelveMonkeys ImageIO", read.getEntryById(TIFF.TAG_SOFTWARE).getValue());
+
+ assertNotNull(read.getEntryById(TIFF.TAG_IMAGE_WIDTH));
+ assertEquals(1600, read.getEntryById(TIFF.TAG_IMAGE_WIDTH).getValue());
+
+ assertNotNull(read.getEntryById(TIFF.TAG_IMAGE_HEIGHT));
+ assertEquals(900, read.getEntryById(TIFF.TAG_IMAGE_HEIGHT).getValue());
+
+ assertNotNull(read.getEntryById(TIFF.TAG_ORIENTATION));
+ assertEquals(1, read.getEntryById(TIFF.TAG_ORIENTATION).getValue());
+
+ assertNotNull(read.getEntryById(TIFF.TAG_ARTIST));
+ assertEquals("Harald K.", read.getEntryById(TIFF.TAG_ARTIST).getValue());
+ }
+
+ @Test
+ public void testWriteMotorola() throws IOException {
+ ArrayList entries = new ArrayList();
+ entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
+ entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, Integer.MAX_VALUE, TIFF.TYPE_LONG));
+ Directory directory = new AbstractDirectory(entries) {};
+
+ ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
+ ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
+
+ imageStream.setByteOrder(ByteOrder.BIG_ENDIAN); // BE = Motorola
+
+ new EXIFWriter().write(directory, imageStream);
+ imageStream.flush();
+
+ assertEquals(output.size(), imageStream.getStreamPosition());
+
+ byte[] data = output.toByteArray();
+
+ assertEquals(60, data.length);
+ assertEquals('M', data[0]);
+ assertEquals('M', data[1]);
+ assertEquals(0, data[2]);
+ assertEquals(42, data[3]);
+
+ Directory read = new EXIFReader().read(new ByteArrayImageInputStream(data));
+
+ assertNotNull(read);
+ assertEquals(2, read.size());
+ assertNotNull(read.getEntryById(TIFF.TAG_SOFTWARE));
+ assertEquals("TwelveMonkeys ImageIO", read.getEntryById(TIFF.TAG_SOFTWARE).getValue());
+ assertNotNull(read.getEntryById(TIFF.TAG_IMAGE_WIDTH));
+ assertEquals((long) Integer.MAX_VALUE, read.getEntryById(TIFF.TAG_IMAGE_WIDTH).getValue());
+ }
+
+ @Test
+ public void testWriteIntel() throws IOException {
+ ArrayList entries = new ArrayList();
+ entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
+ entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, Integer.MAX_VALUE, TIFF.TYPE_LONG));
+ Directory directory = new AbstractDirectory(entries) {};
+
+ ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
+ ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
+
+ imageStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); // LE = Intel
+
+ new EXIFWriter().write(directory, imageStream);
+ imageStream.flush();
+
+ assertEquals(output.size(), imageStream.getStreamPosition());
+
+ byte[] data = output.toByteArray();
+
+ assertEquals(60, data.length);
+ assertEquals('I', data[0]);
+ assertEquals('I', data[1]);
+ assertEquals(42, data[2]);
+ assertEquals(0, data[3]);
+
+ Directory read = new EXIFReader().read(new ByteArrayImageInputStream(data));
+
+ assertNotNull(read);
+ assertEquals(2, read.size());
+ assertNotNull(read.getEntryById(TIFF.TAG_SOFTWARE));
+ assertEquals("TwelveMonkeys ImageIO", read.getEntryById(TIFF.TAG_SOFTWARE).getValue());
+ assertNotNull(read.getEntryById(TIFF.TAG_IMAGE_WIDTH));
+ assertEquals((long) Integer.MAX_VALUE, read.getEntryById(TIFF.TAG_IMAGE_WIDTH).getValue());
+ }
+
+ @Test
+ public void testNesting() throws IOException {
+ EXIFEntry artist = new EXIFEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO", TIFF.TYPE_ASCII);
+
+ EXIFEntry subSubSubSubIFD = new EXIFEntry(TIFF.TAG_SUB_IFD, new IFD(Collections.singletonList(artist)), TIFF.TYPE_LONG);
+ EXIFEntry subSubSubIFD = new EXIFEntry(TIFF.TAG_SUB_IFD, new IFD(Collections.singletonList(subSubSubSubIFD)), TIFF.TYPE_LONG);
+ EXIFEntry subSubIFD = new EXIFEntry(TIFF.TAG_SUB_IFD, new IFD(Collections.singletonList(subSubSubIFD)), TIFF.TYPE_LONG);
+ EXIFEntry subIFD = new EXIFEntry(TIFF.TAG_SUB_IFD, new IFD(Collections.singletonList(subSubIFD)), TIFF.TYPE_LONG);
+
+ Directory directory = new IFD(Collections.singletonList(subIFD));
+
+ ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
+ ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
+
+ new EXIFWriter().write(directory, imageStream);
+ imageStream.flush();
+
+ assertEquals(output.size(), imageStream.getStreamPosition());
+
+ Directory read = new EXIFReader().read(new ByteArrayImageInputStream(output.toByteArray()));
+
+ assertNotNull(read);
+ assertEquals(1, read.size());
+ assertEquals(subIFD, read.getEntryById(TIFF.TAG_SUB_IFD)); // Recursively tests content!
+ }
+
+ @Test
+ public void testReadWriteRead() throws IOException {
+ Directory original = createReader().read(getDataAsIIS());
+
+ ByteArrayOutputStream output = new FastByteArrayOutputStream(256);
+ ImageOutputStream imageOutput = ImageIO.createImageOutputStream(output);
+
+ try {
+ createWriter().write(original, imageOutput);
+ }
+ finally {
+ imageOutput.close();
+ }
+
+ Directory read = createReader().read(new ByteArrayImageInputStream(output.toByteArray()));
+
+ assertEquals(original, read);
+ }
+
+ @Test
+ public void testComputeIFDSize() throws IOException {
+ ArrayList entries = new ArrayList();
+ entries.add(new EXIFEntry(TIFF.TAG_ORIENTATION, 1, TIFF.TYPE_SHORT));
+ entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, 1600, TIFF.TYPE_SHORT));
+ entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {});
+ entries.add(new EXIFEntry(TIFF.TAG_ARTIST, "Harald K.", TIFF.TYPE_ASCII));
+ entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
+
+ EXIFWriter writer = createWriter();
+
+ ImageOutputStream stream = new NullImageOutputStream();
+ writer.write(new IFD(entries), stream);
+
+ assertEquals(stream.getStreamPosition(), writer.computeIFDSize(entries) + 12);
+ }
+
+ @Test
+ public void testComputeIFDSizeNested() throws IOException {
+ EXIFEntry artist = new EXIFEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO", TIFF.TYPE_ASCII);
+
+ EXIFEntry subSubSubSubIFD = new EXIFEntry(TIFF.TAG_SUB_IFD, new IFD(Collections.singletonList(artist)), TIFF.TYPE_LONG);
+ EXIFEntry subSubSubIFD = new EXIFEntry(TIFF.TAG_SUB_IFD, new IFD(Collections.singletonList(subSubSubSubIFD)), TIFF.TYPE_LONG);
+ EXIFEntry subSubIFD = new EXIFEntry(TIFF.TAG_SUB_IFD, new IFD(Collections.singletonList(subSubSubIFD)), TIFF.TYPE_LONG);
+ EXIFEntry subIFD = new EXIFEntry(TIFF.TAG_SUB_IFD, new IFD(Collections.singletonList(subSubIFD)), TIFF.TYPE_LONG);
+
+ List entries = Collections.singletonList(subIFD);
+
+ EXIFWriter writer = createWriter();
+
+ ImageOutputStream stream = new NullImageOutputStream();
+ writer.write(new IFD(entries), stream);
+
+ assertEquals(stream.getStreamPosition(), writer.computeIFDSize(entries) + 12);
+ }
+
+ private static class NullImageOutputStream extends ImageOutputStreamImpl {
+ @Override
+ public void write(int b) throws IOException {
+ streamPos++;
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ streamPos += len;
+ }
+
+ @Override
+ public int read() throws IOException {
+ throw new UnsupportedOperationException("Method read not implemented");
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ throw new UnsupportedOperationException("Method read not implemented");
+ }
+ }
+}
diff --git a/imageio/imageio-metadata/src/test/resources/jpeg/exif-with-interop-subdir-R98.jpg b/imageio/imageio-metadata/src/test/resources/jpeg/exif-with-interop-subdir-R98.jpg
new file mode 100644
index 00000000..cf0e4c43
Binary files /dev/null and b/imageio/imageio-metadata/src/test/resources/jpeg/exif-with-interop-subdir-R98.jpg differ
diff --git a/imageio/imageio-metadata/src/test/resources/jpeg/exif-with-interop-subdir-bad.jpg b/imageio/imageio-metadata/src/test/resources/jpeg/exif-with-interop-subdir-bad.jpg
new file mode 100644
index 00000000..dee98118
Binary files /dev/null and b/imageio/imageio-metadata/src/test/resources/jpeg/exif-with-interop-subdir-bad.jpg differ
diff --git a/imageio/imageio-metadata/src/test/resources/jpeg/exif-with-interop-subdir-empty.jpg b/imageio/imageio-metadata/src/test/resources/jpeg/exif-with-interop-subdir-empty.jpg
new file mode 100644
index 00000000..3572745e
Binary files /dev/null and b/imageio/imageio-metadata/src/test/resources/jpeg/exif-with-interop-subdir-empty.jpg differ
diff --git a/imageio/imageio-metadata/src/test/resources/jpeg/exif-with-interop-subdir-eof.jpg b/imageio/imageio-metadata/src/test/resources/jpeg/exif-with-interop-subdir-eof.jpg
new file mode 100644
index 00000000..9b8594b7
Binary files /dev/null and b/imageio/imageio-metadata/src/test/resources/jpeg/exif-with-interop-subdir-eof.jpg differ
diff --git a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/dcx/DCXProviderInfo.java b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/dcx/DCXProviderInfo.java
new file mode 100644
index 00000000..264a7701
--- /dev/null
+++ b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/dcx/DCXProviderInfo.java
@@ -0,0 +1,33 @@
+package com.twelvemonkeys.imageio.plugins.dcx;
+
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
+
+/**
+ * DCXProviderInfo.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: DCXProviderInfo.java,v 1.0 20/03/15 harald.kuhr Exp$
+ */
+final class DCXProviderInfo extends ReaderWriterProviderInfo {
+ protected DCXProviderInfo() {
+ super(
+ DCXProviderInfo.class,
+ new String[]{
+ "dcx",
+ "DCX"
+ },
+ new String[]{"dcx"},
+ new String[]{
+ // No official IANA record exists
+ "image/dcx",
+ "image/x-dcx",
+ },
+ "com.twelvemkonkeys.imageio.plugins.dcx.DCXImageReader",
+ new String[] {"com.twelvemkonkeys.imageio.plugins.dcx.DCXImageReaderSpi"},
+ null, null,
+ false, null, null, null, null,
+ true, null, null, null, null
+ );
+ }
+}
diff --git a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReaderSpi.java b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReaderSpi.java
index dfd41f09..bd3ab064 100755
--- a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReaderSpi.java
+++ b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReaderSpi.java
@@ -28,48 +28,20 @@
package com.twelvemonkeys.imageio.plugins.pcx;
-import com.twelvemonkeys.imageio.spi.ProviderInfo;
-import com.twelvemonkeys.imageio.util.IIOUtil;
+import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
import javax.imageio.ImageReader;
-import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.util.Locale;
-public final class PCXImageReaderSpi extends ImageReaderSpi {
+public final class PCXImageReaderSpi extends ImageReaderSpiBase {
/**
* Creates a {@code PCXImageReaderSpi}.
*/
public PCXImageReaderSpi() {
- this(IIOUtil.getProviderInfo(PCXImageReaderSpi.class));
- }
-
- private PCXImageReaderSpi(final ProviderInfo providerInfo) {
- super(
- providerInfo.getVendorName(),
- providerInfo.getVersion(),
- new String[]{
- "pcx",
- "PCX"
- },
- new String[]{"pcx"},
- new String[]{
- // No official IANA record exists
- "image/pcx",
- "image/x-pcx",
- },
- "com.twelvemkonkeys.imageio.plugins.pcx.PCXImageReader",
- new Class[] {ImageInputStream.class},
- null,
- true, // supports standard stream metadata
- null, null, // native stream format name and class
- null, null, // extra stream formats
- true, // supports standard image metadata
- null, null,
- null, null // extra image metadata formats
- );
+ super(new PCXProviderInfo());
}
@Override public boolean canDecodeInput(final Object source) throws IOException {
diff --git a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXProviderInfo.java b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXProviderInfo.java
new file mode 100644
index 00000000..e55e8c0c
--- /dev/null
+++ b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXProviderInfo.java
@@ -0,0 +1,33 @@
+package com.twelvemonkeys.imageio.plugins.pcx;
+
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
+
+/**
+ * PCXProviderInfo.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: PCXProviderInfo.java,v 1.0 20/03/15 harald.kuhr Exp$
+ */
+final class PCXProviderInfo extends ReaderWriterProviderInfo {
+ protected PCXProviderInfo() {
+ super(
+ PCXProviderInfo.class,
+ new String[]{
+ "pcx",
+ "PCX"
+ },
+ new String[]{"pcx"},
+ new String[]{
+ // No official IANA record exists
+ "image/pcx",
+ "image/x-pcx",
+ },
+ "com.twelvemkonkeys.imageio.plugins.pcx.PCXImageReader",
+ new String[] {"com.twelvemkonkeys.imageio.plugins.pcx.PCXImageReaderSpi"},
+ null, null,
+ false, null, null, null, null,
+ true, null, null, null, null
+ );
+ }
+}
diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/BitMap.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/BitMap.java
new file mode 100644
index 00000000..20ad04fd
--- /dev/null
+++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/BitMap.java
@@ -0,0 +1,11 @@
+package com.twelvemonkeys.imageio.plugins.pict;
+
+/**
+ * BitMap.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: BitMap.java,v 1.0 20/02/15 harald.kuhr Exp$
+ */
+final class BitMap {
+}
diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/BitMapPattern.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/BitMapPattern.java
index de6f80f2..4eb79f01 100755
--- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/BitMapPattern.java
+++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/BitMapPattern.java
@@ -29,10 +29,9 @@
package com.twelvemonkeys.imageio.plugins.pict;
import java.awt.*;
-import java.awt.image.WritableRaster;
-import java.awt.image.DataBufferByte;
-import java.awt.image.BufferedImage;
-import java.awt.image.Raster;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.*;
/**
* BitMapPattern
@@ -43,22 +42,46 @@ import java.awt.image.Raster;
*/
final class BitMapPattern extends Pattern {
+ private final byte[] pattern;
+
BitMapPattern(final Paint pColor) {
- super(pColor);
+ this(pColor, null);
}
public BitMapPattern(final byte[] pPattern) {
- this(create8x8Pattern(pPattern));
+ this(create8x8Pattern(pPattern), pPattern);
+ }
+
+ private BitMapPattern(final Paint pColor, final byte[] pPattern) {
+ super(pColor);
+
+ pattern = pPattern;
+ }
+
+ // TODO: Refactor, don't need both BitMapPattern constructors and create8x8Pattern methods?
+ public BitMapPattern(final byte[] pPattern, Color fg, Color bg) {
+ this(create8x8Pattern(pPattern, fg, bg));
}
BitMapPattern(final int pPattern) {
this(create8x8Pattern(pPattern));
}
- private static TexturePaint create8x8Pattern(final int pPattern) {
- // TODO: Creating a special purpose Pattern might be faster than piggy-backing on TexturePaint
- WritableRaster raster = QuickDraw.MONOCHROME.createCompatibleWritableRaster(8, 8);
- byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
+ private static Paint create8x8Pattern(final int pPattern) {
+// // TODO: Creating a special purpose Pattern might be faster than piggy-backing on TexturePaint
+// WritableRaster raster = QuickDraw.MONOCHROME.createCompatibleWritableRaster(8, 8);
+// byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
+//
+// for (int i = 0; i < data.length; i += 4) {
+// data[i ] = (byte) ((pPattern >> 24) & 0xFF);
+// data[i + 1] = (byte) ((pPattern >> 16) & 0xFF);
+// data[i + 2] = (byte) ((pPattern >> 8) & 0xFF);
+// data[i + 3] = (byte) ((pPattern ) & 0xFF);
+// }
+//
+// BufferedImage img = new BufferedImage(QuickDraw.MONOCHROME, raster, false, null);
+// return new TexturePaint(img, new Rectangle(8, 8));
+ byte[] data = new byte[8];
for (int i = 0; i < data.length; i += 4) {
data[i ] = (byte) ((pPattern >> 24) & 0xFF);
@@ -67,13 +90,57 @@ final class BitMapPattern extends Pattern {
data[i + 3] = (byte) ((pPattern ) & 0xFF);
}
- BufferedImage img = new BufferedImage(QuickDraw.MONOCHROME, raster, false, null);
- return new TexturePaint(img, new Rectangle(8, 8));
+ return create8x8Pattern(data);
}
- private static TexturePaint create8x8Pattern(final byte[] pPattern) {
+ private static Paint create8x8Pattern(final byte[] pPattern) {
WritableRaster raster = Raster.createPackedRaster(new DataBufferByte(pPattern, 8), 8, 8, 1, new Point());
BufferedImage img = new BufferedImage(QuickDraw.MONOCHROME, raster, false, null);
return new TexturePaint(img, new Rectangle(8, 8));
}
+
+ private static Paint create8x8Pattern(final byte[] pPattern, Color fg, Color bg) {
+ switch (isSolid(pPattern)) {
+ case 0: // 0x00
+ return bg;
+ case -1: // 0xff
+ return fg;
+ default:
+ // Fall through
+ }
+
+ WritableRaster raster = Raster.createPackedRaster(new DataBufferByte(pPattern, 8), 8, 8, 1, new Point());
+ IndexColorModel cm = new IndexColorModel(1, 2, new int[] {bg.getRGB(), fg.getRGB()}, 0, false, -1, DataBuffer.TYPE_BYTE);
+ BufferedImage img = new BufferedImage(cm, raster, false, null);
+ return new TexturePaint(img, new Rectangle(8, 8));
+ }
+
+ private static int isSolid(byte[] pPattern) {
+ int prev = pPattern[0];
+
+ for (int i = 1; i < pPattern.length; i++) {
+ if (prev != pPattern[i]) {
+ return 1;
+ }
+ }
+
+ return prev;
+ }
+
+ @Override
+ public PaintContext createContext(ColorModel pModel, Rectangle pDeviceBounds, Rectangle2D pUserBounds, AffineTransform pTransform, RenderingHints pHints) {
+// switch (isSolid(pattern)) {
+// }
+ return super.createContext(pModel, pDeviceBounds, pUserBounds, pTransform, pHints);
+ }
+
+ @Override
+ public Pattern derive(final Color foreground, final Color background) {
+ if (paint instanceof Color) {
+ // TODO: This only holds for patterns that are already foregrounds...
+ return new BitMapPattern(foreground);
+ }
+
+ return null;
+ }
}
diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICT.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICT.java
index eca3c6a4..5038f791 100755
--- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICT.java
+++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICT.java
@@ -175,4 +175,41 @@ interface PICT {
int OP_UNCOMPRESSED_QUICKTIME = 0x8201;
String APPLE_USE_RESERVED_FIELD = "Reserved for Apple use.";
+
+ /*
+ * Picture comment 'kind' codes from: http://developer.apple.com/technotes/qd/qd_10.html
+ int TextBegin = 150;
+ int TextEnd = 151;
+ int StringBegin = 152;
+ int StringEnd = 153;
+ int TextCenter = 154;
+ int LineLayoutOff = 155;
+ int LineLayoutOn = 156;
+ int ClientLineLayout = 157;
+ int PolyBegin = 160;
+ int PolyEnd = 161;
+ int PolyIgnore = 163;
+ int PolySmooth = 164;
+ int PolyClose = 165;
+ int DashedLine = 180;
+ int DashedStop = 181;
+ int SetLineWidth = 182;
+ int PostScriptBegin = 190;
+ int PostScriptEnd = 191;
+ int PostScriptHandle = 192;
+ int PostScriptFile = 193;
+ int TextIsPostScript = 194;
+ int ResourcePS = 195;
+ int PSBeginNoSave = 196;
+ int SetGrayLevel = 197;
+ int RotateBegin = 200;
+ int RotateEnd = 201;
+ int RotateCenter = 202;
+ int FormsPrinting = 210;
+ int EndFormsPrinting = 211;
+ int ICC_Profile = 224;
+ int Photoshop_Data = 498;
+ int BitMapThinningOff = 1000;
+ int BitMapThinningOn = 1001;
+ */
}
diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReader.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReader.java
index 2cc202d6..c954ff47 100644
--- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReader.java
+++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReader.java
@@ -64,7 +64,6 @@ import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import com.twelvemonkeys.io.enc.Decoder;
import com.twelvemonkeys.io.enc.DecoderStream;
-import com.twelvemonkeys.io.enc.PackBits16Decoder;
import com.twelvemonkeys.io.enc.PackBitsDecoder;
import javax.imageio.*;
@@ -100,23 +99,21 @@ import java.util.List;
* - Or methods like frameRect(pen, penmode, penwidth, rect), frameOval(pen, penmode, penwidth, rect), etc?
* - Or methods like frameShape(pen, penmode, penwidth, shape), paintShape(pen, penmode, shape) etc??
* QuickDrawContext that wraps an AWT Grpahics, and with methods macthing opcodes, seems like the best fit ATM
- * @todo Remove null-checks for Graphics, as null-graphics makes no sense.
* @todo Some MAJOR clean up
- * @todo Object orientation of different opcodes?
* @todo As we now have Graphics2D with more options, support more of the format?
- * @todo Support for some other compression (packType 3) that seems to be common...
*/
public class PICTImageReader extends ImageReaderBase {
- static boolean DEBUG = false;
+ final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.pict.debug"));
- // Private fields
private QuickDrawContext context;
private Rectangle frame;
+ // TODO: Do we need this?
private int version;
// Variables for storing draw status
+ // TODO: Get rid of these, or move to context
private Point penPosition = new Point(0, 0);
private Rectangle lastRectangle = new Rectangle(0, 0);
@@ -180,7 +177,7 @@ public class PICTImageReader extends ImageReaderBase {
pStream.seek(0l);
// Skip first 512 bytes
- skipNullHeader(pStream);
+ PICTImageReaderSpi.skipNullHeader(pStream);
readPICTHeader0(pStream);
}
}
@@ -326,12 +323,6 @@ public class PICTImageReader extends ImageReaderBase {
pStream.flushBefore(imageStartStreamPos);
}
- static void skipNullHeader(final ImageInputStream pStream) throws IOException {
- // NOTE: Only skip if FILE FORMAT, not needed for Mac OS DnD
- // Spec says "platofrm dependent", may not be all nulls..
- pStream.skipBytes(PICT.PICT_NULL_HEADER_SIZE);
- }
-
/**
* Reads the PICT stream.
* The contents of the stream will be drawn onto the supplied graphics
@@ -369,12 +360,9 @@ public class PICTImageReader extends ImageReaderBase {
int opCode, dh, dv, dataLength;
byte[] colorBuffer = new byte[3 * PICT.COLOR_COMP_SIZE];
-
Pattern fill = QuickDraw.BLACK;
Pattern bg;
Pattern pen;
- Paint foreground;
- Paint background;
Color hilight = Color.RED;
Point origin, dh_dv;
@@ -441,39 +429,27 @@ public class PICTImageReader extends ImageReaderBase {
}
break;
- case PICT.OP_TX_FONT:// DIFFICULT TO KNOW THE FONT???
+ case PICT.OP_TX_FONT:
// Get the data
- pStream.readFully(new byte[2], 0, 2);
+ byte[] fontData = new byte[2];
+ pStream.readFully(fontData, 0, 2);
// TODO: Font family id, 0 - System font, 1 - Application font.
// But how can we get these mappings?
if (DEBUG) {
- System.out.println("txFont");
+ System.out.println("txFont: " + Arrays.toString(fontData));
}
break;
- case PICT.OP_TX_FACE:// SEE IF IT IS TO BE IMPLEMENTED FOR NOW?
+ case PICT.OP_TX_FACE:
// Get the data
- byte txFace = pStream.readByte();
-
- //// Construct text face mask
-// currentFont = mGraphics.getFont();
- //int awt_face_mask = 0;
- //if ((txFace & (byte) QuickDraw.TX_BOLD_MASK) > 0) {
- // awt_face_mask |= Font.BOLD;
- //}
- //if ((txFace & (byte) QuickDraw.TX_ITALIC_MASK) > 0) {
- // awt_face_mask |= Font.ITALIC;
- //}
- //
- //// Set the font
- //mGraphics.setFont(new Font(currentFont.getName(), awt_face_mask, currentFont.getSize()));
-
+ int txFace = pStream.readUnsignedByte();
+ context.setTextFace(txFace);
if (DEBUG) {
System.out.println("txFace: " + txFace);
}
break;
- case PICT.OP_TX_MODE:// SEE IF IT IS TO BE IMPLEMENTED FOR NOW?
+ case PICT.OP_TX_MODE:
// Get the data
byte[] mode_buf = new byte[2];
pStream.readFully(mode_buf, 0, mode_buf.length);
@@ -494,25 +470,25 @@ public class PICTImageReader extends ImageReaderBase {
// Get the two words
// NOTE: This is out of order, compared to other Points
Dimension pnsize = new Dimension(pStream.readUnsignedShort(), pStream.readUnsignedShort());
- context.setPenSize(pnsize);
if (DEBUG) {
System.out.println("pnsize: " + pnsize);
}
+ context.setPenSize(pnsize);
+
break;
- case PICT.OP_PN_MODE:// TRY EMULATING WITH SETXORMODE ETC
+ case PICT.OP_PN_MODE:
// Get the data
int mode = pStream.readUnsignedShort();
if (DEBUG) {
System.out.println("pnMode: " + mode);
}
-
context.setPenMode(mode);
break;
case PICT.OP_PN_PAT:
- context.setPenPattern(PICTUtil.readPattern(pStream));
+ context.setPenPattern(PICTUtil.readPattern(pStream, context.getForeground(), context.getBackground()));
if (DEBUG) {
System.out.println("pnPat");
}
@@ -546,9 +522,6 @@ public class PICTImageReader extends ImageReaderBase {
y = getYPtCoord(pStream.readUnsignedShort());
x = getXPtCoord(pStream.readUnsignedShort());
origin = new Point(x, y);
- //if (mGraphics != null) {
- // mGraphics.translate(origin.x, origin.y);
- //}
if (DEBUG) {
System.out.println("Origin: " + origin);
}
@@ -557,10 +530,6 @@ public class PICTImageReader extends ImageReaderBase {
case PICT.OP_TX_SIZE:// OK
// Get the text size
int tx_size = getYPtCoord(pStream.readUnsignedShort());
- //if (mGraphics != null) {
- // currentFont = mGraphics.getFont();
- // mGraphics.setFont(new Font(currentFont.getName(), currentFont.getStyle(), tx_size));
- //}
context.setTextSize(tx_size);
if (DEBUG) {
System.out.println("txSize: " + tx_size);
@@ -604,14 +573,23 @@ public class PICTImageReader extends ImageReaderBase {
case 0x0012: // BkPixPat
bg = PICTUtil.readColorPattern(pStream);
context.setBackgroundPattern(bg);
+ if (DEBUG) {
+ System.out.println("BkPixPat");
+ }
break;
case 0x0013: // PnPixPat
pen = PICTUtil.readColorPattern(pStream);
- context.setBackgroundPattern(pen);
+ context.setPenPattern(pen);
+ if (DEBUG) {
+ System.out.println("PnPixPat");
+ }
break;
case 0x0014: // FillPixPat
fill = PICTUtil.readColorPattern(pStream);
- context.setBackgroundPattern(fill);
+ context.setFillPattern(fill);
+ if (DEBUG) {
+ System.out.println("FillPixPat");
+ }
break;
case PICT.OP_PN_LOC_H_FRAC:// TO BE DONE???
@@ -633,23 +611,22 @@ public class PICTImageReader extends ImageReaderBase {
case PICT.OP_RGB_FG_COL:// OK
// Get the color
pStream.readFully(colorBuffer, 0, colorBuffer.length);
- foreground = new Color((colorBuffer[0] & 0xFF), (colorBuffer[2] & 0xFF), (colorBuffer[4] & 0xFF));
- //if (mGraphics != null) {
- // mGraphics.setColor(foreground);
- //}
+ Color foreground = new Color((colorBuffer[0] & 0xFF), (colorBuffer[2] & 0xFF), (colorBuffer[4] & 0xFF));
if (DEBUG) {
System.out.println("rgbFgColor: " + foreground);
}
+ context.setForeground(foreground);
break;
case PICT.OP_RGB_BK_COL:// OK
// Get the color
pStream.readFully(colorBuffer, 0, colorBuffer.length);
- // TODO: The color might be 16 bit per component..
- background = new Color((colorBuffer[0] & 0xFF), (colorBuffer[2] & 0xFF), (colorBuffer[4] & 0xFF));
+ // The color might be 16 bit per component..
+ Color background = new Color(colorBuffer[0] & 0xFF, colorBuffer[2] & 0xFF, colorBuffer[4] & 0xFF);
if (DEBUG) {
System.out.println("rgbBgColor: " + background);
}
+ context.setBackground(background);
break;
case PICT.OP_HILITE_MODE:
@@ -725,11 +702,12 @@ public class PICTImageReader extends ImageReaderBase {
x = getXPtCoord(pStream.readUnsignedShort());
origin = new Point(x, y);
- y = getYPtCoord(pStream.readByte());
x = getXPtCoord(pStream.readByte());
+ y = getYPtCoord(pStream.readByte());
dh_dv = new Point(x, y);
// Move pen to new position, draw line if we have a graphics
+ context.moveTo(origin);
penPosition.setLocation(origin.x + dh_dv.x, origin.y + dh_dv.y);
context.lineTo(penPosition);
@@ -740,8 +718,8 @@ public class PICTImageReader extends ImageReaderBase {
case PICT.OP_SHORT_LINE_FROM:// OK
// Get dh, dv
- y = getYPtCoord(pStream.readByte());
x = getXPtCoord(pStream.readByte());
+ y = getYPtCoord(pStream.readByte());
// Draw line
context.line(x, y);
@@ -788,10 +766,6 @@ public class PICTImageReader extends ImageReaderBase {
penPosition.translate(dh, 0);
context.moveTo(penPosition);
text = PICTUtil.readPascalString(pStream);
- // TODO
-// if (mGraphics != null) {
-// mGraphics.drawString(text, penPosition.x, penPosition.y);
-// }
context.drawString(text);
if (DEBUG) {
System.out.println("DHText dh: " + dh + ", text:" + text);
@@ -804,10 +778,6 @@ public class PICTImageReader extends ImageReaderBase {
penPosition.translate(0, dv);
context.moveTo(penPosition);
text = PICTUtil.readPascalString(pStream);
- // TODO
- //if (mGraphics != null) {
- // mGraphics.drawString(text, penPosition.x, penPosition.y);
- //}
context.drawString(text);
if (DEBUG) {
System.out.println("DVText dv: " + dv + ", text:" + text);
@@ -821,10 +791,6 @@ public class PICTImageReader extends ImageReaderBase {
penPosition.translate(x, y);
context.moveTo(penPosition);
text = PICTUtil.readPascalString(pStream);
- // TODO
- //if (mGraphics != null) {
- // mGraphics.drawString(text, penPosition.x, penPosition.y);
- //}
context.drawString(text);
if (DEBUG) {
System.out.println("DHDVText penPosition: " + penPosition + ", text:" + text);
@@ -837,25 +803,20 @@ public class PICTImageReader extends ImageReaderBase {
pStream.readShort();
// Get old font ID, ignored
-// pStream.readInt();
pStream.readUnsignedShort();
// Get font name and set the new font if we have one
- text = PICTUtil.readPascalString(pStream);
- // TODO
- //if (mGraphics != null) {
- // mGraphics.setFont(Font.decode(text)
- // .deriveFont(currentFont.getStyle(), currentFont.getSize()));
- //}
- context.drawString(text);
+ String fontName = PICTUtil.readPascalString(pStream);
+ context.setTextFont(fontName);
if (DEBUG) {
- System.out.println("fontName: \"" + text +"\"");
+ System.out.println("fontName: \"" + fontName +"\"");
}
break;
- case PICT.OP_LINE_JUSTIFY:// TO BE DONE???
+ case PICT.OP_LINE_JUSTIFY:// TODO
// Get data
- pStream.readFully(new byte[10], 0, 10);
+ byte[] lineJustifyData = new byte[10];
+ pStream.readFully(lineJustifyData, 0, lineJustifyData.length);
if (DEBUG) {
System.out.println("opLineJustify");
}
@@ -863,9 +824,10 @@ public class PICTImageReader extends ImageReaderBase {
case PICT.OP_GLYPH_STATE:// TODO: NOT SUPPORTED IN AWT GRAPHICS YET?
// Get data
- pStream.readFully(new byte[6], 0, 6);
+ byte[] glyphState = new byte[6];
+ pStream.readFully(glyphState, 0, glyphState.length);
if (DEBUG) {
- System.out.println("glyphState");
+ System.out.println("glyphState: " + Arrays.toString(glyphState));
}
break;
@@ -1319,6 +1281,14 @@ public class PICTImageReader extends ImageReaderBase {
// Polygon treatments finished
break;
+ case 0x7d:
+ case 0x7e:
+ case 0x7f:
+ if (DEBUG) {
+ System.out.println(String.format("%s: 0x%04x", PICT.APPLE_USE_RESERVED_FIELD, opCode));
+ }
+ break;
+
case 0x75:
case 0x76:
case 0x77:
@@ -1440,14 +1410,14 @@ public class PICTImageReader extends ImageReaderBase {
*/
int rowBytesRaw = pStream.readUnsignedShort();
- int rowBytes = rowBytesRaw & 0x3FFF;
+ int rowBytes = rowBytesRaw & 0x7FFF;
// TODO: Use rowBytes to determine size of PixMap/ColorTable?
if ((rowBytesRaw & 0x8000) > 0) {
// Do stuff...
}
- // Get bounds rectangle. THIS IS NOT TO BE SCALED BY THE RESOLUTION! TODO: ?!
+ // Get bounds rectangle. THIS IS NOT TO BE SCALED BY THE RESOLUTION!
bounds = new Rectangle();
y = pStream.readUnsignedShort();
x = pStream.readUnsignedShort();
@@ -1455,8 +1425,7 @@ public class PICTImageReader extends ImageReaderBase {
y = pStream.readUnsignedShort();
x = pStream.readUnsignedShort();
- bounds.setSize(x - bounds.x,
- y - bounds.y);
+ bounds.setSize(x - bounds.x, y - bounds.y);
Rectangle srcRect = new Rectangle();
readRectangle(pStream, srcRect);
@@ -1465,7 +1434,6 @@ public class PICTImageReader extends ImageReaderBase {
readRectangle(pStream, dstRect);
mode = pStream.readUnsignedShort();
- context.setPenMode(mode); // TODO: Or parameter?
if (DEBUG) {
System.out.print("bitsRect, rowBytes: " + rowBytes);
@@ -1496,13 +1464,6 @@ public class PICTImageReader extends ImageReaderBase {
Rectangle rect = new Rectangle(srcRect);
rect.translate(-bounds.x, -bounds.y);
context.copyBits(image, rect, dstRect, mode, null);
- //mGraphics.drawImage(image,
- // dstRect.x, dstRect.y,
- // dstRect.x + dstRect.width, dstRect.y + dstRect.height,
- // srcRect.x - bounds.x, srcRect.y - bounds.y,
- // srcRect.x - bounds.x + srcRect.width, srcRect.y - bounds.y + srcRect.height,
- // null);
- //
break;
case PICT.OP_BITS_RGN:
@@ -1518,7 +1479,7 @@ public class PICTImageReader extends ImageReaderBase {
pixData: PixData;
*/
if (DEBUG) {
- System.out.println("bitsRgn");
+ System.out.println("bitsRgn - TODO");
}
break;
@@ -1531,15 +1492,12 @@ public class PICTImageReader extends ImageReaderBase {
dataLength = pStream.readUnsignedShort();
pStream.readFully(new byte[dataLength], 0, dataLength);
if (DEBUG) {
- System.out.println(String.format("%s: 0x%04x", PICT.APPLE_USE_RESERVED_FIELD, opCode));
+ System.out.println(String.format("%s: 0x%04x - length: %d", PICT.APPLE_USE_RESERVED_FIELD, opCode, dataLength));
}
break;
case PICT.OP_PACK_BITS_RECT:
- readOpPackBitsRect(pStream, bounds, pixmapCount++);
- if (DEBUG) {
- System.out.println("packBitsRect - TODO");
- }
+ readOpPackBitsRect(pStream, pixmapCount++);
break;
case PICT.OP_PACK_BITS_RGN:
@@ -1551,7 +1509,7 @@ public class PICTImageReader extends ImageReaderBase {
break;
case PICT.OP_DIRECT_BITS_RECT:
- readOpDirectBitsRect(pStream, bounds, pixmapCount++);
+ readOpDirectBitsRect(pStream, pixmapCount++);
break;
case PICT.OP_DIRECT_BITS_RGN:
@@ -1575,17 +1533,21 @@ public class PICTImageReader extends ImageReaderBase {
break;
case PICT.OP_SHORT_COMMENT:// NOTHING TO DO, JUST JUMP OVER
- pStream.readFully(new byte[2], 0, 2);
+ byte[] shortComment = new byte[2];
+ pStream.readFully(shortComment, 0, 2);
if (DEBUG) {
- System.out.println("Short comment");
+ System.out.println("Short comment: " + Arrays.toString(shortComment));
}
break;
case PICT.OP_LONG_COMMENT:// NOTHING TO DO, JUST JUMP OVER
- readLongComment(pStream);
- if (DEBUG) {
- System.out.println("Long comment");
- }
+ /*byte[] longComment =*/ readLongComment(pStream);
+ // TODO: Don't just skip...
+ // https://developer.apple.com/legacy/library/documentation/mac/pdf/Imaging_With_QuickDraw/Appendix_B.pdf
+ // Long comments can be used for PhotoShop IRBs (kind 498) or ICC profiles (224) and other meta data...
+// if (DEBUG) {
+// System.out.println("Long comment: " + Arrays.toString(longComment));
+// }
break;
case PICT.OP_END_OF_PICTURE:// OK
@@ -1654,6 +1616,8 @@ public class PICTImageReader extends ImageReaderBase {
pStream.readFully(new byte[dataLength], 0, dataLength);
}
else {
+ // TODO: We could issue a warning and return instead? In any case, can't continue, as we don't know the length of the opcode...
+// return;
throw new IIOException(String.format("Found unknown opcode: 0x%04x", opCode));
}
@@ -1737,7 +1701,7 @@ public class PICTImageReader extends ImageReaderBase {
pStream.seek(pos + dataLength); // Might be word-align mismatch here
- // Skip "QuickTime? and a ... decompressor required" text
+ // Skip "QuickTimeâ„¢ and a ... decompressor required" text
// TODO: Verify that this is correct. It works with all my test data, but the algorithm is
// reverse-engineered by looking at the input data and not from any spec I've seen...
int penSizeMagic = pStream.readInt();
@@ -1768,23 +1732,17 @@ public class PICTImageReader extends ImageReaderBase {
*/
- private void readOpPackBitsRect(ImageInputStream pStream, Rectangle pBounds, int pPixmapCount) throws IOException {
- if (DEBUG) {
- System.out.println("packBitsRect");
- }
-
- // Skip PixMap pointer (always 0x000000FF);
-// pStream.skipBytes(4);
-// int pixmapPointer = pStream.readInt();
-// System.out.println(String.format("%08d: 0x%08x", pStream.getStreamPosition(), pixmapPointer));
-
+ private void readOpPackBitsRect(final ImageInputStream pStream, final int pPixmapCount) throws IOException {
// Get rowBytes
int rowBytesRaw = pStream.readUnsignedShort();
// System.out.println(String.format("%08d: 0x%04x", pStream.getStreamPosition(), rowBytesRaw));
- int rowBytes = rowBytesRaw & 0x3FFF;
+ // TODO: This way to determine pixmap vs bitmap is for version 2 only!
+ int rowBytes = rowBytesRaw & 0x7FFF;
+ boolean isPixMap = (rowBytesRaw & 0x8000) > 0;
+
if (DEBUG) {
System.out.print("packBitsRect, rowBytes: " + rowBytes);
- if ((rowBytesRaw & 0x8000) > 0) {
+ if (isPixMap) {
System.out.print(", it is a PixMap");
}
else {
@@ -1793,98 +1751,114 @@ public class PICTImageReader extends ImageReaderBase {
}
// Get bounds rectangle. THIS IS NOT TO BE SCALED BY THE RESOLUTION!
+ // TODO: ...or then again...? :-)
+ Rectangle bounds = new Rectangle();
int y = pStream.readUnsignedShort();
int x = pStream.readUnsignedShort();
- pBounds.setLocation(x, y);
+ bounds.setLocation(x, y);
y = pStream.readUnsignedShort();
x = pStream.readUnsignedShort();
- pBounds.setSize(x - pBounds.x, y - pBounds.y);
+ bounds.setSize(x - bounds.x, y - bounds.y);
if (DEBUG) {
- System.out.print(", bounds: " + pBounds);
+ System.out.print(", bounds: " + bounds);
}
- // Get PixMap record version number
- int pmVersion = pStream.readUnsignedShort() & 0xFFFF;
- if (DEBUG) {
- System.out.print(", pmVersion: " + pmVersion);
- }
-
- // Get packing format
- int packType = pStream.readUnsignedShort() & 0xFFFF;
- if (DEBUG) {
- System.out.print(", packType: " + packType);
- }
-
- // Get size of packed data (not used for v2)
- int packSize = pStream.readInt();
- if (DEBUG) {
- System.out.println(", packSize: " + packSize);
- }
-
- // Get resolution info
- double hRes = PICTUtil.readFixedPoint(pStream);
- double vRes = PICTUtil.readFixedPoint(pStream);
- if (DEBUG) {
- System.out.print("hRes: " + hRes + ", vRes: " + vRes);
- }
-
- // Get pixel type
- int pixelType = pStream.readUnsignedShort();
- if (DEBUG) {
- if (pixelType == 0) {
- System.out.print(", indexed pixels");
- }
- else {
- System.out.print(", RGBDirect");
- }
- }
-
- // Get pixel size
- int pixelSize = pStream.readUnsignedShort();
- if (DEBUG) {
- System.out.print(", pixelSize:" + pixelSize);
- }
-
- // Get pixel component count
- int cmpCount = pStream.readUnsignedShort();
- if (DEBUG) {
- System.out.print(", cmpCount:" + cmpCount);
- }
-
- // Get pixel component size
- int cmpSize = pStream.readUnsignedShort();
- if (DEBUG) {
- System.out.print(", cmpSize:" + cmpSize);
- }
-
- // planeBytes (ignored)
- int planeBytes = pStream.readInt();
- if (DEBUG) {
- System.out.print(", planeBytes:" + planeBytes);
- }
-
- // Handle to ColorTable record, there should be none for direct
- // bits so this should be 0, just skip
- int clutId = pStream.readInt();
- if (DEBUG) {
- System.out.println(", clutId:" + clutId);
- }
-
- // Reserved
- pStream.readInt();
-
- // Color table
ColorModel colorModel;
- if (pixelType == 0) {
+ int cmpSize;
+
+ if (isPixMap) {
+ // Get PixMap record version number
+ int pmVersion = pStream.readUnsignedShort();
+ if (DEBUG) {
+ System.out.print(", pmVersion: " + pmVersion);
+ }
+
+ // Get packing format
+ int packType = pStream.readUnsignedShort();
+ if (DEBUG) {
+ System.out.print(", packType: " + packType);
+ }
+
+ // Get size of packed data (not used for v2)
+ int packSize = pStream.readInt(); // TODO: Probably not int for BitMap (value seems too high)?
+ if (DEBUG) {
+ System.out.println(", packSize: " + packSize);
+ }
+
+ // Get resolution info
+ double hRes = PICTUtil.readFixedPoint(pStream);
+ double vRes = PICTUtil.readFixedPoint(pStream);
+ if (DEBUG) {
+ System.out.print("hRes: " + hRes + ", vRes: " + vRes);
+ }
+
+ // Get pixel type
+ int pixelType = pStream.readUnsignedShort();
+ if (DEBUG) {
+ if (pixelType == 0) {
+ System.out.print(", indexed pixels");
+ }
+ else {
+ System.out.print(", RGBDirect");
+ }
+ }
+
+ // Get pixel size
+ int pixelSize = pStream.readUnsignedShort();
+ if (DEBUG) {
+ System.out.print(", pixelSize:" + pixelSize);
+ }
+
+ // Get pixel component count
+ int cmpCount = pStream.readUnsignedShort();
+ if (DEBUG) {
+ System.out.print(", cmpCount:" + cmpCount);
+ }
+
+ // Get pixel component size
+ cmpSize = pStream.readUnsignedShort();
+ if (DEBUG) {
+ System.out.print(", cmpSize:" + cmpSize);
+ }
+
+ // planeBytes (ignored)
+ int planeBytes = pStream.readInt();
+ if (DEBUG) {
+ System.out.print(", planeBytes:" + planeBytes);
+ }
+
+ // Handle to ColorTable record
+ int clutId = pStream.readInt();
+ if (DEBUG) {
+ System.out.println(", clutId:" + clutId);
+ }
+
+ // Reserved
+ pStream.readInt();
+
+ // TODO: Seems to be packType 0 all the time?
+ // packType = 0 means default....
+
+ if (packType != 0) {
+ throw new IIOException("Unknown pack type: " + packType);
+ }
+ if (pixelType != 0) {
+ throw new IIOException("Unsupported pixel type: " + pixelType);
+ }
+
+ // Color table
colorModel = PICTUtil.readColorTable(pStream, pixelSize);
}
else {
- throw new IIOException("Unsupported pixel type: " + pixelType);
+ // Old style BitMap record
+ cmpSize = 1;
+ colorModel = QuickDraw.MONOCHROME;
}
// Get source rectangle. We DO NOT scale the coordinates by the
// resolution info, since we are in pixmap coordinates here
+ // TODO: readReactangleNonScaled()
Rectangle srcRect = new Rectangle();
y = pStream.readUnsignedShort();
x = pStream.readUnsignedShort();
@@ -1910,157 +1884,58 @@ public class PICTImageReader extends ImageReaderBase {
// Get transfer mode
int transferMode = pStream.readUnsignedShort();
if (DEBUG) {
- System.out.print(", mode: " + transferMode);
+ System.out.println(", mode: " + transferMode);
}
// Set up pixel buffer for the RGB values
-
- // TODO: Seems to be packType 0 all the time?
- // packType = 0 means default....
-
-
- // Read in the RGB arrays
- byte[] dstBytes;
- /*
- if (packType == 1 || rowBytes < 8) {
- // TODO: Verify this...
- dstBytes = new byte[rowBytes];
- }
- else if (packType == 2) {
- // TODO: Verify this...
- dstBytes = new byte[rowBytes * 3 / 4];
- }
- else if (packType == 3) {
- dstBytes = new byte[2 * pBounds.width];
- }
- else if (packType == 4) {
- dstBytes = new byte[cmpCount * pBounds.width];
- }
- else {
- throw new IIOException("Unknown pack type: " + packType);
- }
- */
- if (packType == 0) {
- dstBytes = new byte[cmpCount * pBounds.width];
- }
- else {
- throw new IIOException("Unknown pack type: " + packType);
- }
-
-// int[] pixArray = new int[pBounds.height * pBounds.width];
- byte[] pixArray = new byte[pBounds.height * pBounds.width];
+ byte[] pixArray = new byte[srcRect.height * rowBytes];
int pixBufOffset = 0;
- int packedBytesCount;
- for (int scanline = 0; scanline < pBounds.height; scanline++) {
- // Get byteCount of the scanline
- if (rowBytes > 250) {
- packedBytesCount = pStream.readUnsignedShort();
- }
- else {
- packedBytesCount = pStream.readUnsignedByte();
- }
- if (DEBUG) {
- System.out.println();
- System.out.print("Line " + scanline + ", byteCount: " + packedBytesCount);
- System.out.print(" dstBytes: " + dstBytes.length);
- }
-
+ // Read in the RGB arrays
+ for (int scanline = 0; scanline < srcRect.height; scanline++) {
// Read in the scanline
- /*if (packType > 2) {
- // Unpack them all*/
- Decoder decoder;/*
- if (packType == 3) {
- decoder = new PackBits16Decoder();
- }
- else {*/
- decoder = new PackBitsDecoder();
- /*}*/
- DataInput unPackBits = new DataInputStream(new DecoderStream(IIOUtil.createStreamAdapter(pStream, packedBytesCount), decoder));
-// unPackBits.readFully(dstBytes);
- unPackBits.readFully(pixArray, pixBufOffset, pBounds.width);
- /*}
- else {
- imageInput.readFully(dstBytes);
- }*/
+ if (rowBytes > 8) {
+ // Get byteCount of the scanline
+ int packedBytesCount = rowBytes > 250 ? pStream.readUnsignedShort() : pStream.readUnsignedByte();
- // TODO: Use TYPE_USHORT_555_RGB for 16 bit
- /*
- if (packType == 3) {
- for (int i = 0; i < pBounds.width; i++) {
- // Set alpha values to all opaque
- pixArray[pixBufOffset + i] = 0xFF000000;
-
- // Get red values
- int red = 8 * ((dstBytes[2 * i] & 0x7C) >> 2);
- pixArray[pixBufOffset + i] |= red << 16;
- // Get green values
- int green = 8 * (((dstBytes[2 * i] & 0x07) << 3) + ((dstBytes[2 * i + 1] & 0xE0) >> 5));
- pixArray[pixBufOffset + i] |= green << 8;
- // Get blue values
- int blue = 8 * ((dstBytes[2 * i + 1] & 0x1F));
- pixArray[pixBufOffset + i] |= blue;
- }
+ // Unpack them all
+ Decoder decoder = new PackBitsDecoder();
+ DataInput unPackBits = new DataInputStream(new DecoderStream(IIOUtil.createStreamAdapter(pStream, packedBytesCount), decoder));
+ unPackBits.readFully(pixArray, pixBufOffset, rowBytes);
}
else {
- if (cmpCount == 3) {
- for (int i = 0; i < pBounds.width; i++) {
- // Set alpha values to all opaque
- pixArray[pixBufOffset + i] = 0xFF000000;
- // Get red values
- pixArray[pixBufOffset + i] |= (dstBytes[i] & 0xFF) << 16;
- // Get green values
- pixArray[pixBufOffset + i] |= (dstBytes[pBounds.width + i] & 0xFF) << 8;
- // Get blue values
- pixArray[pixBufOffset + i] |= (dstBytes[2 * pBounds.width + i] & 0xFF);
- }
- }
- else {
- for (int i = 0; i < pBounds.width; i++) {
-// // Get alpha values
-// pixArray[pixBufOffset + i] = (dstBytes[i] & 0xFF) << 24;
-// // Get red values
-// pixArray[pixBufOffset + i] |= (dstBytes[pBounds.width + i] & 0xFF) << 16;
-// // Get green values
-// pixArray[pixBufOffset + i] |= (dstBytes[2 * pBounds.width + i] & 0xFF) << 8;
-// // Get blue values
-// pixArray[pixBufOffset + i] |= (dstBytes[3 * pBounds.width + i] & 0xFF);
-
- // TODO: Fake it for now... Should ideally just use byte array and use the ICM
-// pixArray[pixBufOffset + i] = 0xFF << 24;
-// pixArray[pixBufOffset + i] |= colorModel.getRed(dstBytes[i] & 0xFF) << 16;
-// pixArray[pixBufOffset + i] |= colorModel.getGreen(dstBytes[i] & 0xFF) << 8;
-// pixArray[pixBufOffset + i] |= colorModel.getBlue(dstBytes[i] & 0xFF);
-
- pixArray[pixBufOffset + i] = dstBytes[i];
- }
-// }
-// }
-*/
+ // Uncompressed
+ imageInput.readFully(pixArray, pixBufOffset, rowBytes);
+ }
// Increment pixel buffer offset
- pixBufOffset += pBounds.width;
+ pixBufOffset += rowBytes;
////////////////////////////////////////////////////
// TODO: This works for single image PICTs only...
// However, this is the most common case. Ok for now
- processImageProgress(scanline * 100 / pBounds.height);
+ processImageProgress(scanline * 100 / srcRect.height);
if (abortRequested()) {
processReadAborted();
// Skip rest of image data
- for (int skip = scanline + 1; skip < pBounds.height; skip++) {
+ for (int skip = scanline + 1; skip < srcRect.height; skip++) {
// Get byteCount of the scanline
- if (rowBytes > 250) {
+ int packedBytesCount;
+
+ if (rowBytes <= 8) {
+ packedBytesCount = rowBytes;
+ }
+ else if (rowBytes > 250) {
packedBytesCount = pStream.readUnsignedShort();
}
else {
packedBytesCount = pStream.readUnsignedByte();
}
+
pStream.readFully(new byte[packedBytesCount], 0, packedBytesCount);
if (DEBUG) {
- System.out.println();
System.out.print("Skip " + skip + ", byteCount: " + packedBytesCount);
}
}
@@ -2074,11 +1949,8 @@ public class PICTImageReader extends ImageReaderBase {
// "pPixmapCount" will never be greater than the size of the vector
if (images.size() <= pPixmapCount) {
// Create BufferedImage and add buffer it for multiple reads
-// DirectColorModel cm = (DirectColorModel) ColorModel.getRGBdefault();
-// DataBuffer db = new DataBufferInt(pixArray, pixArray.length);
-// WritableRaster raster = Raster.createPackedRaster(db, pBounds.width, pBounds.height, pBounds.width, cm.getMasks(), null);
DataBuffer db = new DataBufferByte(pixArray, pixArray.length);
- WritableRaster raster = Raster.createPackedRaster(db, pBounds.width, pBounds.height, cmpSize, null); // TODO: last param should ideally be srcRect.getLocation()
+ WritableRaster raster = Raster.createPackedRaster(db, (rowBytes * 8) / cmpSize, srcRect.height, cmpSize, null); // TODO: last param should ideally be srcRect.getLocation()
BufferedImage img = new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null);
images.add(img);
@@ -2102,24 +1974,23 @@ public class PICTImageReader extends ImageReaderBase {
* Reads the data following a {@code directBitsRect} opcode.
*
* @param pStream the stream to read from
- * @param pBounds the bounding rectangle
* @param pPixmapCount the index of the bitmap in the PICT file, used for
* cahcing.
*
* @throws javax.imageio.IIOException if the data can not be read.
- * @throws IOException if an I/O error occurs while reading the image.
+ * @throws java.io.IOException if an I/O error occurs while reading the image.
*/
- private void readOpDirectBitsRect(ImageInputStream pStream, Rectangle pBounds, int pPixmapCount) throws IOException {
+ private void readOpDirectBitsRect(final ImageInputStream pStream, final int pPixmapCount) throws IOException {
if (DEBUG) {
System.out.println("directBitsRect");
}
// Skip PixMap pointer (always 0x000000FF);
- pStream.skipBytes(4);
+ pStream.readInt();
// Get rowBytes
int rowBytesRaw = pStream.readUnsignedShort();
- int rowBytes = rowBytesRaw & 0x3FFF;
+ int rowBytes = rowBytesRaw & 0x7FFF;
if (DEBUG) {
System.out.print("directBitsRect, rowBytes: " + rowBytes);
if ((rowBytesRaw & 0x8000) > 0) {
@@ -2131,25 +2002,27 @@ public class PICTImageReader extends ImageReaderBase {
}
// Get bounds rectangle. THIS IS NOT TO BE SCALED BY THE RESOLUTION!
+ // TODO: ...or then again...? :-)
+ Rectangle bounds = new Rectangle();
int y = pStream.readUnsignedShort();
int x = pStream.readUnsignedShort();
- pBounds.setLocation(x, y);
+ bounds.setLocation(x, y);
y = pStream.readUnsignedShort();
x = pStream.readUnsignedShort();
- pBounds.setSize(x - pBounds.x, y - pBounds.y);
+ bounds.setSize(x - bounds.x, y - bounds.y);
if (DEBUG) {
- System.out.print(", bounds: " + pBounds);
+ System.out.print(", bounds: " + bounds);
}
// Get PixMap record version number
- int pmVersion = pStream.readUnsignedShort() & 0xFFFF;
+ int pmVersion = pStream.readUnsignedShort();
if (DEBUG) {
System.out.print(", pmVersion: " + pmVersion);
}
// Get packing format
- int packType = pStream.readUnsignedShort() & 0xFFFF;
+ int packType = pStream.readUnsignedShort();
if (DEBUG) {
System.out.print(", packType: " + packType);
}
@@ -2221,7 +2094,6 @@ public class PICTImageReader extends ImageReaderBase {
System.out.print("opDirectBitsRect, srcRect:" + srcRect);
}
- // TODO: FixMe...
// Get destination rectangle. We DO scale the coordinates according to
// the image resolution, since we are working in display coordinates
Rectangle dstRect = new Rectangle();
@@ -2240,19 +2112,11 @@ public class PICTImageReader extends ImageReaderBase {
// Read in the RGB arrays
byte[] dstBytes;
- if (packType == 1 || rowBytes < 8) {
- // TODO: Verify this...
+ if (packType == 1 || packType == 2 || packType == 3) {
dstBytes = new byte[rowBytes];
}
- else if (packType == 2) {
- // TODO: Verify this...
- dstBytes = new byte[rowBytes * 3 / 4];
- }
- else if (packType == 3) {
- dstBytes = new byte[2 * pBounds.width];
- }
else if (packType == 4) {
- dstBytes = new byte[cmpCount * pBounds.width];
+ dstBytes = new byte[cmpCount * rowBytes / 4];
}
else {
throw new IIOException("Unknown pack type: " + packType);
@@ -2261,40 +2125,35 @@ public class PICTImageReader extends ImageReaderBase {
int[] pixArray = null;
short[] shortArray = null;
if (packType == 3) {
- shortArray = new short[pBounds.height * pBounds.width];
+ shortArray = new short[srcRect.height * (rowBytes + 1) / 2];
}
else {
- pixArray = new int[pBounds.height * pBounds.width];
+ pixArray = new int[srcRect.height * (rowBytes + 3) / 4];
}
int pixBufOffset = 0;
int packedBytesCount;
- for (int scanline = 0; scanline < pBounds.height; scanline++) {
- // Get byteCount of the scanline
- if (rowBytes > 250) {
- packedBytesCount = pStream.readUnsignedShort();
- }
- else {
- packedBytesCount = pStream.readUnsignedByte();
- }
- if (DEBUG) {
- System.out.println();
- System.out.print("Line " + scanline + ", byteCount: " + packedBytesCount);
- System.out.print(" dstBytes: " + dstBytes.length);
- }
-
+ for (int scanline = 0; scanline < srcRect.height; scanline++) {
// Read in the scanline
if (packType > 2) {
- // Unpack them all
- Decoder decoder;
- if (packType == 3) {
- decoder = new PackBits16Decoder();
+ // Get byteCount of the scanline
+ if (rowBytes > 250) {
+ packedBytesCount = pStream.readUnsignedShort();
}
else {
- decoder = new PackBitsDecoder();
+ packedBytesCount = pStream.readUnsignedByte();
}
- DataInput unPackBits = new DataInputStream(new DecoderStream(IIOUtil.createStreamAdapter(pStream, packedBytesCount), decoder));
+
+ if (DEBUG) {
+ System.out.print("Line " + scanline + ", byteCount: " + packedBytesCount);
+ System.out.print(" dstBytes: " + dstBytes.length);
+ System.out.println();
+ }
+
+ // Unpack them all
+ Decoder decoder = packType == 3 ? new PackBitsDecoder(2, false) : new PackBitsDecoder();
+ DataInput unPackBits = new DataInputStream(new DecoderStream(IIOUtil.createStreamAdapter(pStream, packedBytesCount), decoder, dstBytes.length));
unPackBits.readFully(dstBytes);
}
else {
@@ -2303,63 +2162,51 @@ public class PICTImageReader extends ImageReaderBase {
if (packType == 3) {
// TYPE_USHORT_555_RGB for 16 bit
- for (int i = 0; i < pBounds.width; i++) {
+ for (int i = 0; i < srcRect.width; i++) {
shortArray[pixBufOffset + i] = (short) (((0xff & dstBytes[2 * i]) << 8) | (0xff & dstBytes[2 * i + 1]));
-// // Set alpha values to all opaque
-// pixArray[pixBufOffset + i] = 0xFF000000;
-//
-// // Get red values
-// int red = 8 * ((dstBytes[2 * i] & 0x7C) >> 2);
-// pixArray[pixBufOffset + i] |= red << 16;
-// // Get green values
-// int green = 8 * (((dstBytes[2 * i] & 0x07) << 3) + ((dstBytes[2 * i + 1] & 0xE0) >> 5));
-// pixArray[pixBufOffset + i] |= green << 8;
-// // Get blue values
-// int blue = 8 * ((dstBytes[2 * i + 1] & 0x1F));
-// pixArray[pixBufOffset + i] |= blue;
}
}
else {
if (cmpCount == 3) {
// RGB
- for (int i = 0; i < pBounds.width; i++) {
+ for (int i = 0; i < srcRect.width; i++) {
// Set alpha values to all opaque
- pixArray[pixBufOffset + i] = 0xFF000000;
+ pixArray[pixBufOffset + i] = 0xFF000000
// Get red values
- pixArray[pixBufOffset + i] |= (dstBytes[i] & 0xFF) << 16;
+ | (dstBytes[/*0* bounds.width*/i] & 0xFF) << 16
// Get green values
- pixArray[pixBufOffset + i] |= (dstBytes[pBounds.width + i] & 0xFF) << 8;
+ | (dstBytes[/**/bounds.width + i] & 0xFF) << 8
// Get blue values
- pixArray[pixBufOffset + i] |= (dstBytes[2 * pBounds.width + i] & 0xFF);
+ | (dstBytes[2 * bounds.width + i] & 0xFF);
}
}
else {
// ARGB
- for (int i = 0; i < pBounds.width; i++) {
+ for (int i = 0; i < srcRect.width; i++) {
// Get alpha values
- pixArray[pixBufOffset + i] = (dstBytes[i] & 0xFF) << 24;
+ pixArray[pixBufOffset + i] = (dstBytes[/*0* bounds.width*/i] & 0xFF) << 24
// Get red values
- pixArray[pixBufOffset + i] |= (dstBytes[pBounds.width + i] & 0xFF) << 16;
+ | (dstBytes[/**/bounds.width + i] & 0xFF) << 16
// Get green values
- pixArray[pixBufOffset + i] |= (dstBytes[2 * pBounds.width + i] & 0xFF) << 8;
+ | (dstBytes[2 * bounds.width + i] & 0xFF) << 8
// Get blue values
- pixArray[pixBufOffset + i] |= (dstBytes[3 * pBounds.width + i] & 0xFF);
+ | (dstBytes[3 * bounds.width + i] & 0xFF);
}
}
}
// Increment pixel buffer offset
- pixBufOffset += pBounds.width;
+ pixBufOffset += srcRect.width;
////////////////////////////////////////////////////
// TODO: This works for single image PICTs only...
// However, this is the most common case. Ok for now
- processImageProgress(scanline * 100 / pBounds.height);
+ processImageProgress(scanline * 100 / srcRect.height);
if (abortRequested()) {
processReadAborted();
// Skip rest of image data
- for (int skip = scanline + 1; skip < pBounds.height; skip++) {
+ for (int skip = scanline + 1; skip < srcRect.height; skip++) {
// Get byteCount of the scanline
if (rowBytes > 250) {
packedBytesCount = pStream.readUnsignedShort();
@@ -2390,12 +2237,12 @@ public class PICTImageReader extends ImageReaderBase {
if (packType == 3) {
cm = new DirectColorModel(15, 0x7C00, 0x03E0, 0x001F); // See BufferedImage TYPE_USHORT_555_RGB
DataBuffer db = new DataBufferUShort(shortArray, shortArray.length);
- raster = Raster.createPackedRaster(db, pBounds.width, pBounds.height, pBounds.width, cm.getMasks(), null); // TODO: last param should ideally be srcRect.getLocation()
+ raster = Raster.createPackedRaster(db, srcRect.width, srcRect.height, srcRect.width, cm.getMasks(), null); // TODO: last param should ideally be srcRect.getLocation()
}
else {
cm = (DirectColorModel) ColorModel.getRGBdefault();
DataBuffer db = new DataBufferInt(pixArray, pixArray.length);
- raster = Raster.createPackedRaster(db, pBounds.width, pBounds.height, pBounds.width, cm.getMasks(), null); // TODO: last param should ideally be srcRect.getLocation()
+ raster = Raster.createPackedRaster(db, srcRect.width, srcRect.height, srcRect.width, cm.getMasks(), null); // TODO: last param should ideally be srcRect.getLocation()
}
BufferedImage img = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
@@ -2540,13 +2387,20 @@ public class PICTImageReader extends ImageReaderBase {
/*
* Read a long comment from the stream.
*/
- private void readLongComment(final DataInput pStream) throws IOException {
+ private byte[] readLongComment(final DataInput pStream) throws IOException {
// Comment kind and data byte count
- pStream.readShort();
+ short kind = pStream.readShort();
+ int length = pStream.readUnsignedShort();
+
+ if (DEBUG) {
+ System.err.println("Long comment: " + kind + ", " + length + " bytes");
+ }
// Get as many bytes as indicated by byte count
- int length = pStream.readUnsignedShort();
- pStream.readFully(new byte[length], 0, length);
+ byte[] bytes = new byte[length];
+ pStream.readFully(bytes, 0, length);
+
+ return bytes;
}
/*
@@ -2623,12 +2477,19 @@ public class PICTImageReader extends ImageReaderBase {
BufferedImage image = getDestination(pParam, getImageTypes(pIndex), getXPtCoord(frame.width), getYPtCoord(frame.height));
Graphics2D g = image.createGraphics();
try {
- // TODO: Might need to clear background
+ // Might need to clear background
+ g.setComposite(AlphaComposite.Src);
+ g.setColor(new Color(0x00ffffff, true)); // Transparent white
+// g.setColor(Color.WHITE);
+ g.fillRect(0, 0, image.getWidth(), image.getHeight());
+
AffineTransform instance = new AffineTransform();
+
if (pParam != null && pParam.getSourceRegion() != null) {
Rectangle rectangle = pParam.getSourceRegion();
instance.translate(-rectangle.x, -rectangle.y);
}
+
instance.scale(screenImageXRatio / subX, screenImageYRatio / subY);
g.setTransform(instance);
// try {
@@ -2667,158 +2528,37 @@ public class PICTImageReader extends ImageReaderBase {
).iterator();
}
- public static void main(String[] pArgs) throws IOException {
- ImageReader reader = new PICTImageReader(new PICTImageReaderSpi());
-
- ImageInputStream input;
- String title;
- if (pArgs.length >= 1) {
- File file = new File(pArgs[0]);
- input = ImageIO.createImageInputStream(file);
- title = file.getName();
- }
- else {
- input = ImageIO.createImageInputStream(new ByteArrayInputStream(DATA_V1_OVERPAINTED_ARC));
- title = "PICT test data";
- }
-
- System.out.println("canRead: " + reader.getOriginatingProvider().canDecodeInput(input));
-
- reader.setInput(input);
- long start = System.currentTimeMillis();
- BufferedImage image = reader.read(0);
-
- System.out.println("time: " + (System.currentTimeMillis() - start));
-
- showIt(image, title);
-
- System.out.println("image = " + image);
+ protected static void showIt(final BufferedImage pImage, final String pTitle) {
+ ImageReaderBase.showIt(pImage, pTitle);
}
- // Sample data from http://developer.apple.com/documentation/mac/QuickDraw/QuickDraw-458.html
- // TODO: Create test case(s)!
- private static final byte[] DATA_EXT_V2 = {
- 0x00, 0x78, /* picture size; don't use this value for picture size */
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x6C, 0x00, (byte) 0xA8, /* bounding rectangle of picture at 72 dpi */
- 0x00, 0x11, /* VersionOp opcode; always $0011 for extended version 2 */
- 0x02, (byte) 0xFF, /* Version opcode; always $02FF for extended version 2 */
- 0x0C, 0x00, /* HeaderOp opcode; always $0C00 for extended version 2 */
- /* next 24 bytes contain header information */
- (byte) 0xFF, (byte) 0xFE, /* version; always -2 for extended version 2 */
- 0x00, 0x00, /* reserved */
- 0x00, 0x48, 0x00, 0x00, /* best horizontal resolution: 72 dpi */
- 0x00, 0x48, 0x00, 0x00, /* best vertical resolution: 72 dpi */
- 0x00, 0x02, 0x00, 0x02, 0x00, 0x6E, 0x00, (byte) 0xAA, /* optimal source rectangle for 72 dpi horizontal
- and 72 dpi vertical resolutions */
- 0x00, 0x00, /* reserved */
- 0x00, 0x1E, /* DefHilite opcode to use default hilite color */
- 0x00, 0x01, /* Clip opcode to define clipping region for picture */
- 0x00, 0x0A, /* region size */
- 0x00, 0x02, 0x00, 0x02, 0x00, 0x6E, 0x00, (byte) 0xAA, /* bounding rectangle for clipping region */
- 0x00, 0x0A, /* FillPat opcode; fill pattern specified in next 8 bytes */
- 0x77, (byte) 0xDD, 0x77, (byte) 0xDD, 0x77, (byte) 0xDD, 0x77, (byte) 0xDD, /* fill pattern */
- 0x00, 0x34, /* fillRect opcode; rectangle specified in next 8 bytes */
- 0x00, 0x02, 0x00, 0x02, 0x00, 0x6E, 0x00, (byte) 0xAA, /* rectangle to fill */
- 0x00, 0x0A, /* FillPat opcode; fill pattern specified in next 8 bytes */
- (byte) 0x88, 0x22, (byte) 0x88, 0x22, (byte) 0x88, 0x22, (byte) 0x88, 0x22, /* fill pattern */
- 0x00, 0x5C, /* fillSameOval opcode */
- 0x00, 0x08, /* PnMode opcode */
- 0x00, 0x08, /* pen mode data */
- 0x00, 0x71, /* paintPoly opcode */
- 0x00, 0x1A, /* size of polygon */
- 0x00, 0x02, 0x00, 0x02, 0x00, 0x6E, 0x00, (byte) 0xAA, /* bounding rectangle for polygon */
- 0x00, 0x6E, 0x00, 0x02, 0x00, 0x02, 0x00, 0x54, 0x00, 0x6E, 0x00, (byte) 0xAA, 0x00, 0x6E, 0x00, 0x02, /* polygon points */
- 0x00, (byte) 0xFF, /* OpEndPic opcode; end of picture */
- };
+ public static void main(final String[] pArgs) throws IOException {
+ ImageReader reader = new PICTImageReader(new PICTImageReaderSpi());
- private static final byte[] DATA_V2 = {
- 0x00, 0x78, /* picture size; don't use this value for picture size */
- 0x00, 0x02, 0x00, 0x02, 0x00, 0x6E, 0x00, (byte) 0xAA, /* bounding rectangle of picture */
- 0x00, 0x11, /* VersionOp opcode; always $0x00, 0x11, for version 2 */
- 0x02, (byte) 0xFF, /* Version opcode; always $0x02, 0xFF, for version 2 */
- 0x0C, 0x00, /* HeaderOp opcode; always $0C00 for version 2 */
- /* next 24 bytes contain header information */
- (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, /* version; always -1 (long) for version 2 */
- 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, (byte) 0xAA, 0x00, 0x00, 0x00, 0x6E, 0x00, 0x00, /* fixed-point bounding
- rectangle for picture */
- 0x00, 0x00, 0x00, 0x00, /* reserved */
- 0x00, 0x1E, /* DefHilite opcode to use default hilite color */
- 0x00, 0x01, /* Clip opcode to define clipping region for picture */
- 0x00, 0x0A, /* region size */
- 0x00, 0x02, 0x00, 0x02, 0x00, 0x6E, 0x00, (byte) 0xAA, /* bounding rectangle for clipping region */
- 0x00, 0x0A, /* FillPat opcode; fill pattern specifed in next 8 bytes */
- 0x77, (byte) 0xDD, 0x77, (byte) 0xDD, 0x77, (byte) 0xDD, 0x77, (byte) 0xDD, /* fill pattern */
- 0x00, 0x34, /* fillRect opcode; rectangle specified in next 8 bytes */
- 0x00, 0x02, 0x00, 0x02, 0x00, 0x6E, 0x00, (byte) 0xAA, /* rectangle to fill */
- 0x00, 0x0A, /* FillPat opcode; fill pattern specified in next 8 bytes */
- (byte) 0x88, 0x22, (byte) 0x88, 0x22, (byte) 0x88, 0x22, (byte) 0x88, 0x22, /* fill pattern */
- 0x00, 0x5C, /* fillSameOval opcode */
- 0x00, 0x08, /* PnMode opcode */
- 0x00, 0x08, /* pen mode data */
- 0x00, 0x71, /* paintPoly opcode */
- 0x00, 0x1A, /* size of polygon */
- 0x00, 0x02, 0x00, 0x02, 0x00, 0x6E, 0x00, (byte) 0xAA, /* bounding rectangle for polygon */
- 0x00, 0x6E, 0x00, 0x02, 0x00, 0x02, 0x00, 0x54, 0x00, 0x6E, 0x00, (byte) 0xAA, 0x00, 0x6E, 0x00, 0x02, /* polygon points */
- 0x00, (byte) 0xFF, /* OpEndPic opcode; end of picture */
- };
+ for (String arg : pArgs) {
+ File file = new File(arg);
+ try {
+ ImageInputStream input = ImageIO.createImageInputStream(file);
+ String title = file.getName();
- private static final byte[] DATA_V1 = {
- 0x00, 0x4F, /* picture size; this value is reliable for version 1 pictures */
- 0x00, 0x02, 0x00, 0x02, 0x00, 0x6E, 0x00, (byte) 0xAA, /* bounding rectangle of picture */
- 0x11, /* picVersion opcode for version 1 */
- 0x01, /* version number 1 */
- 0x01, /* ClipRgn opcode to define clipping region for picture */
- 0x00, 0x0A, /* region size */
- 0x00, 0x02, 0x00, 0x02, 0x00, 0x6E, 0x00, (byte) 0xAA, /* bounding rectangle for region */
- 0x0A, /* FillPat opcode; fill pattern specified in next 8 bytes */
- 0x77, (byte) 0xDD, 0x77, (byte) 0xDD, 0x77, (byte) 0xDD, 0x77, (byte) 0xDD, /* fill pattern */
- 0x34, /* fillRect opcode; rectangle specified in next 8 bytes */
- 0x00, 0x02, 0x00, 0x02, 0x00, 0x6E, 0x00, (byte) 0xAA, /* rectangle to fill */
- 0x0A, /* FillPat opcode; fill pattern specified in next 8 bytes */
- (byte) 0x88, 0x22, (byte) 0x88, 0x22, (byte) 0x88, 0x22, (byte) 0x88, 0x22, /* fill pattern */
- 0x5C, /* fillSameOval opcode */
- 0x71, /* paintPoly opcode */
- 0x00, 0x1A, /* size of polygon */
- 0x00, 0x02, 0x00, 0x02, 0x00, 0x6E, 0x00, (byte) 0xAA, /* bounding rectangle for polygon */
- 0x00, 0x6E, 0x00, 0x02, 0x00, 0x02, 0x00, 0x54, 0x00, 0x6E, 0x00, (byte) 0xAA, 0x00, 0x6E, 0x00, 0x02, /* polygon points */
- (byte) 0xFF, /* EndOfPicture opcode; end of picture */
- };
+ System.out.println("canRead: " + reader.getOriginatingProvider().canDecodeInput(input));
- // Examples from http://developer.apple.com/technotes/qd/qd_14.html
- private static final byte[] DATA_V1_OVAL_RECT = {
- 0x00, 0x26, /*size */
- 0x00, 0x0A, 0x00, 0x14, 0x00, (byte) 0xAF, 0x00, 0x78, /* picFrame */
- 0x11, 0x01, /* version 1 */
- 0x01, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xFA, 0x01, (byte) 0x90, /* clipRgn -- 10 byte region */
- 0x0B, 0x00, 0x04, 0x00, 0x05, /* ovSize point */
- 0x40, 0x00, 0x0A, 0x00, 0x14, 0x00, (byte) 0xAF, 0x00, 0x78, /* frameRRect rectangle */
- (byte) 0xFF, /* fin */
- };
+ reader.setInput(input);
+ // BufferedImage image = reader.getImageTypes(0).next().createBufferedImage(reader.getWidth(0), reader.getHeight(0));
+ // ImageReadParam param = reader.getDefaultReadParam();
+ // param.setDestination(image);
- private static final byte[] DATA_V1_OVERPAINTED_ARC = {
- 0x00, 0x36, /* size */
- 0x00, 0x0A, 0x00, 0x14, 0x00, (byte) 0xAF, 0x00, 0x78, /* picFrame */
- 0x11, 0x01, /* version 1 */
- 0x01, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xFA, 0x01, (byte) 0x90, /* clipRgn -- 10 byte region */
- 0x61, 0x00, 0x0A, 0x00, 0x14, 0x00, (byte) 0xAF, 0x00, 0x78, 0x00, 0x03, 0x00, 0x2D, /* paintArc rectangle,startangle,endangle */
- 0x08, 0x00, 0x0A, /* pnMode patXor -- note that the pnMode comes before the pnPat */
- 0x09, (byte) 0xAA, 0x55, (byte) 0xAA, 0x55, (byte) 0xAA, 0x55, (byte) 0xAA, 0x55, /* pnPat gray */
- 0x69, 0x00, 0x03, 0x00, 0x2D, /* paintSameArc startangle,endangle */
- (byte) 0xFF, /* fin */
- };
+ long start = System.currentTimeMillis();
+ BufferedImage image = reader.read(0);
+ System.out.println("time: " + (System.currentTimeMillis() - start));
- private static final byte[] DATA_V1_COPY_BITS = {
- 0x00, 0x48, /* size */
- 0x00, 0x0A, 0x00, 0x14, 0x00, (byte) 0xAF, 0x00, 0x78, /* picFrame */
- 0x11, 0x01, /* version 1 */
- 0x01, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xFA, 0x01, (byte) 0x90, /* clipRgn -- 10 byte region */
- 0x31, 0x00, 0x0A, 0x00, 0x14, 0x00, (byte) 0xAF, 0x00, 0x78, /* paintRect rectangle */
- (byte) 0x90, 0x00, 0x02, 0x00, 0x0A, 0x00, 0x14, 0x00, 0x0F, 0x00, 0x1C, /* BitsRect rowbytes bounds (note that bounds is wider than smallr) */
- 0x00, 0x0A, 0x00, 0x14, 0x00, 0x0F, 0x00, 0x19, /* srcRect */
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x1E, /* dstRect */
- 0x00, 0x06, /* mode=notSrcXor */
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5 rows of empty bitmap (we copied from a
- still-blank window) */
- (byte) 0xFF, /* fin */
- };
+ showIt(image, title);
+
+ System.out.println("image = " + image);
+ }
+ catch (IOException e) {
+ System.err.println("Could not read " + file.getAbsolutePath() + ": " + e);
+ }
+ }
+ }
}
diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderSpi.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderSpi.java
index bfa92b0c..e6323d46 100755
--- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderSpi.java
+++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderSpi.java
@@ -28,14 +28,12 @@
package com.twelvemonkeys.imageio.plugins.pict;
-import com.twelvemonkeys.imageio.spi.ProviderInfo;
-import com.twelvemonkeys.imageio.util.IIOUtil;
+import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
import javax.imageio.ImageReader;
-import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
-import java.io.IOException;
import java.io.EOFException;
+import java.io.IOException;
import java.util.Locale;
/**
@@ -45,28 +43,13 @@ import java.util.Locale;
* @author Harald Kuhr
* @version $Id: PICTImageReaderSpi.java,v 1.0 28.feb.2006 19:21:05 haku Exp$
*/
-public class PICTImageReaderSpi extends ImageReaderSpi {
+public class PICTImageReaderSpi extends ImageReaderSpiBase {
/**
* Creates a {@code PICTImageReaderSpi}.
*/
public PICTImageReaderSpi() {
- this(IIOUtil.getProviderInfo(PICTImageReaderSpi.class));
- }
-
- private PICTImageReaderSpi(final ProviderInfo pProviderInfo) {
- super(
- pProviderInfo.getVendorName(),
- pProviderInfo.getVersion(),
- new String[]{"pct", "PCT", "pict", "PICT"},
- new String[]{"pct", "pict"},
- new String[]{"image/pict", "image/x-pict"},
- "com.twelvemkonkeys.imageio.plugins.pict.PICTImageReader",
- new Class[] {ImageInputStream.class},
- new String[]{"com.twelvemkonkeys.imageio.plugins.pict.PICTImageWriterSpi"},
- true, null, null, null, null,
- true, null, null, null, null
- );
+ super(new PICTProviderInfo());
}
public boolean canDecodeInput(final Object pSource) throws IOException {
@@ -85,7 +68,7 @@ public class PICTImageReaderSpi extends ImageReaderSpi {
else {
// Skip header 512 bytes for file-based streams
stream.reset();
- PICTImageReader.skipNullHeader(stream);
+ skipNullHeader(stream);
}
return isPICT(stream);
@@ -98,6 +81,12 @@ public class PICTImageReaderSpi extends ImageReaderSpi {
}
}
+ static void skipNullHeader(final ImageInputStream pStream) throws IOException {
+ // NOTE: Only skip if FILE FORMAT, not needed for Mac OS DnD
+ // Spec says "platofrm dependent", may not be all nulls..
+ pStream.skipBytes(PICT.PICT_NULL_HEADER_SIZE);
+ }
+
private boolean isPICT(final ImageInputStream pStream) throws IOException {
// Size may be 0, so we can't use this for validation...
pStream.readUnsignedShort();
diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageWriter.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageWriter.java
index a4baa53c..df8734b6 100755
--- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageWriter.java
+++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageWriter.java
@@ -258,7 +258,7 @@ public class PICTImageWriter extends ImageWriterBase {
// Treat the scanline.
for (int j = 0; j < w; j++) {
if (model instanceof ComponentColorModel && model.getColorSpace().getType() == ColorSpace.TYPE_RGB) {
- // NOTE: Assumes component order always (A)BGR
+ // NOTE: Assumes component order always (A)BGR and sRGB
// TODO: Alpha support
scanlineBytes[x + j] = pixels[off + i * scansize * components + components * j + components - 1];
scanlineBytes[x + w + j] = pixels[off + i * scansize * components + components * j + components - 2];
diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageWriterSpi.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageWriterSpi.java
index 75382282..86507cdb 100755
--- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageWriterSpi.java
+++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageWriterSpi.java
@@ -28,12 +28,10 @@
package com.twelvemonkeys.imageio.plugins.pict;
-import com.twelvemonkeys.imageio.spi.ProviderInfo;
-import com.twelvemonkeys.imageio.util.IIOUtil;
+import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriter;
-import javax.imageio.spi.ImageWriterSpi;
import java.io.IOException;
import java.util.Locale;
@@ -44,29 +42,13 @@ import java.util.Locale;
* @author Harald Kuhr
* @version $Id: PICTImageWriterSpi.java,v 1.0 02.mar.2006 19:21:05 haku Exp$
*/
-public class PICTImageWriterSpi extends ImageWriterSpi {
+public class PICTImageWriterSpi extends ImageWriterSpiBase {
/**
* Creates a {@code PICTImageWriterSpi}.
*/
public PICTImageWriterSpi() {
- this(IIOUtil.getProviderInfo(PICTImageWriterSpi.class));
- }
-
- private PICTImageWriterSpi(final ProviderInfo pProviderInfo) {
- super(
- pProviderInfo.getVendorName(),
- pProviderInfo.getVersion(),
- new String[]{"pct", "PCT",
- "pict", "PICT"},
- new String[]{"pct", "pict"},
- new String[]{"image/pict", "image/x-pict"},
- "com.twelvemonkeys.imageio.plugins.pict.PICTImageWriter",
- STANDARD_OUTPUT_TYPE,
- new String[]{"com.twelvemonkeys.imageio.plugins.pict.PICTImageReaderSpi"},
- true, null, null, null, null,
- true, null, null, null, null
- );
+ super(new PICTProviderInfo());
}
public boolean canEncodeImage(ImageTypeSpecifier pType) {
diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTProviderInfo.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTProviderInfo.java
new file mode 100644
index 00000000..d5117e2f
--- /dev/null
+++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTProviderInfo.java
@@ -0,0 +1,27 @@
+package com.twelvemonkeys.imageio.plugins.pict;
+
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
+
+/**
+ * PICTProviderInfo.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: PICTProviderInfo.java,v 1.0 20/03/15 harald.kuhr Exp$
+ */
+final class PICTProviderInfo extends ReaderWriterProviderInfo {
+ protected PICTProviderInfo() {
+ super(
+ PICTProviderInfo.class,
+ new String[] {"pct", "PCT", "pict", "PICT"},
+ new String[] {"pct", "pict"},
+ new String[] {"image/pict", "image/x-pict"},
+ "com.twelvemkonkeys.imageio.plugins.pict.PICTImageReader",
+ new String[] {"com.twelvemonkeys.imageio.plugins.pict.PICTImageReaderSpi"},
+ "com.twelvemonkeys.imageio.plugins.pict.PICTImageWriter",
+ new String[] {"com.twelvemkonkeys.imageio.plugins.pict.PICTImageWriterSpi"},
+ false, null, null, null, null,
+ true, null, null, null, null
+ );
+ }
+}
diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTUtil.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTUtil.java
index ef503715..08de9277 100755
--- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTUtil.java
+++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTUtil.java
@@ -34,7 +34,8 @@ import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.io.DataInput;
import java.io.IOException;
-import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.UnsupportedCharsetException;
/**
* PICTUtil
@@ -47,15 +48,14 @@ final class PICTUtil {
private static final String ENC_MAC_ROMAN = "MacRoman";
- public static final String ENCODING = initEncoding();
+ public static final Charset ENCODING = initEncoding();
- private static String initEncoding() {
+ private static Charset initEncoding() {
try {
- new String("\uF8FF".getBytes(), ENC_MAC_ROMAN);
- return ENC_MAC_ROMAN;
+ return Charset.forName(ENC_MAC_ROMAN);
}
- catch (UnsupportedEncodingException e) {
- return "ISO-8859-1";
+ catch (UnsupportedCharsetException e) {
+ return Charset.forName("ISO-8859-1");
}
}
@@ -86,9 +86,9 @@ final class PICTUtil {
* @throws java.io.IOException if an I/O error occurs during read
*/
public static Dimension readDimension(final DataInput pStream) throws IOException {
- final int h = pStream.readShort() ;
- final int v = pStream.readShort() ;
- return new Dimension(h,v);
+ int h = pStream.readShort();
+ int v = pStream.readShort();
+ return new Dimension(h, v);
}
/**
@@ -102,8 +102,8 @@ final class PICTUtil {
* @throws IOException if an I/O exception occurs during reading
*/
public static String readStr31(final DataInput pStream) throws IOException {
- String text = readPascalString(pStream);
- int length = 31 - text.length();
+ String text = readPascalString(pStream);
+ int length = 31 - text.length();
if (length < 0) {
throw new IOException("String length exceeds maximum (31): " + text.length());
}
@@ -112,7 +112,7 @@ final class PICTUtil {
}
/**
- * Reads a Pascal String from the given strean.
+ * Reads a Pascal String from the given stream.
* The input stream must be positioned at the length byte of the text,
* which can thus be a maximum of 255 characters long.
*
@@ -146,6 +146,14 @@ final class PICTUtil {
return new BitMapPattern(data);
}
+ // TODO: Refactor, don't need both readPattern methods
+ public static Pattern readPattern(final DataInput pStream, final Color fg, final Color bg) throws IOException {
+ // Get the data (8 bytes)
+ byte[] data = new byte[8];
+ pStream.readFully(data);
+ return new BitMapPattern(data, fg, bg);
+ }
+
/**
* Reads a variable width {@link Pattern color pattern} from the given stream
*
@@ -221,7 +229,7 @@ final class PICTUtil {
/**
* Reads a {@code ColorTable} data structure from the given stream.
*
- * @param pStream the input stream
+ * @param pStream the input stream
* @param pPixelSize the pixel size
* @return the indexed color model created from the {@code ColorSpec} records read.
*
@@ -252,7 +260,7 @@ final class PICTUtil {
int[] colors = new int[size];
- for (int i = 0; i < size ; i++) {
+ for (int i = 0; i < size; i++) {
// Read ColorSpec records
int index = pStream.readUnsignedShort();
Color color = readRGBColor(pStream);
diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/Pattern.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/Pattern.java
index 32841877..13ceeec1 100755
--- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/Pattern.java
+++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/Pattern.java
@@ -29,9 +29,9 @@
package com.twelvemonkeys.imageio.plugins.pict;
import java.awt.*;
-import java.awt.geom.Rectangle2D;
import java.awt.geom.AffineTransform;
-import java.awt.image.*;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.ColorModel;
import java.util.Collections;
/**
@@ -42,7 +42,7 @@ import java.util.Collections;
* @version $Id: Pattern.java,v 1.0 Oct 9, 2007 1:21:38 AM haraldk Exp$
*/
abstract class Pattern implements Paint {
- private final Paint paint;
+ protected final Paint paint;
Pattern(final Paint pPaint) {
paint = pPaint;
@@ -60,5 +60,7 @@ abstract class Pattern implements Paint {
public int getTransparency() {
return paint.getTransparency();
- }
+ }
+
+ public abstract Pattern derive(Color foreground, Color background);
}
diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PixMap.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PixMap.java
new file mode 100644
index 00000000..43192ca8
--- /dev/null
+++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PixMap.java
@@ -0,0 +1,11 @@
+package com.twelvemonkeys.imageio.plugins.pict;
+
+/**
+ * PixMap.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: PixMap.java,v 1.0 20/02/15 harald.kuhr Exp$
+ */
+final class PixMap {
+}
diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PixMapPattern.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PixMapPattern.java
index d2108320..d84f0a45 100755
--- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PixMapPattern.java
+++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PixMapPattern.java
@@ -48,7 +48,12 @@ final class PixMapPattern extends Pattern {
/**
* @return the fallback B/W pattern
*/
- public Pattern getPattern() {
+ public Pattern getFallbackPattern() {
return fallback;
}
+
+ @Override
+ public Pattern derive(final Color foreground, final Color background) {
+ return getFallbackPattern().derive(foreground, background);
+ }
}
diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QuickDrawContext.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QuickDrawContext.java
index e9f52f9d..fd5a0567 100755
--- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QuickDrawContext.java
+++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QuickDrawContext.java
@@ -31,8 +31,11 @@ package com.twelvemonkeys.imageio.plugins.pict;
import com.twelvemonkeys.lang.Validate;
import java.awt.*;
-import java.awt.image.BufferedImage;
import java.awt.geom.*;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.Raster;
+import java.awt.image.WritableRaster;
/**
* Emulates an Apple QuickDraw rendering context, backed by a Java {@link Graphics2D}.
@@ -121,9 +124,20 @@ class QuickDrawContext {
private Dimension2D penSize = new Dimension();
private int penMode;
- QuickDrawContext(Graphics2D pGraphics) {
+ // TODO: Make sure setting bgColor/fgColor does not reset pattern, and pattern not resetting bg/fg!
+ private Color bgColor = Color.WHITE;
+ private Color fgColor = Color.BLACK;
+
+ private int textMode;
+ private Pattern textPattern = new BitMapPattern(Color.BLACK);
+ private Pattern fillPattern;
+
+ QuickDrawContext(final Graphics2D pGraphics) {
graphics = Validate.notNull(pGraphics, "graphics");
-
+
+ graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
setPenNormal();
}
@@ -144,18 +158,34 @@ class QuickDrawContext {
// Font number (sic), integer
void setTextFont(int fontFamily) {
// ..?
- System.err.println("QuickDrawContext.setTextFont");
+ System.err.println("QuickDrawContext.setTextFont: " + fontFamily);
+ }
+
+ public void setTextFont(final String fontName) {
+ // TODO: Need mapping between known QD font names and Java font names?
+ Font current = graphics.getFont();
+ graphics.setFont(Font.decode(fontName).deriveFont(current.getStyle(), (float) current.getSize()));
}
// Sets the text's font style (0..255)
- void setTextFace(int face) {
- // int?
- System.err.println("QuickDrawContext.setTextFace");
+ void setTextFace(final int face) {
+ int style = 0;
+ if ((face & QuickDraw.TX_BOLD_MASK) > 0) {
+ style |= Font.BOLD;
+ }
+ if ((face & QuickDraw.TX_ITALIC_MASK) > 0) {
+ style |= Font.ITALIC;
+ }
+
+ // TODO: Other face options, like underline, shadow, etc...
+
+ graphics.setFont(graphics.getFont().deriveFont(style));
}
void setTextMode(int pSourceMode) {
// ..?
System.err.println("QuickDrawContext.setTextMode");
+ textMode = pSourceMode;
}
public void setTextSize(int pSize) {
@@ -175,15 +205,24 @@ class QuickDrawContext {
graphics.translate(pOrigin.getX(), pOrigin.getY());
}
- public void setForeground(Color pColor) {
- // TODO: Is this really correct? Or does it depend on pattern mode?
+ public void setForeground(final Color pColor) {
+ fgColor = pColor;
penPattern = new BitMapPattern(pColor);
}
- public void setBackground(Color pColor) {
+ Color getForeground() {
+ return fgColor;
+ }
+
+ public void setBackground(final Color pColor) {
+ bgColor = pColor;
background = new BitMapPattern(pColor);
}
+ Color getBackground() {
+ return bgColor;
+ }
+
/*
// Pen management:
// NOTE: The HidePen procedure is called by the OpenRgn, OpenPicture, and OpenPoly routines so that you can create regions, pictures, and polygons without drawing on the screen.
@@ -306,10 +345,14 @@ class QuickDrawContext {
BackPat // Used by the Erase* methods
*BackPixPat
*/
- public void setBackgroundPattern(Pattern pPaint) {
+ public void setBackgroundPattern(final Pattern pPaint) {
background = pPaint;
}
+ public void setFillPattern(final Pattern fillPattern) {
+ this.fillPattern = fillPattern;
+ }
+
private Composite getCompositeFor(final int pMode) {
switch (pMode & ~QuickDraw.DITHER_COPY) {
// Boolean source transfer modes
@@ -321,9 +364,10 @@ class QuickDrawContext {
return AlphaComposite.Xor;
case QuickDraw.SRC_BIC:
return AlphaComposite.Clear;
+ case QuickDraw.NOT_SRC_XOR:
+ return new NotSrcXor();
case QuickDraw.NOT_SRC_COPY:
case QuickDraw.NOT_SRC_OR:
- case QuickDraw.NOT_SRC_XOR:
case QuickDraw.NOT_SRC_BIC:
throw new UnsupportedOperationException("Not implemented for mode " + pMode);
// return null;
@@ -349,6 +393,15 @@ class QuickDrawContext {
}
}
+ /**
+ * Sets up context for text drawing.
+ */
+ protected void setupForText() {
+ graphics.setPaint(textPattern);
+ graphics.setComposite(getCompositeFor(textMode));
+ }
+
+
/**
* Sets up context for line drawing/painting.
*/
@@ -415,9 +468,7 @@ class QuickDrawContext {
if (isPenVisible()) {
// NOTE: Workaround for known Mac JDK bug: Paint, not frame
- //graphics.setStroke(getStroke(penSize)); // Make sure we have correct stroke
paintShape(graphics.getStroke().createStrokedShape(line));
-
}
moveTo(pX, pY);
@@ -811,13 +862,18 @@ class QuickDrawContext {
// TODO: All other operations can delegate to these! :-)
private void frameShape(final Shape pShape) {
- setupForPaint();
- graphics.draw(pShape);
+ if (isPenVisible()) {
+ setupForPaint();
+
+ Stroke stroke = getStroke(penSize);
+ Shape shape = stroke.createStrokedShape(pShape);
+ graphics.draw(shape);
+ }
}
private void paintShape(final Shape pShape) {
setupForPaint();
- graphics.fill(pShape);
+ graphics.fill(pShape); // Yes, fill
}
private void fillShape(final Shape pShape, final Pattern pPattern) {
@@ -878,20 +934,22 @@ class QuickDrawContext {
pSrcRect.y + pSrcRect.height,
null
);
+
+ setClipRegion(null);
}
/**
* CopyMask
*/
public void copyMask(BufferedImage pSrcBitmap, BufferedImage pMaskBitmap, Rectangle pSrcRect, Rectangle pMaskRect, Rectangle pDstRect, int pSrcCopy, Shape pMaskRgn) {
- throw new UnsupportedOperationException("Method copyBits not implemented"); // TODO: Implement
+ throw new UnsupportedOperationException("Method copyMask not implemented"); // TODO: Implement
}
/**
* CopyDeepMask -- available to basic QuickDraw only in System 7, combines the functionality of both CopyBits and CopyMask
*/
public void copyDeepMask(BufferedImage pSrcBitmap, BufferedImage pMaskBitmap, Rectangle pSrcRect, Rectangle pMaskRect, Rectangle pDstRect, int pSrcCopy, Shape pMaskRgn) {
- throw new UnsupportedOperationException("Method copyBits not implemented"); // TODO: Implement
+ throw new UnsupportedOperationException("Method copyDeepMask not implemented"); // TODO: Implement
}
/*
@@ -926,7 +984,8 @@ class QuickDrawContext {
* @param pString a Pascal string (a string of length less than or equal to 255 chars).
*/
public void drawString(String pString) {
- graphics.drawString(pString, (float) getPenPosition().getX(), (float) getPenPosition().getY());
+ setupForText();
+ graphics.drawString(pString, (float) getPenPosition().getX(), (float) getPenPosition().getY());
}
/*
@@ -1049,4 +1108,41 @@ class QuickDrawContext {
}
}
+
+ private static class NotSrcXor implements Composite {
+ // TODO: Src can probably be any color model that can be encoded in PICT, dst is always RGB/TYPE_INT
+ public CompositeContext createContext(final ColorModel srcColorModel, final ColorModel dstColorModel, RenderingHints hints) {
+ {
+ if (!srcColorModel.getColorSpace().isCS_sRGB() || !dstColorModel.getColorSpace().isCS_sRGB()) {
+ throw new IllegalArgumentException("Only sRGB supported");
+ }
+ }
+
+ return new CompositeContext() {
+ public void dispose() {
+
+ }
+
+ public void compose(Raster src, Raster dstIn, WritableRaster dstOut) {
+ // We always work in RGB, using DataBuffer.TYPE_INT transfer type.
+ int[] srcData = null;
+ int[] dstData = null;
+ int[] resData = new int[src.getWidth() - src.getMinX()];
+
+ for (int y = src.getMinY(); y < src.getHeight(); y++) {
+ srcData = (int[]) src.getDataElements(src.getMinX(), y, src.getWidth(), 1, srcData);
+ dstData = (int[]) dstIn.getDataElements(src.getMinX(), y, src.getWidth(), 1, dstData);
+
+ for (int x = src.getMinX(); x < src.getWidth(); x++) {
+ // TODO: Decide how to handle alpha (if at all)
+ resData[x] = 0xff000000 | ((~ srcData[x] ^ dstData[x])) & 0xffffff ;
+// resData[x] = ~ srcData[x] ^ dstData[x];
+ }
+
+ dstOut.setDataElements(src.getMinX(), y, src.getWidth(), 1, resData);
+ }
+ }
+ };
+ }
+ }
}
diff --git a/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderTest.java b/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderTest.java
index d85e6fb3..41efe9f5 100644
--- a/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderTest.java
+++ b/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderTest.java
@@ -1,15 +1,19 @@
package com.twelvemonkeys.imageio.plugins.pict;
+import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
+import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStreamSpi;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
import org.junit.Test;
+import javax.imageio.spi.IIORegistry;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
+import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertFalse;
/**
* ICOImageReaderTestCase
@@ -20,6 +24,10 @@ import static org.junit.Assert.*;
*/
public class PICTImageReaderTest extends ImageReaderAbstractTestCase {
+ static {
+ IIORegistry.getDefaultInstance().registerServiceProvider(new ByteArrayImageInputStreamSpi());
+ }
+
static ImageReaderSpi sProvider = new PICTImageReaderSpi();
// TODO: Should also test the clipboard format (without 512 byte header)
@@ -32,8 +40,20 @@ public class PICTImageReaderTest extends ImageReaderAbstractTestCase 0);
- ImageInputStream input = ImageIO.createImageInputStream(new ByteArrayInputStream(buffer.toByteArray()));
+ ImageInputStream input = new ByteArrayImageInputStream(buffer.toByteArray());
BufferedImage written = ImageIO.read(input);
assertNotNull(written);
@@ -113,16 +112,23 @@ public class PICTImageWriterTest extends ImageWriterAbstractTestCase {
int originalRGB = original.getRGB(x, y);
int writtenRGB = written.getRGB(x, y);
+ int expectedR = (originalRGB & 0xff0000) >> 16;
+ int actualR = (writtenRGB & 0xff0000) >> 16;
+ int expectedG = (originalRGB & 0x00ff00) >> 8;
+ int actualG = (writtenRGB & 0x00ff00) >> 8;
+ int expectedB = originalRGB & 0x0000ff;
+ int actualB = writtenRGB & 0x0000ff;
+
if (original.getColorModel().getColorSpace().getType() == ColorSpace.TYPE_GRAY) {
// NOTE: For some reason, gray data seems to be one step off...
- assertEquals("Test data " + i + " R(" + x + "," + y + ")", originalRGB & 0xff0000, writtenRGB & 0xff0000, 0x10000);
- assertEquals("Test data " + i + " G(" + x + "," + y + ")", originalRGB & 0x00ff00, writtenRGB & 0x00ff00, 0x100);
- assertEquals("Test data " + i + " B(" + x + "," + y + ")", originalRGB & 0x0000ff, writtenRGB & 0x0000ff, 0x1);
+ // ...and vary with different backing CMSs... :-(
+ assertTrue(String.format("original 0x%08x != gray! (%d,%d)", originalRGB, x, y), expectedR == expectedG && expectedG == expectedB);
+ assertTrue(String.format("written 0x%08x != gray! (%d,%d)", writtenRGB, x, y), actualR == actualG && actualG == actualB);
}
else {
- assertEquals("Test data " + i + " R(" + x + "," + y + ")", originalRGB & 0xff0000, writtenRGB & 0xff0000);
- assertEquals("Test data " + i + " G(" + x + "," + y + ")", originalRGB & 0x00ff00, writtenRGB & 0x00ff00);
- assertEquals("Test data " + i + " B(" + x + "," + y + ")", originalRGB & 0x0000ff, writtenRGB & 0x0000ff);
+ assertEquals(String.format("Test data %d R(%d,%d)", i, x, y), expectedR, actualR);
+ assertEquals(String.format("Test data %d G(%d,%d)", i, x, y), expectedG, actualG);
+ assertEquals(String.format("Test data %d B(%d,%d)", i, x, y), expectedB, actualB);
}
}
}
diff --git a/imageio/imageio-pict/src/test/resources/pict/FC10.PCT b/imageio/imageio-pict/src/test/resources/pict/FC10.PCT
new file mode 100644
index 00000000..9f80bd9d
Binary files /dev/null and b/imageio/imageio-pict/src/test/resources/pict/FC10.PCT differ
diff --git a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PAMImageWriterSpi.java b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PAMImageWriterSpi.java
index 10d6f9ea..51490cb1 100755
--- a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PAMImageWriterSpi.java
+++ b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PAMImageWriterSpi.java
@@ -29,7 +29,6 @@
package com.twelvemonkeys.imageio.plugins.pnm;
import com.twelvemonkeys.imageio.spi.ProviderInfo;
-import com.twelvemonkeys.imageio.util.IIOUtil;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriter;
@@ -43,16 +42,16 @@ public final class PAMImageWriterSpi extends ImageWriterSpi {
* Creates a {@code PAMImageWriterSpi}.
*/
public PAMImageWriterSpi() {
- this(IIOUtil.getProviderInfo(PAMImageWriterSpi.class));
+ this(new PNMProviderInfo());
}
private PAMImageWriterSpi(final ProviderInfo pProviderInfo) {
super(
pProviderInfo.getVendorName(),
pProviderInfo.getVersion(),
- new String[]{"pam", "PAM"},
- new String[]{"pam"},
- new String[]{
+ new String[] {"pam", "PAM"},
+ new String[] {"pam"},
+ new String[] {
// No official IANA record exists, these are conventional
"image/x-portable-arbitrarymap" // PAM
},
@@ -73,7 +72,8 @@ public final class PAMImageWriterSpi extends ImageWriterSpi {
return new PNMImageWriter(this);
}
- @Override public String getDescription(final Locale locale) {
+ @Override
+ public String getDescription(final Locale locale) {
return "NetPBM Portable Arbitrary Map (PAM) image writer";
}
}
diff --git a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReaderSpi.java b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReaderSpi.java
index 967144d9..c35e64d0 100755
--- a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReaderSpi.java
+++ b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReaderSpi.java
@@ -29,7 +29,6 @@
package com.twelvemonkeys.imageio.plugins.pnm;
import com.twelvemonkeys.imageio.spi.ProviderInfo;
-import com.twelvemonkeys.imageio.util.IIOUtil;
import javax.imageio.ImageReader;
import javax.imageio.spi.ImageReaderSpi;
@@ -43,19 +42,19 @@ public final class PNMImageReaderSpi extends ImageReaderSpi {
* Creates a {@code PNMImageReaderSpi}.
*/
public PNMImageReaderSpi() {
- this(IIOUtil.getProviderInfo(PNMImageReaderSpi.class));
+ this(new PNMProviderInfo());
}
private PNMImageReaderSpi(final ProviderInfo providerInfo) {
super(
providerInfo.getVendorName(),
providerInfo.getVersion(),
- new String[]{
- "pnm", "pbm", "pgm", "ppm", "pam",
- "PNM", "PBM", "PGM", "PPM", "PAM"
+ new String[] {
+ "pnm", "pbm", "pgm", "ppm", "pam", "pfm",
+ "PNM", "PBM", "PGM", "PPM", "PAM", "PFM"
},
- new String[]{"pbm", "pgm", "ppm", "pam"},
- new String[]{
+ new String[] {"pbm", "pgm", "ppm", "pam", "pfm"},
+ new String[] {
// No official IANA record exists, these are conventional
"image/x-portable-pixmap",
"image/x-portable-anymap",
@@ -63,7 +62,7 @@ public final class PNMImageReaderSpi extends ImageReaderSpi {
},
"com.twelvemkonkeys.imageio.plugins.pnm.PNMImageReader",
new Class[] {ImageInputStream.class},
- new String[]{
+ new String[] {
"com.twelvemkonkeys.imageio.plugins.pnm.PNMImageWriterSpi",
"com.twelvemkonkeys.imageio.plugins.pnm.PAMImageWriterSpi"
},
@@ -76,7 +75,8 @@ public final class PNMImageReaderSpi extends ImageReaderSpi {
);
}
- @Override public boolean canDecodeInput(final Object source) throws IOException {
+ @Override
+ public boolean canDecodeInput(final Object source) throws IOException {
if (!(source instanceof ImageInputStream)) {
return false;
}
@@ -109,11 +109,13 @@ public final class PNMImageReaderSpi extends ImageReaderSpi {
}
}
- @Override public ImageReader createReaderInstance(final Object extension) throws IOException {
+ @Override
+ public ImageReader createReaderInstance(final Object extension) throws IOException {
return new PNMImageReader(this);
}
- @Override public String getDescription(final Locale locale) {
+ @Override
+ public String getDescription(final Locale locale) {
return "NetPBM Portable Any Map (PNM and PAM) image reader";
}
}
diff --git a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageWriterSpi.java b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageWriterSpi.java
index 287e6039..5a7522c0 100755
--- a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageWriterSpi.java
+++ b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageWriterSpi.java
@@ -29,7 +29,6 @@
package com.twelvemonkeys.imageio.plugins.pnm;
import com.twelvemonkeys.imageio.spi.ProviderInfo;
-import com.twelvemonkeys.imageio.util.IIOUtil;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriter;
@@ -41,23 +40,24 @@ public final class PNMImageWriterSpi extends ImageWriterSpi {
// TODO: Consider one Spi for each sub-format, as it makes no sense for the writer to write PPM if client code requested PBM or PGM format.
// ...Then again, what if user asks for PNM? :-P
+
/**
* Creates a {@code PNMImageWriterSpi}.
*/
public PNMImageWriterSpi() {
- this(IIOUtil.getProviderInfo(PNMImageWriterSpi.class));
+ this(new PNMProviderInfo());
}
private PNMImageWriterSpi(final ProviderInfo pProviderInfo) {
super(
pProviderInfo.getVendorName(),
pProviderInfo.getVersion(),
- new String[]{
+ new String[] {
"pnm", "pbm", "pgm", "ppm",
"PNM", "PBM", "PGM", "PPM"
},
- new String[]{"pbm", "pgm", "ppm"},
- new String[]{
+ new String[] {"pbm", "pgm", "ppm"},
+ new String[] {
// No official IANA record exists, these are conventional
"image/x-portable-pixmap",
"image/x-portable-anymap"
@@ -79,7 +79,8 @@ public final class PNMImageWriterSpi extends ImageWriterSpi {
return new PNMImageWriter(this);
}
- @Override public String getDescription(final Locale locale) {
+ @Override
+ public String getDescription(final Locale locale) {
return "NetPBM Portable Any Map (PNM) image writer";
}
}
diff --git a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMProviderInfo.java b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMProviderInfo.java
new file mode 100644
index 00000000..15eb1d8f
--- /dev/null
+++ b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMProviderInfo.java
@@ -0,0 +1,19 @@
+package com.twelvemonkeys.imageio.plugins.pnm;
+
+import com.twelvemonkeys.imageio.spi.ProviderInfo;
+
+/**
+ * PNMProviderInfo.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: PNMProviderInfo.java,v 1.0 20/03/15 harald.kuhr Exp$
+ */
+class PNMProviderInfo extends ProviderInfo {
+ // NOTE: Because the ReaderSpi and the two WriterSpis supports different formats,
+ // we don't use the standard ImageReaderWriterProviderInfo superclass here.
+
+ public PNMProviderInfo() {
+ super(PNMProviderInfo.class.getPackage());
+ }
+}
diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDHeader.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDHeader.java
index 7278f3f5..cdd31def 100755
--- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDHeader.java
+++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDHeader.java
@@ -29,7 +29,6 @@
package com.twelvemonkeys.imageio.plugins.psd;
import javax.imageio.IIOException;
-
import java.io.DataInput;
import java.io.IOException;
@@ -120,7 +119,7 @@ final class PSDHeader {
case PSD.COLOR_MODE_LAB:
break;
default:
- throw new IIOException(String.format("Unsupported mode depth for PSD: %d", mode));
+ throw new IIOException(String.format("Unsupported color mode for PSD: %d", mode));
}
}
diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderSpi.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderSpi.java
index 831d08dc..35e6f5d3 100755
--- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderSpi.java
+++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderSpi.java
@@ -28,11 +28,9 @@
package com.twelvemonkeys.imageio.plugins.psd;
-import com.twelvemonkeys.imageio.spi.ProviderInfo;
-import com.twelvemonkeys.imageio.util.IIOUtil;
+import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
import javax.imageio.ImageReader;
-import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.util.Locale;
@@ -44,39 +42,13 @@ import java.util.Locale;
* @author last modified by $Author: haraldk$
* @version $Id: PSDImageReaderSpi.java,v 1.0 Apr 29, 2008 4:49:03 PM haraldk Exp$
*/
-final public class PSDImageReaderSpi extends ImageReaderSpi {
+final public class PSDImageReaderSpi extends ImageReaderSpiBase {
/**
* Creates a {@code PSDImageReaderSpi}.
*/
public PSDImageReaderSpi() {
- this(IIOUtil.getProviderInfo(PSDImageReaderSpi.class));
- }
-
- private PSDImageReaderSpi(final ProviderInfo providerInfo) {
- super(
- providerInfo.getVendorName(),
- providerInfo.getVersion(),
- new String[] {"psd", "PSD"},
- new String[] {"psd"},
- new String[] {
- "image/vnd.adobe.photoshop", // Official, IANA registered
- "application/vnd.adobe.photoshop", // Used in XMP
- "image/x-psd",
- "application/x-photoshop",
- "image/x-photoshop"
- },
- "com.twelvemkonkeys.imageio.plugins.psd.PSDImageReader",
- new Class[] {ImageInputStream.class},
-// new String[] {"com.twelvemkonkeys.imageio.plugins.psd.PSDImageWriterSpi"},
- null,
- true, // supports standard stream metadata
- null, null, // native stream format name and class
- null, null, // extra stream formats
- true, // supports standard image metadata
- PSDMetadata.NATIVE_METADATA_FORMAT_NAME, PSDMetadata.NATIVE_METADATA_FORMAT_CLASS_NAME,
- null, null // extra image metadata formats
- );
+ super(new PSDProviderInfo());
}
public boolean canDecodeInput(final Object pSource) throws IOException {
diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDProviderInfo.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDProviderInfo.java
new file mode 100644
index 00000000..2b52c770
--- /dev/null
+++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDProviderInfo.java
@@ -0,0 +1,33 @@
+package com.twelvemonkeys.imageio.plugins.psd;
+
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
+
+/**
+ * PSDProviderInfo.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: PSDProviderInfo.java,v 1.0 20/03/15 harald.kuhr Exp$
+ */
+final class PSDProviderInfo extends ReaderWriterProviderInfo {
+ protected PSDProviderInfo() {
+ super(
+ PSDProviderInfo.class,
+ new String[] {"psd", "PSD"},
+ new String[] {"psd"},
+ new String[] {
+ "image/vnd.adobe.photoshop", // Official, IANA registered
+ "application/vnd.adobe.photoshop", // Used in XMP
+ "image/x-psd",
+ "application/x-photoshop",
+ "image/x-photoshop"
+ },
+ "com.twelvemkonkeys.imageio.plugins.psd.PSDImageReader",
+ new String[] {"com.twelvemonkeys.imageio.plugins.psd.PSDImageReaderSpi"},
+ null,
+ null, // new String[] {"com.twelvemkonkeys.imageio.plugins.psd.PSDImageWriterSpi"},
+ false, null, null, null, null,
+ true, PSDMetadata.NATIVE_METADATA_FORMAT_NAME, PSDMetadata.NATIVE_METADATA_FORMAT_CLASS_NAME, null, null
+ );
+ }
+}
diff --git a/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIImageReaderSpi.java b/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIImageReaderSpi.java
index 5d72062b..1aa9436e 100755
--- a/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIImageReaderSpi.java
+++ b/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIImageReaderSpi.java
@@ -28,48 +28,20 @@
package com.twelvemonkeys.imageio.plugins.sgi;
-import com.twelvemonkeys.imageio.spi.ProviderInfo;
-import com.twelvemonkeys.imageio.util.IIOUtil;
+import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
import javax.imageio.ImageReader;
-import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.util.Locale;
-public final class SGIImageReaderSpi extends ImageReaderSpi {
+public final class SGIImageReaderSpi extends ImageReaderSpiBase {
/**
* Creates a {@code SGIImageReaderSpi}.
*/
public SGIImageReaderSpi() {
- this(IIOUtil.getProviderInfo(SGIImageReaderSpi.class));
- }
-
- private SGIImageReaderSpi(final ProviderInfo providerInfo) {
- super(
- providerInfo.getVendorName(),
- providerInfo.getVersion(),
- new String[]{
- "sgi",
- "SGI"
- },
- new String[]{"sgi"},
- new String[]{
- // No official IANA record exists
- "image/sgi",
- "image/x-sgi",
- },
- "com.twelvemkonkeys.imageio.plugins.sgi.SGIImageReader",
- new Class[] {ImageInputStream.class},
- null,
- true, // supports standard stream metadata
- null, null, // native stream format name and class
- null, null, // extra stream formats
- true, // supports standard image metadata
- null, null,
- null, null // extra image metadata formats
- );
+ super(new SGIProviderInfo());
}
@Override public boolean canDecodeInput(final Object source) throws IOException {
diff --git a/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIProviderInfo.java b/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIProviderInfo.java
new file mode 100644
index 00000000..bcfd503b
--- /dev/null
+++ b/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIProviderInfo.java
@@ -0,0 +1,34 @@
+package com.twelvemonkeys.imageio.plugins.sgi;
+
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
+
+/**
+ * SGIProviderInfo.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: SGIProviderInfo.java,v 1.0 20/03/15 harald.kuhr Exp$
+ */
+final class SGIProviderInfo extends ReaderWriterProviderInfo {
+ protected SGIProviderInfo() {
+ super(
+ SGIProviderInfo.class,
+ new String[] {
+ "sgi",
+ "SGI"
+ },
+ new String[] {"sgi"},
+ new String[] {
+ // No official IANA record exists
+ "image/sgi",
+ "image/x-sgi",
+ },
+ "com.twelvemkonkeys.imageio.plugins.sgi.SGIImageReader",
+ new String[] {"com.twelvemonkeys.imageio.plugins.sgi.SGIImageReaderSpi"},
+ null,
+ null,
+ false, null, null, null, null,
+ true, null, null, null, null
+ );
+ }
+}
diff --git a/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAImageReaderSpi.java b/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAImageReaderSpi.java
index 57ca4808..2948915a 100755
--- a/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAImageReaderSpi.java
+++ b/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAImageReaderSpi.java
@@ -28,49 +28,21 @@
package com.twelvemonkeys.imageio.plugins.tga;
-import com.twelvemonkeys.imageio.spi.ProviderInfo;
-import com.twelvemonkeys.imageio.util.IIOUtil;
+import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
import javax.imageio.ImageReader;
-import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.nio.ByteOrder;
import java.util.Locale;
-public final class TGAImageReaderSpi extends ImageReaderSpi {
+public final class TGAImageReaderSpi extends ImageReaderSpiBase {
/**
* Creates a {@code TGAImageReaderSpi}.
*/
public TGAImageReaderSpi() {
- this(IIOUtil.getProviderInfo(TGAImageReaderSpi.class));
- }
-
- private TGAImageReaderSpi(final ProviderInfo providerInfo) {
- super(
- providerInfo.getVendorName(),
- providerInfo.getVersion(),
- new String[]{
- "tga", "TGA",
- "targa", "TARGA"
- },
- new String[]{"tga", "tpic"},
- new String[]{
- // No official IANA record exists
- "image/tga", "image/x-tga",
- "image/targa", "image/x-targa",
- },
- "com.twelvemkonkeys.imageio.plugins.tga.TGAImageReader",
- new Class[] {ImageInputStream.class},
- null,
- true, // supports standard stream metadata
- null, null, // native stream format name and class
- null, null, // extra stream formats
- true, // supports standard image metadata
- null, null,
- null, null // extra image metadata formats
- );
+ super(new TGAProviderInfo());
}
@Override public boolean canDecodeInput(final Object source) throws IOException {
diff --git a/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAProviderInfo.java b/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAProviderInfo.java
new file mode 100644
index 00000000..c48d47e2
--- /dev/null
+++ b/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAProviderInfo.java
@@ -0,0 +1,34 @@
+package com.twelvemonkeys.imageio.plugins.tga;
+
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
+
+/**
+ * SGIProviderInfo.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: SGIProviderInfo.java,v 1.0 20/03/15 harald.kuhr Exp$
+ */
+final class TGAProviderInfo extends ReaderWriterProviderInfo {
+ protected TGAProviderInfo() {
+ super(
+ TGAProviderInfo.class,
+ new String[]{
+ "tga", "TGA",
+ "targa", "TARGA"
+ },
+ new String[]{"tga", "tpic"},
+ new String[]{
+ // No official IANA record exists
+ "image/tga", "image/x-tga",
+ "image/targa", "image/x-targa",
+ },
+ "com.twelvemkonkeys.imageio.plugins.tga.TGAImageReader",
+ new String[] {"com.twelvemonkeys.imageio.plugins.tga.TGAImageReaderSpi"},
+ null,
+ null,
+ false, null, null, null, null,
+ true, null, null, null, null
+ );
+ }
+}
diff --git a/imageio/imageio-thumbsdb/src/main/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBImageReaderSpi.java b/imageio/imageio-thumbsdb/src/main/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBImageReaderSpi.java
index a8ad5fb2..89c474ae 100755
--- a/imageio/imageio-thumbsdb/src/main/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBImageReaderSpi.java
+++ b/imageio/imageio-thumbsdb/src/main/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBImageReaderSpi.java
@@ -28,8 +28,7 @@
package com.twelvemonkeys.imageio.plugins.thumbsdb;
-import com.twelvemonkeys.imageio.spi.ProviderInfo;
-import com.twelvemonkeys.imageio.util.IIOUtil;
+import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
import com.twelvemonkeys.io.ole2.CompoundDocument;
import javax.imageio.ImageReader;
@@ -48,36 +47,21 @@ import java.util.Locale;
* @author Harald Kuhr
* @version $Id: ThumbsDBImageReaderSpi.java,v 1.0 28.feb.2006 19:21:05 haku Exp$
*/
-public class ThumbsDBImageReaderSpi extends ImageReaderSpi {
+public class ThumbsDBImageReaderSpi extends ImageReaderSpiBase {
private ImageReaderSpi jpegProvider;
/**
* Creates a {@code ThumbsDBImageReaderSpi}.
*/
public ThumbsDBImageReaderSpi() {
- this(IIOUtil.getProviderInfo(ThumbsDBImageReaderSpi.class));
- }
-
- private ThumbsDBImageReaderSpi(final ProviderInfo pProviderInfo) {
- super(
- pProviderInfo.getVendorName(),
- pProviderInfo.getVersion(),
- new String[]{"thumbs", "THUMBS", "Thumbs DB"},
- new String[]{"db"},
- new String[]{"image/x-thumbs-db", "application/octet-stream"}, // TODO: Check IANA et al...
- "com.twelvemonkeys.imageio.plugins.thumbsdb.ThumbsDBImageReader",
- new Class[] {ImageInputStream.class},
- null,
- true, null, null, null, null,
- true, null, null, null, null
- );
+ super(new ThumbsDBProviderInfo());
}
public boolean canDecodeInput(Object source) throws IOException {
return source instanceof ImageInputStream && canDecode((ImageInputStream) source);
}
- public boolean canDecode(ImageInputStream pInput) throws IOException {
+ public boolean canDecode(final ImageInputStream pInput) throws IOException {
maybeInitJPEGProvider();
// If this is a OLE 2 CompoundDocument, we could try...
// TODO: How do we know it's thumbs.db format (structure), without reading quite a lot?
diff --git a/imageio/imageio-thumbsdb/src/main/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBProviderInfo.java b/imageio/imageio-thumbsdb/src/main/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBProviderInfo.java
new file mode 100644
index 00000000..861dab33
--- /dev/null
+++ b/imageio/imageio-thumbsdb/src/main/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBProviderInfo.java
@@ -0,0 +1,27 @@
+package com.twelvemonkeys.imageio.plugins.thumbsdb;
+
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
+
+/**
+ * ThumbsDBProviderInfo.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: ThumbsDBProviderInfo.java,v 1.0 20/03/15 harald.kuhr Exp$
+ */
+final class ThumbsDBProviderInfo extends ReaderWriterProviderInfo {
+ protected ThumbsDBProviderInfo() {
+ super(
+ ThumbsDBProviderInfo.class,
+ new String[]{"thumbs", "THUMBS", "Thumbs DB"},
+ new String[]{"db"},
+ new String[]{"image/x-thumbs-db", "application/octet-stream"}, // TODO: Check IANA et al...
+ "com.twelvemonkeys.imageio.plugins.thumbsdb.ThumbsDBImageReader",
+ new String[] {"com.twelvemonkeys.imageio.plugins.thumbsdb.ThumbsDBImageReaderSpi"},
+ null,
+ null,
+ false, null, null, null, null,
+ true, null, null, null, null
+ );
+ }
+}
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/HorizontalDifferencingStream.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/HorizontalDifferencingStream.java
new file mode 100644
index 00000000..7a8943f1
--- /dev/null
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/HorizontalDifferencingStream.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (c) 2014, Harald Kuhr
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name "TwelveMonkeys" nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.twelvemonkeys.imageio.plugins.tiff;
+
+import com.twelvemonkeys.lang.Validate;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.Channels;
+import java.nio.channels.WritableByteChannel;
+
+/**
+ * A decoder for data converted using "horizontal differencing predictor".
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: HorizontalDeDifferencingStream.java,v 1.0 11.03.13 14:20 haraldk Exp$
+ */
+final class HorizontalDifferencingStream extends OutputStream {
+ // See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64.
+
+ private final int columns;
+ // NOTE: PlanarConfiguration == 2 may be treated as samplesPerPixel == 1
+ private final int samplesPerPixel;
+ private final int bitsPerSample;
+
+ private final WritableByteChannel channel;
+ private final ByteBuffer buffer;
+
+ public HorizontalDifferencingStream(final OutputStream stream, final int columns, final int samplesPerPixel, final int bitsPerSample, final ByteOrder byteOrder) {
+ this.columns = Validate.isTrue(columns > 0, columns, "width must be greater than 0");
+ this.samplesPerPixel = Validate.isTrue(bitsPerSample >= 8 || samplesPerPixel == 1, samplesPerPixel, "Unsupported samples per pixel for < 8 bit samples: %s");
+ this.bitsPerSample = Validate.isTrue(isValidBPS(bitsPerSample), bitsPerSample, "Unsupported bits per sample value: %s");
+
+ channel = Channels.newChannel(Validate.notNull(stream, "stream"));
+
+ buffer = ByteBuffer.allocate((columns * samplesPerPixel * bitsPerSample + 7) / 8).order(byteOrder);
+ }
+
+ private boolean isValidBPS(final int bitsPerSample) {
+ switch (bitsPerSample) {
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ case 16:
+ case 32:
+ case 64:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private boolean flushBuffer() throws IOException {
+ if (buffer.position() == 0) {
+ return false;
+ }
+
+ encodeRow();
+
+ buffer.flip();
+ channel.write(buffer);
+ buffer.clear();
+
+ return true;
+ }
+
+ private void encodeRow() throws EOFException {
+ // Apply horizontal predictor
+ byte original;
+ int sample = 0;
+ int prev;
+ byte temp;
+
+ // Optimization:
+ // Access array directly for <= 8 bits per sample, as buffer does extra index bounds check for every
+ // put/get operation... (Measures to about 100 ms difference for 4000 x 3000 image)
+ final byte[] array = buffer.array();
+
+ switch (bitsPerSample) {
+ case 1:
+ for (int b = ((columns + 7) / 8) - 1; b > 0; b--) {
+ // Subtract previous sample from current sample
+ original = array[b];
+ prev = array[b - 1] & 0x1;
+ temp = (byte) ((((original & 0x80) >> 7) - prev) << 7);
+
+ sample = ((original & 0x40) >> 6) - ((original & 0x80) >> 7);
+ temp |= (sample << 6) & 0x40;
+
+ sample = ((original & 0x20) >> 5) - ((original & 0x40) >> 6);
+ temp |= (sample << 5) & 0x20;
+
+ sample = ((original & 0x10) >> 4) - ((original & 0x20) >> 5);
+ temp |= (sample << 4) & 0x10;
+
+ sample = ((original & 0x08) >> 3) - ((original & 0x10) >> 4);
+ temp |= (sample << 3) & 0x08;
+
+ sample = ((original & 0x04) >> 2) - ((original & 0x08) >> 3);
+ temp |= (sample << 2) & 0x04;
+
+ sample = ((original & 0x02) >> 1) - ((original & 0x04) >> 2);
+ temp |= (sample << 1) & 0x02;
+
+ sample = (original & 0x01) - ((original & 0x02) >> 1);
+
+ array[b] = (byte) (temp & 0xfe | sample & 0x01);
+ }
+
+ // First sample in row as is
+ original = array[0];
+ temp = (byte) (original & 0x80);
+
+ sample = ((original & 0x40) >> 6) - ((original & 0x80) >> 7);
+ temp |= (sample << 6) & 0x40;
+
+ sample = ((original & 0x20) >> 5) - ((original & 0x40) >> 6);
+ temp |= (sample << 5) & 0x20;
+
+ sample = ((original & 0x10) >> 4) - ((original & 0x20) >> 5);
+ temp |= (sample << 4) & 0x10;
+
+ sample = ((original & 0x08) >> 3) - ((original & 0x10) >> 4);
+ temp |= (sample << 3) & 0x08;
+
+ sample = ((original & 0x04) >> 2) - ((original & 0x08) >> 3);
+ temp |= (sample << 2) & 0x04;
+
+ sample = ((original & 0x02) >> 1) - ((original & 0x04) >> 2);
+ temp |= (sample << 1) & 0x02;
+
+ sample = (original & 0x01) - ((original & 0x02) >> 1);
+
+ array[0] = (byte) (temp & 0xfe | sample & 0x01);
+ break;
+
+ case 2:
+ for (int b = ((columns + 3) / 4) - 1; b > 0; b--) {
+ // Subtract previous sample from current sample
+ original = array[b];
+ prev = array[b - 1] & 0x3;
+ temp = (byte) ((((original & 0xc0) >> 6) - prev) << 6);
+
+ sample = ((original & 0x30) >> 4) - ((original & 0xc0) >> 6);
+ temp |= (sample << 4) & 0x30;
+
+ sample = ((original & 0x0c) >> 2) - ((original & 0x30) >> 4);
+ temp |= (sample << 2) & 0x0c;
+
+ sample = (original & 0x03) - ((original & 0x0c) >> 2);
+
+ array[b] = (byte) (temp & 0xfc | sample & 0x03);
+ }
+
+ // First sample in row as is
+ original = array[0];
+ temp = (byte) (original & 0xc0);
+
+ sample = ((original & 0x30) >> 4) - ((original & 0xc0) >> 6);
+ temp |= (sample << 4) & 0x30;
+
+ sample = ((original & 0x0c) >> 2) - ((original & 0x30) >> 4);
+ temp |= (sample << 2) & 0x0c;
+
+ sample = (original & 0x03) - ((original & 0x0c) >> 2);
+
+ array[0] = (byte) (temp & 0xfc | sample & 0x03);
+ break;
+
+ case 4:
+ for (int b = ((columns + 1) / 2) - 1; b > 0; b--) {
+ // Subtract previous sample from current sample
+ original = array[b];
+ prev = array[b - 1] & 0xf;
+ temp = (byte) ((((original & 0xf0) >> 4) - prev) << 4);
+ sample = (original & 0x0f) - ((original & 0xf0) >> 4);
+ array[b] = (byte) (temp & 0xf0 | sample & 0xf);
+ }
+
+ // First sample in row as is
+ original = array[0];
+ sample = (original & 0x0f) - ((original & 0xf0) >> 4);
+ array[0] = (byte) (original & 0xf0 | sample & 0xf);
+
+ break;
+
+ case 8:
+ for (int x = columns - 1; x > 0; x--) {
+ final int xOff = x * samplesPerPixel;
+
+ for (int b = 0; b < samplesPerPixel; b++) {
+ int off = xOff + b;
+ array[off] = (byte) (array[off] - array[off - samplesPerPixel]);
+ }
+ }
+ break;
+
+ case 16:
+ for (int x = columns - 1; x > 0; x--) {
+ for (int b = 0; b < samplesPerPixel; b++) {
+ int off = x * samplesPerPixel + b;
+ buffer.putShort(2 * off, (short) (buffer.getShort(2 * off) - buffer.getShort(2 * (off - samplesPerPixel))));
+ }
+ }
+ break;
+
+ case 32:
+ for (int x = columns - 1; x > 0; x--) {
+ for (int b = 0; b < samplesPerPixel; b++) {
+ int off = x * samplesPerPixel + b;
+ buffer.putInt(4 * off, buffer.getInt(4 * off) - buffer.getInt(4 * (off - samplesPerPixel)));
+ }
+ }
+ break;
+
+ case 64:
+ for (int x = columns - 1; x > 0; x--) {
+ for (int b = 0; b < samplesPerPixel; b++) {
+ int off = x * samplesPerPixel + b;
+ buffer.putLong(8 * off, buffer.getLong(8 * off) - buffer.getLong(8 * (off - samplesPerPixel)));
+ }
+ }
+ break;
+
+ default:
+ throw new AssertionError(String.format("Unsupported bits per sample value: %d", bitsPerSample));
+ }
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ buffer.put((byte) b);
+
+ if (!buffer.hasRemaining()) {
+ flushBuffer();
+ }
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ while (len > 0) {
+ int maxLenForRow = Math.min(len, buffer.remaining());
+
+ buffer.put(b, off, maxLenForRow);
+ off += maxLenForRow;
+ len -= maxLenForRow;
+
+ if (!buffer.hasRemaining()) {
+ flushBuffer();
+ }
+ }
+ }
+
+ @Override
+ public void flush() throws IOException {
+ flushBuffer();
+ }
+
+ @Override
+ public void close() throws IOException {
+ try {
+ flushBuffer();
+ super.close();
+ }
+ finally {
+ if (channel.isOpen()) {
+ channel.close();
+ }
+ }
+ }
+}
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java
index bc47635c..87c328c3 100644
--- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java
@@ -108,6 +108,10 @@ abstract class LZWDecoder implements Decoder {
break;
}
+ if (table[code] == null) {
+ throw new DecodeException(String.format("Corrupted TIFF LZW: code %d (table size: %d)", code, tableLength));
+ }
+
table[code].writeTo(buffer);
}
else {
@@ -184,8 +188,9 @@ abstract class LZWDecoder implements Decoder {
}
}
- public static LZWDecoder create(boolean oldBitReversedStream) {
+ public static Decoder create(boolean oldBitReversedStream) {
return oldBitReversedStream ? new LZWCompatibilityDecoder() : new LZWSpecDecoder();
+// return oldBitReversedStream ? new LZWCompatibilityDecoder() : new LZWTreeDecoder();
}
static final class LZWSpecDecoder extends LZWDecoder {
@@ -282,7 +287,9 @@ abstract class LZWDecoder implements Decoder {
}
}
- static final class LZWString {
+ static final class LZWString implements Comparable {
+ static final LZWString EMPTY = new LZWString((byte) 0, (byte) 0, 0, null);
+
final LZWString previous;
final int length;
@@ -300,8 +307,12 @@ abstract class LZWDecoder implements Decoder {
this.previous = previous;
}
- public final LZWString concatenate(final byte firstChar) {
- return new LZWString(firstChar, this.firstChar, length + 1, this);
+ public final LZWString concatenate(final byte value) {
+ if (this == EMPTY) {
+ return new LZWString(value);
+ }
+
+ return new LZWString(value, this.firstChar, length + 1, this);
}
public final void writeTo(final ByteBuffer buffer) {
@@ -364,6 +375,35 @@ abstract class LZWDecoder implements Decoder {
result = 31 * result + (int) firstChar;
return result;
}
+
+ @Override
+ public int compareTo(final LZWString other) {
+ if (other == this) {
+ return 0;
+ }
+
+ if (length != other.length) {
+ return other.length - length;
+ }
+
+ if (firstChar != other.firstChar) {
+ return other.firstChar - firstChar;
+ }
+
+ LZWString t = this;
+ LZWString o = other;
+
+ for (int i = length - 1; i > 0; i--) {
+ if (t.value != o.value) {
+ return o.value - t.value;
+ }
+
+ t = t.previous;
+ o = o.previous;
+ }
+
+ return 0;
+ }
}
}
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWEncoder.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWEncoder.java
new file mode 100644
index 00000000..ee3cdf17
--- /dev/null
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWEncoder.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (c) 2014, Harald Kuhr
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name "TwelveMonkeys" nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.twelvemonkeys.imageio.plugins.tiff;
+
+import com.twelvemonkeys.io.enc.Encoder;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.TreeMap;
+
+import static com.twelvemonkeys.imageio.plugins.tiff.LZWDecoder.LZWString;
+
+/**
+ * LZWEncoder
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: LZWEncoder.java,v 1.0 02.12.13 14:13 haraldk Exp$
+ */
+final class LZWEncoder implements Encoder {
+ // TODO: Consider extracting LZWStringTable from LZWDecoder
+
+ /** Clear: Re-initialize tables. */
+ static final int CLEAR_CODE = 256;
+ /** End of Information. */
+ static final int EOI_CODE = 257;
+
+ private static final int MIN_BITS = 9;
+ private static final int MAX_BITS = 12;
+
+ private static final int TABLE_SIZE = 1 << MAX_BITS;
+
+ private int remaining;
+
+ private final LZWString[] table = new LZWString[TABLE_SIZE];
+// private final Map reverseTable = new HashMap<>(TABLE_SIZE - 256); // This is foobar
+ private final Map reverseTable = new TreeMap<>(); // This is foobar
+ private int tableLength;
+ LZWString omega = LZWString.EMPTY;
+
+ int bitsPerCode;
+ private int oldCode = CLEAR_CODE;
+ private int maxCode;
+ int bitMask;
+
+ int bits;
+ int bitPos;
+
+ protected LZWEncoder(final int length) {
+ this.remaining = length;
+
+ // First 258 entries of table is always fixed
+ for (int i = 0; i < 256; i++) {
+ table[i] = new LZWString((byte) i);
+ }
+
+ init();
+ }
+
+ private static int bitmaskFor(final int bits) {
+ return (1 << bits) - 1;
+ }
+
+ private void init() {
+ tableLength = 258;
+ bitsPerCode = MIN_BITS;
+ bitMask = bitmaskFor(bitsPerCode);
+ maxCode = maxCode();
+// omega = LZWString.EMPTY;
+ reverseTable.clear();
+ }
+
+ protected int maxCode() {
+ return bitMask;
+ }
+
+ public void encode(final OutputStream stream, final ByteBuffer buffer) throws IOException {
+// InitializeStringTable();
+// WriteCode(ClearCode);
+// Ω = the empty string;
+// for each character in the strip {
+// K = GetNextCharacter();
+// if Ω+K is in the string table {
+// Ω = Ω+K;/* string concatenation */
+// }
+// else{
+// WriteCode (CodeFromString( Ω));
+// AddTableEntry(Ω+K);
+// Ω=K;
+// } }/*end of for loop*/
+// WriteCode (CodeFromString(Ω));
+// WriteCode (EndOfInformation);
+
+ if (remaining < 0) {
+ throw new IOException("Write past end of stream");
+ }
+
+ // TODO: Write 9 bit clear code ONLY first time!
+ if (oldCode == CLEAR_CODE) {
+ writeCode(stream, CLEAR_CODE);
+ }
+
+ int len = buffer.remaining();
+
+ while (buffer.hasRemaining()) {
+ byte k = buffer.get();
+
+ LZWString string = omega.concatenate(k);
+
+ int tableIndex = isInTable(string);
+ if (tableIndex >= 0) {
+ omega = string;
+ oldCode = tableIndex;
+ }
+ else {
+ writeCode(stream, oldCode);
+ addStringToTable(string);
+ oldCode = k & 0xff;
+ omega = table[k & 0xff];
+
+ // Handle table (almost) full
+ if (tableLength >= TABLE_SIZE - 2) {
+ writeCode(stream, CLEAR_CODE);
+ init();
+ }
+ }
+ }
+
+ remaining -= len;
+
+ // Write EOI when er are done (the API isn't very supportive of this)
+ if (remaining <= 0) {
+ writeCode(stream, oldCode);
+ writeCode(stream, EOI_CODE);
+ if (bitPos > 0) {
+ writeCode(stream, 0);
+ }
+ }
+ }
+
+ private int isInTable(final LZWString string) {
+ if (string.length == 1) {
+ return string.value & 0xff;
+ }
+
+ Integer index = reverseTable.get(string);
+ return index != null ? index : -1;
+
+ // TODO: Needs optimization :-)
+// for (int i = 258; i < tableLength; i++) {
+// if (table[i].equals(string)) {
+// return i;
+// }
+// }
+
+// return -1;
+ }
+
+ private int addStringToTable(final LZWString string) {
+// System.err.println("LZWEncoder.addStringToTable: " + string);
+ final int index = tableLength++;
+ table[index] = string;
+ reverseTable.put(string, index);
+
+ if (tableLength > maxCode) {
+ bitsPerCode++;
+
+ if (bitsPerCode > MAX_BITS) {
+ throw new IllegalStateException(String.format("TIFF LZW with more than %d bits per code encountered (table overflow)", MAX_BITS));
+ }
+
+ bitMask = bitmaskFor(bitsPerCode);
+ maxCode = maxCode();
+ }
+
+// if (string.length > maxString) {
+// maxString = string.length;
+// }
+
+ return index;
+ }
+
+ private void writeCode(final OutputStream stream, final int code) throws IOException {
+// System.err.printf("LZWEncoder.writeCode: 0x%04x\n", code);
+ bits = (bits << bitsPerCode) | (code & bitMask);
+ bitPos += bitsPerCode;
+
+ while (bitPos >= 8) {
+ int b = (bits >> (bitPos - 8)) & 0xff;
+// System.err.printf("write: 0x%02x\n", b);
+ stream.write(b);
+ bitPos -= 8;
+ }
+
+ bits &= bitmaskFor(bitPos);
+ }
+}
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderSpi.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderSpi.java
index 451993f6..989f1166 100644
--- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderSpi.java
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderSpi.java
@@ -29,8 +29,7 @@
package com.twelvemonkeys.imageio.plugins.tiff;
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
-import com.twelvemonkeys.imageio.spi.ProviderInfo;
-import com.twelvemonkeys.imageio.util.IIOUtil;
+import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.spi.ServiceRegistry;
@@ -46,34 +45,12 @@ import java.util.Locale;
* @author last modified by $Author: haraldk$
* @version $Id: TIFFImageReaderSpi.java,v 1.0 08.05.12 15:14 haraldk Exp$
*/
-public class TIFFImageReaderSpi extends ImageReaderSpi {
+public class TIFFImageReaderSpi extends ImageReaderSpiBase {
/**
* Creates a {@code TIFFImageReaderSpi}.
*/
public TIFFImageReaderSpi() {
- this(IIOUtil.getProviderInfo(TIFFImageReaderSpi.class));
- }
-
- private TIFFImageReaderSpi(final ProviderInfo providerInfo) {
- super(
- providerInfo.getVendorName(),
- providerInfo.getVersion(),
- new String[]{"tiff", "TIFF"},
- new String[]{"tif", "tiff"},
- new String[]{
- "image/tiff", "image/x-tiff"
- },
- "com.twelvemkonkeys.imageio.plugins.tiff.TIFFImageReader",
- new Class[] {ImageInputStream.class},
-// new String[]{"com.twelvemkonkeys.imageio.plugins.tif.TIFFImageWriterSpi"},
- null,
- true, // supports standard stream metadata
- null, null, // native stream format name and class
- null, null, // extra stream formats
- true, // supports standard image metadata
- null, null,
- null, null // extra image metadata formats
- );
+ super(new TIFFProviderInfo());
}
@SuppressWarnings("unchecked")
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriteParam.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriteParam.java
new file mode 100644
index 00000000..637ea6f3
--- /dev/null
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriteParam.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2014, Harald Kuhr
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name "TwelveMonkeys" nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.twelvemonkeys.imageio.plugins.tiff;
+
+import javax.imageio.ImageWriteParam;
+import java.util.Locale;
+
+/**
+ * TIFFImageWriteParam
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: TIFFImageWriteParam.java,v 1.0 18.09.13 12:47 haraldk Exp$
+ */
+public final class TIFFImageWriteParam extends ImageWriteParam {
+ // TODO: Support CCITT Modified Huffman compression (2) BASELINE!!
+ // TODO: Support CCITT T.4 (3)
+ // TODO: Support CCITT T.6 (4)
+ // TODO: Support JBIG compression via ImageIO plugin/delegate?
+ // TODO: Support JPEG2000 compression via ImageIO plugin/delegate?
+ // TODO: Support tiling
+ // TODO: Support OPTIONAL predictor. See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64.
+
+ // DONE:
+ // Support no compression (None/1)
+ // Support ZLIB (/Deflate) compression (8)
+ // Support PackBits compression (32773)
+ // Support LZW compression (5)?
+ // Support JPEG compression (7)
+
+ TIFFImageWriteParam() {
+ this(Locale.getDefault());
+ }
+
+ TIFFImageWriteParam(final Locale locale) {
+ super(locale);
+
+ // NOTE: We use the same spelling/casing as the JAI equivalent to be as compatible as possible
+ // See: http://download.java.net/media/jai-imageio/javadoc/1.1/com/sun/media/imageio/plugins/tiff/TIFFImageWriteParam.html
+ compressionTypes = new String[] {
+ "None",
+ null, null, null,/* "CCITT RLE", "CCITT T.4", "CCITT T.6", */
+ "LZW", "JPEG", "ZLib", "PackBits", "Deflate",
+ null/* "EXIF JPEG" */ // A well-defined form of "Old-style JPEG", no tables/process, only 513 (offset) and 514 (length)
+ };
+ compressionType = compressionTypes[0];
+ canWriteCompressed = true;
+ }
+
+ @Override
+ public float[] getCompressionQualityValues() {
+ super.getCompressionQualityValues();
+
+ // TODO: Special case for JPEG and ZLib/Deflate
+
+ return null;
+ }
+
+ @Override
+ public String[] getCompressionQualityDescriptions() {
+ super.getCompressionQualityDescriptions();
+
+ // TODO: Special case for JPEG and ZLib/Deflate
+
+ return null;
+ }
+
+ static int getCompressionType(final ImageWriteParam param) {
+ // TODO: Support mode COPY_FROM_METADATA (when we have metadata...)
+ if (param == null || param.getCompressionMode() != MODE_EXPLICIT || param.getCompressionType().equals("None")) {
+ return TIFFBaseline.COMPRESSION_NONE;
+ }
+ else if (param.getCompressionType().equals("PackBits")) {
+ return TIFFBaseline.COMPRESSION_PACKBITS;
+ }
+ else if (param.getCompressionType().equals("ZLib")) {
+ return TIFFExtension.COMPRESSION_ZLIB;
+ }
+ else if (param.getCompressionType().equals("Deflate")) {
+ return TIFFExtension.COMPRESSION_DEFLATE;
+ }
+ else if (param.getCompressionType().equals("LZW")) {
+ return TIFFExtension.COMPRESSION_LZW;
+ }
+ else if (param.getCompressionType().equals("JPEG")) {
+ return TIFFExtension.COMPRESSION_JPEG;
+ }
+// else if (param.getCompressionType().equals("EXIF JPEG")) {
+// return TIFFExtension.COMPRESSION_OLD_JPEG;
+// }
+
+ throw new IllegalArgumentException(String.format("Unsupported compression type: %s", param.getCompressionType()));
+ }
+}
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java
new file mode 100644
index 00000000..9f672cfb
--- /dev/null
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java
@@ -0,0 +1,767 @@
+/*
+ * Copyright (c) 2014, Harald Kuhr
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name "TwelveMonkeys" nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.twelvemonkeys.imageio.plugins.tiff;
+
+import com.twelvemonkeys.image.ImageUtil;
+import com.twelvemonkeys.imageio.ImageWriterBase;
+import com.twelvemonkeys.imageio.metadata.AbstractEntry;
+import com.twelvemonkeys.imageio.metadata.Entry;
+import com.twelvemonkeys.imageio.metadata.exif.EXIFWriter;
+import com.twelvemonkeys.imageio.metadata.exif.Rational;
+import com.twelvemonkeys.imageio.metadata.exif.TIFF;
+import com.twelvemonkeys.imageio.stream.SubImageOutputStream;
+import com.twelvemonkeys.imageio.util.IIOUtil;
+import com.twelvemonkeys.io.enc.EncoderStream;
+import com.twelvemonkeys.io.enc.PackBitsEncoder;
+
+import javax.imageio.*;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.spi.ImageWriterSpi;
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.stream.ImageOutputStream;
+import java.awt.*;
+import java.awt.color.ColorSpace;
+import java.awt.color.ICC_ColorSpace;
+import java.awt.image.*;
+import java.io.*;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+
+/**
+ * TIFFImageWriter
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: TIFFImageWriter.java,v 1.0 18.09.13 12:46 haraldk Exp$
+ */
+public final class TIFFImageWriter extends ImageWriterBase {
+ // Short term
+ // TODO: Support JPEG compression (7) - might need extra input to allow multiple images with single DQT
+ // TODO: Use sensible defaults for compression based on input? None is sensible... :-)
+
+ // Long term
+ // TODO: Support tiling
+ // TODO: Support thumbnails
+ // TODO: Support ImageIO metadata
+ // TODO: Support CCITT Modified Huffman compression (2)
+ // TODO: Full "Baseline TIFF" support
+ // TODO: Support LZW compression (5)?
+ // ----
+ // TODO: Support storing multiple images in one stream (multi-page TIFF)
+ // TODO: Support use-case: Transcode multi-layer PSD to multi-page TIFF with metadata
+ // TODO: Support use-case: Transcode multi-page TIFF to multiple single-page TIFFs with metadata
+ // TODO: Support use-case: Losslessly transcode JPEG to JPEG in TIFF with (EXIF) metadata (and back)
+
+ // Very long term...
+ // TODO: Support JBIG compression via ImageIO plugin/delegate? Pending support in Reader
+ // TODO: Support JPEG2000 compression via ImageIO plugin/delegate? Pending support in Reader
+
+ // Done
+ // Create a basic writer that supports most inputs. Store them using the simplest possible format.
+ // Support no compression (None/1) - BASELINE
+ // Support predictor. See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64.
+ // Support PackBits compression (32773) - easy - BASELINE
+ // Support ZLIB (/Deflate) compression (8) - easy
+
+ public static final Rational STANDARD_DPI = new Rational(72);
+
+ TIFFImageWriter(final ImageWriterSpi provider) {
+ super(provider);
+ }
+
+ static final class TIFFEntry extends AbstractEntry {
+ TIFFEntry(Object identifier, Object value) {
+ super(identifier, value);
+ }
+ }
+
+ @Override
+ public void write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param) throws IOException {
+ // TODO: Validate input
+
+ assertOutput();
+
+ // TODO: Consider writing TIFF header, offset to IFD0 (leave blank), write image data with correct
+ // tiling/compression/etc, then write IFD0, go back and update IFD0 offset?
+
+ // Write minimal TIFF header (required "Baseline" fields)
+ // Use EXIFWriter to write leading metadata (TODO: consider rename to TTIFFWriter, again...)
+ // TODO: Make TIFFEntry and possibly TIFFDirectory? public
+ RenderedImage renderedImage = image.getRenderedImage();
+ ColorModel colorModel = renderedImage.getColorModel();
+ int numComponents = colorModel.getNumComponents();
+
+ SampleModel sampleModel = renderedImage.getSampleModel();
+
+ int[] bandOffsets;
+ int[] bitOffsets;
+ if (sampleModel instanceof ComponentSampleModel) {
+ bandOffsets = ((ComponentSampleModel) sampleModel).getBandOffsets();
+// System.err.println("bandOffsets: " + Arrays.toString(bandOffsets));
+ bitOffsets = null;
+ }
+ else if (sampleModel instanceof SinglePixelPackedSampleModel) {
+ bitOffsets = ((SinglePixelPackedSampleModel) sampleModel).getBitOffsets();
+// System.err.println("bitOffsets: " + Arrays.toString(bitOffsets));
+ bandOffsets = null;
+ }
+ else if (sampleModel instanceof MultiPixelPackedSampleModel) {
+ bitOffsets = null;
+ bandOffsets = new int[] {0};
+ }
+ else {
+ throw new IllegalArgumentException("Unknown bit/bandOffsets for sample model: " + sampleModel);
+ }
+
+ List entries = new ArrayList();
+ entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, renderedImage.getWidth()));
+ entries.add(new TIFFEntry(TIFF.TAG_IMAGE_HEIGHT, renderedImage.getHeight()));
+// entries.add(new TIFFEntry(TIFF.TAG_ORIENTATION, 1)); // (optional)
+ entries.add(new TIFFEntry(TIFF.TAG_BITS_PER_SAMPLE, asShortArray(sampleModel.getSampleSize())));
+ // If numComponents > 3, write ExtraSamples
+ if (numComponents > 3) {
+ // TODO: Write per component > 3
+ if (colorModel.hasAlpha()) {
+ entries.add(new TIFFEntry(TIFF.TAG_EXTRA_SAMPLES, colorModel.isAlphaPremultiplied() ? TIFFBaseline.EXTRASAMPLE_ASSOCIATED_ALPHA : TIFFBaseline.EXTRASAMPLE_UNASSOCIATED_ALPHA));
+ }
+ else {
+ entries.add(new TIFFEntry(TIFF.TAG_EXTRA_SAMPLES, TIFFBaseline.EXTRASAMPLE_UNSPECIFIED));
+ }
+ }
+
+ // Write compression field from param or metadata
+ int compression = TIFFImageWriteParam.getCompressionType(param);
+ entries.add(new TIFFEntry(TIFF.TAG_COMPRESSION, compression));
+ // TODO: Let param control predictor
+ switch (compression) {
+ case TIFFExtension.COMPRESSION_ZLIB:
+ case TIFFExtension.COMPRESSION_DEFLATE:
+ case TIFFExtension.COMPRESSION_LZW:
+ entries.add(new TIFFEntry(TIFF.TAG_PREDICTOR, TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING));
+ default:
+ }
+
+ int photometric = compression == TIFFExtension.COMPRESSION_JPEG ?
+ TIFFExtension.PHOTOMETRIC_YCBCR :
+ getPhotometricInterpretation(colorModel);
+ entries.add(new TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, photometric));
+
+ if (photometric == TIFFBaseline.PHOTOMETRIC_PALETTE && colorModel instanceof IndexColorModel) {
+ entries.add(new TIFFEntry(TIFF.TAG_COLOR_MAP, createColorMap((IndexColorModel) colorModel)));
+ entries.add(new TIFFEntry(TIFF.TAG_SAMPLES_PER_PIXEL, 1));
+ }
+ else {
+ entries.add(new TIFFEntry(TIFF.TAG_SAMPLES_PER_PIXEL, numComponents));
+
+ // Note: Assuming sRGB to be the default RGB interpretation
+ ColorSpace colorSpace = colorModel.getColorSpace();
+ if (colorSpace instanceof ICC_ColorSpace && !colorSpace.isCS_sRGB()) {
+ entries.add(new TIFFEntry(TIFF.TAG_ICC_PROFILE, ((ICC_ColorSpace) colorSpace).getProfile().getData()));
+ }
+ }
+
+ if (sampleModel.getDataType() == DataBuffer.TYPE_SHORT /* TODO: if (isSigned(sampleModel.getDataType) or getSampleFormat(sampleModel) != 0 */) {
+ entries.add(new TIFFEntry(TIFF.TAG_SAMPLE_FORMAT, TIFFExtension.SAMPLEFORMAT_INT));
+ }
+
+ entries.add(new TIFFEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO TIFF writer")); // TODO: Get from metadata (optional) + fill in version number
+
+ entries.add(new TIFFEntry(TIFF.TAG_X_RESOLUTION, STANDARD_DPI));
+ entries.add(new TIFFEntry(TIFF.TAG_Y_RESOLUTION, STANDARD_DPI));
+ entries.add(new TIFFEntry(TIFF.TAG_RESOLUTION_UNIT, TIFFBaseline.RESOLUTION_UNIT_DPI));
+
+ // TODO: RowsPerStrip - can be entire image (or even 2^32 -1), but it's recommended to write "about 8K bytes" per strip
+ entries.add(new TIFFEntry(TIFF.TAG_ROWS_PER_STRIP, Integer.MAX_VALUE)); // TODO: Allowed but not recommended
+ // - StripByteCounts - for no compression, entire image data... (TODO: How to know the byte counts prior to writing data?)
+ TIFFEntry dummyStripByteCounts = new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, -1);
+ entries.add(dummyStripByteCounts); // Updated later
+ // - StripOffsets - can be offset to single strip only (TODO: but how large is the IFD data...???)
+ TIFFEntry dummyStripOffsets = new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, -1);
+ entries.add(dummyStripOffsets); // Updated later
+
+ // TODO: If tiled, write tile indexes etc, or always do that?
+
+ EXIFWriter exifWriter = new EXIFWriter();
+
+ if (compression == TIFFBaseline.COMPRESSION_NONE) {
+ // This implementation, allows semi-streaming-compatible uncompressed TIFFs
+ long streamOffset = exifWriter.computeIFDSize(entries) + 12; // 12 == 4 byte magic, 4 byte IDD 0 pointer, 4 byte EOF
+
+ entries.remove(dummyStripByteCounts);
+ entries.add(new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, renderedImage.getWidth() * renderedImage.getHeight() * numComponents));
+ entries.remove(dummyStripOffsets);
+ entries.add(new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, streamOffset));
+
+ exifWriter.write(entries, imageOutput); // NOTE: Writer takes case of ordering tags
+ imageOutput.flush();
+ }
+ else {
+ // Unless compression == 1 / COMPRESSION_NONE (and all offsets known), write only TIFF header/magic + leave room for IFD0 offset
+ exifWriter.writeTIFFHeader(imageOutput);
+ imageOutput.writeInt(-1); // IFD0 pointer, will be updated later
+ }
+
+ // TODO: Create compressor stream per Tile/Strip
+ if (compression == TIFFExtension.COMPRESSION_JPEG) {
+ Iterator writers = ImageIO.getImageWritersByFormatName("JPEG");
+ if (!writers.hasNext()) {
+ // This can only happen if someone deliberately uninstalled it
+ throw new IIOException("No JPEG ImageWriter found!");
+ }
+
+ ImageWriter jpegWriter = writers.next();
+ try {
+ jpegWriter.setOutput(new SubImageOutputStream(imageOutput));
+ jpegWriter.write(renderedImage);
+ }
+ finally {
+ jpegWriter.dispose();
+ }
+ }
+ else {
+ // Write image data
+ writeImageData(createCompressorStream(renderedImage, param), renderedImage, numComponents, bandOffsets, bitOffsets);
+ }
+
+ // TODO: Update IFD0-pointer, and write IFD
+ if (compression != TIFFBaseline.COMPRESSION_NONE) {
+ long streamPosition = imageOutput.getStreamPosition();
+
+ entries.remove(dummyStripOffsets);
+ entries.add(new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, 8));
+ entries.remove(dummyStripByteCounts);
+ entries.add(new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, streamPosition - 8));
+
+ long ifdOffset = exifWriter.writeIFD(entries, imageOutput);
+ imageOutput.writeInt(0); // Next IFD (none)
+ streamPosition = imageOutput.getStreamPosition();
+
+ // Update IFD0 pointer
+ imageOutput.seek(4);
+ imageOutput.writeInt((int) ifdOffset);
+ imageOutput.seek(streamPosition);
+ imageOutput.flush();
+ }
+ }
+
+ private DataOutput createCompressorStream(RenderedImage image, ImageWriteParam param) {
+ /*
+ 36 MB test data:
+
+ No compression:
+ Write time: 450 ms
+ output.length: 36000226
+
+ PackBits:
+ Write time: 688 ms
+ output.length: 30322187
+
+ Deflate, BEST_SPEED (1):
+ Write time: 1276 ms
+ output.length: 14128866
+
+ Deflate, 2:
+ Write time: 1297 ms
+ output.length: 13848735
+
+ Deflate, 3:
+ Write time: 1594 ms
+ output.length: 13103224
+
+ Deflate, 4:
+ Write time: 1663 ms
+ output.length: 13380899 (!!)
+
+ 5
+ Write time: 1941 ms
+ output.length: 13171244
+
+ 6
+ Write time: 2311 ms
+ output.length: 12845101
+
+ 7: Write time: 2853 ms
+ output.length: 12759426
+
+ 8:
+ Write time: 4429 ms
+ output.length: 12624517
+
+ Deflate: DEFAULT_COMPRESSION (6?):
+ Write time: 2357 ms
+ output.length: 12845101
+
+ Deflate, BEST_COMPRESSION (9):
+ Write time: 4998 ms
+ output.length: 12600399
+ */
+
+ // Use predictor by default for LZW and ZLib/Deflate
+ // TODO: Unless explicitly disabled in TIFFImageWriteParam
+ int compression = TIFFImageWriteParam.getCompressionType(param);
+ OutputStream stream;
+
+ switch (compression) {
+ case TIFFBaseline.COMPRESSION_NONE:
+ return imageOutput;
+ case TIFFBaseline.COMPRESSION_PACKBITS:
+ stream = IIOUtil.createStreamAdapter(imageOutput);
+ stream = new EncoderStream(stream, new PackBitsEncoder(), true);
+ // NOTE: PackBits + Predictor is possible, but not generally supported, disable it by default
+ // (and probably not even allow it, see http://stackoverflow.com/questions/20337400/tiff-packbits-compression-with-predictor-step)
+// stream = new HorizontalDifferencingStream(stream, image.getTileWidth(), image.getTile(0, 0).getNumBands(), image.getColorModel().getComponentSize(0), imageOutput.getByteOrder());
+ return new DataOutputStream(stream);
+
+ case TIFFExtension.COMPRESSION_ZLIB:
+ case TIFFExtension.COMPRESSION_DEFLATE:
+ int deflateSetting = Deflater.BEST_SPEED; // This is consistent with default compression quality being 1.0 and 0 meaning max compression...
+ if (param.getCompressionMode() == ImageWriteParam.MODE_EXPLICIT) {
+ // TODO: Determine how to interpret compression quality...
+ // Docs says:
+ // A compression quality setting of 0.0 is most generically interpreted as "high compression is important,"
+ // while a setting of 1.0 is most generically interpreted as "high image quality is important."
+ // Is this what JAI TIFFImageWriter (TIFFDeflater) does? No, it does:
+ /*
+ if (param & compression etc...) {
+ float quality = param.getCompressionQuality();
+ deflateLevel = (int)(1 + 8*quality);
+ } else {
+ deflateLevel = Deflater.DEFAULT_COMPRESSION;
+ }
+ */
+ // PS: PNGImageWriter just uses hardcoded BEST_COMPRESSION... :-P
+ deflateSetting = 9 - Math.round(8 * (param.getCompressionQuality())); // This seems more correct
+ }
+
+ stream = IIOUtil.createStreamAdapter(imageOutput);
+ stream = new DeflaterOutputStream(stream, new Deflater(deflateSetting), 1024);
+ stream = new HorizontalDifferencingStream(stream, image.getTileWidth(), image.getTile(0, 0).getNumBands(), image.getColorModel().getComponentSize(0), imageOutput.getByteOrder());
+
+ return new DataOutputStream(stream);
+
+ case TIFFExtension.COMPRESSION_LZW:
+ stream = IIOUtil.createStreamAdapter(imageOutput);
+ stream = new EncoderStream(stream, new LZWEncoder((image.getTileWidth() * image.getTileHeight() * image.getTile(0, 0).getNumBands() * image.getColorModel().getComponentSize(0) + 7) / 8));
+ stream = new HorizontalDifferencingStream(stream, image.getTileWidth(), image.getTile(0, 0).getNumBands(), image.getColorModel().getComponentSize(0), imageOutput.getByteOrder());
+
+ return new DataOutputStream(stream);
+ }
+
+ throw new IllegalArgumentException(String.format("Unsupported TIFF compression: %d", compression));
+ }
+
+ private int getPhotometricInterpretation(final ColorModel colorModel) {
+ if (colorModel.getNumComponents() == 1 && colorModel.getComponentSize(0) == 1) {
+ if (colorModel instanceof IndexColorModel) {
+ if (colorModel.getRGB(0) == 0xFFFFFF && colorModel.getRGB(1) == 0x000000) {
+ return TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO;
+ }
+ else if (colorModel.getRGB(0) != 0x000000 || colorModel.getRGB(1) != 0xFFFFFF) {
+ return TIFFBaseline.PHOTOMETRIC_PALETTE;
+ }
+ // Else, fall through to default, BLACK_IS_ZERO
+ }
+
+ return TIFFBaseline.PHOTOMETRIC_BLACK_IS_ZERO;
+ }
+ else if (colorModel instanceof IndexColorModel) {
+ return TIFFBaseline.PHOTOMETRIC_PALETTE;
+ }
+
+ switch (colorModel.getColorSpace().getType()) {
+ case ColorSpace.TYPE_GRAY:
+ return TIFFBaseline.PHOTOMETRIC_BLACK_IS_ZERO;
+ case ColorSpace.TYPE_RGB:
+ return TIFFBaseline.PHOTOMETRIC_RGB;
+ case ColorSpace.TYPE_CMYK:
+ return TIFFExtension.PHOTOMETRIC_SEPARATED;
+ }
+
+ throw new IllegalArgumentException("Can't determine PhotometricInterpretation for color model: " + colorModel);
+ }
+
+ private short[] createColorMap(final IndexColorModel colorModel) {
+ // TIFF6.pdf p. 23:
+ // A TIFF color map is stored as type SHORT, count = 3 * (2^BitsPerSample)
+ // "In a TIFF ColorMap, all the Red values come first, followed by the Green values, then the Blue values.
+ // In the ColorMap, black is represented by 0,0,0 and white is represented by 65535, 65535, 65535."
+ short[] colorMap = new short[(int) (3 * Math.pow(2, colorModel.getPixelSize()))];
+
+ for (int i = 0; i < colorModel.getMapSize(); i++) {
+ int color = colorModel.getRGB(i);
+ colorMap[i ] = (short) upScale((color >> 16) & 0xff);
+ colorMap[i + colorMap.length / 3] = (short) upScale((color >> 8) & 0xff);
+ colorMap[i + 2 * colorMap.length / 3] = (short) upScale((color ) & 0xff);
+ }
+
+ return colorMap;
+ }
+
+ private int upScale(final int color) {
+ return 257 * color;
+ }
+
+ private short[] asShortArray(final int[] integers) {
+ short[] shorts = new short[integers.length];
+
+ for (int i = 0; i < shorts.length; i++) {
+ shorts[i] = (short) integers[i];
+ }
+
+ return shorts;
+ }
+
+ private void writeImageData(DataOutput stream, RenderedImage renderedImage, int numComponents, int[] bandOffsets, int[] bitOffsets) throws IOException {
+ // Store 3BYTE, 4BYTE as is (possibly need to re-arrange to RGB order)
+ // Store INT_RGB as 3BYTE, INT_ARGB as 4BYTE?, INT_ABGR must be re-arranged
+ // Store IndexColorModel as is
+ // Store BYTE_GRAY as is
+ // Store USHORT_GRAY as is
+
+ processImageStarted(0);
+
+ final int minTileY = renderedImage.getMinTileY();
+ final int maxYTiles = minTileY + renderedImage.getNumYTiles();
+ final int minTileX = renderedImage.getMinTileX();
+ final int maxXTiles = minTileX + renderedImage.getNumXTiles();
+
+ // Use buffer to have longer, better performing writes
+ final int tileHeight = renderedImage.getTileHeight();
+ final int tileWidth = renderedImage.getTileWidth();
+
+ // TODO: SampleSize may differ between bands/banks
+ int sampleSize = renderedImage.getSampleModel().getSampleSize(0);
+ final ByteBuffer buffer = ByteBuffer.allocate(tileWidth * renderedImage.getSampleModel().getNumBands() * sampleSize / 8);
+
+// System.err.println("tileWidth: " + tileWidth);
+
+ for (int yTile = minTileY; yTile < maxYTiles; yTile++) {
+ for (int xTile = minTileX; xTile < maxXTiles; xTile++) {
+ final Raster tile = renderedImage.getTile(xTile, yTile);
+ final DataBuffer dataBuffer = tile.getDataBuffer();
+ final int numBands = tile.getNumBands();
+// final SampleModel sampleModel = tile.getSampleModel();
+
+ switch (dataBuffer.getDataType()) {
+ case DataBuffer.TYPE_BYTE:
+
+// System.err.println("Writing " + numBands + "BYTE -> " + numBands + "BYTE");
+ for (int b = 0; b < dataBuffer.getNumBanks(); b++) {
+ for (int y = 0; y < tileHeight; y++) {
+ final int yOff = y * tileWidth * numBands;
+
+ for (int x = 0; x < tileWidth; x++) {
+ final int xOff = yOff + x * numBands;
+
+ for (int s = 0; s < numBands; s++) {
+ buffer.put((byte) (dataBuffer.getElem(b, xOff + bandOffsets[s]) & 0xff));
+ }
+ }
+
+ flushBuffer(buffer, stream);
+
+ if (stream instanceof DataOutputStream) {
+ DataOutputStream dataOutputStream = (DataOutputStream) stream;
+ dataOutputStream.flush();
+ }
+ }
+ }
+
+ break;
+
+ case DataBuffer.TYPE_USHORT:
+ case DataBuffer.TYPE_SHORT:
+ if (numComponents == 1) {
+ // TODO: This is foobar...
+// System.err.println("Writing USHORT -> " + numBands * 2 + "_BYTES");
+ for (int b = 0; b < dataBuffer.getNumBanks(); b++) {
+ for (int y = 0; y < tileHeight; y++) {
+ final int yOff = y * tileWidth;
+
+ for (int x = 0; x < tileWidth; x++) {
+ final int xOff = yOff + x;
+
+ buffer.putShort((short) (dataBuffer.getElem(b, xOff) & 0xffff));
+ }
+
+ flushBuffer(buffer, stream);
+
+ if (stream instanceof DataOutputStream) {
+ DataOutputStream dataOutputStream = (DataOutputStream) stream;
+ dataOutputStream.flush();
+ }
+ }
+ }
+ }
+ else {
+// for (int b = 0; b < dataBuffer.getNumBanks(); b++) {
+// for (int y = 0; y < tileHeight; y++) {
+// final int yOff = y * tileWidth;
+//
+// for (int x = 0; x < tileWidth; x++) {
+// final int xOff = yOff + x;
+// int element = dataBuffer.getElem(b, xOff);
+//
+// for (int s = 0; s < numBands; s++) {
+// buffer.put((byte) ((element >> bitOffsets[s]) & 0xff));
+// }
+// }
+//
+// flushBuffer(buffer, stream);
+// if (stream instanceof DataOutputStream) {
+// DataOutputStream dataOutputStream = (DataOutputStream) stream;
+// dataOutputStream.flush();
+// }
+// }
+// }
+ throw new IllegalArgumentException("Not implemented for data type: " + dataBuffer.getDataType());
+ }
+
+ break;
+
+ case DataBuffer.TYPE_INT:
+ // TODO: This is incorrect for 32 bits/sample, only works for packed (INT_(A)RGB)
+// System.err.println("Writing INT -> " + numBands + "_BYTES");
+ for (int b = 0; b < dataBuffer.getNumBanks(); b++) {
+ for (int y = 0; y < tileHeight; y++) {
+ final int yOff = y * tileWidth;
+
+ for (int x = 0; x < tileWidth; x++) {
+ final int xOff = yOff + x;
+ int element = dataBuffer.getElem(b, xOff);
+
+ for (int s = 0; s < numBands; s++) {
+ buffer.put((byte) ((element >> bitOffsets[s]) & 0xff));
+ }
+ }
+
+ flushBuffer(buffer, stream);
+ if (stream instanceof DataOutputStream) {
+ DataOutputStream dataOutputStream = (DataOutputStream) stream;
+ dataOutputStream.flush();
+ }
+ }
+ }
+
+ break;
+ default:
+ throw new IllegalArgumentException("Not implemented for data type: " + dataBuffer.getDataType());
+ }
+ }
+
+ // TODO: Need to flush/start new compression for each row, for proper LZW/PackBits/Deflate/ZLib
+ if (stream instanceof DataOutputStream) {
+ DataOutputStream dataOutputStream = (DataOutputStream) stream;
+ dataOutputStream.flush();
+ }
+
+ // TODO: Report better progress
+ processImageProgress((100f * yTile) / maxYTiles);
+ }
+
+ if (stream instanceof DataOutputStream) {
+ DataOutputStream dataOutputStream = (DataOutputStream) stream;
+ dataOutputStream.close();
+ }
+
+ processImageComplete();
+ }
+
+ // TODO: Would be better to solve this on stream level... But writers would then have to explicitly flush the buffer before done.
+ private void flushBuffer(final ByteBuffer buffer, final DataOutput stream) throws IOException {
+ buffer.flip();
+ stream.write(buffer.array(), buffer.arrayOffset(), buffer.remaining());
+
+ buffer.clear();
+ }
+
+ // Metadata
+
+ @Override
+ public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param) {
+ return null;
+ }
+
+ @Override
+ public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param) {
+ return null;
+ }
+
+ // Param
+
+ @Override
+ public ImageWriteParam getDefaultWriteParam() {
+ return new TIFFImageWriteParam();
+ }
+
+ // Test
+
+ public static void main(String[] args) throws IOException {
+ int argIdx = 0;
+
+ // TODO: Proper argument parsing: -t -c
+ int type = args.length > argIdx + 1 ? Integer.parseInt(args[argIdx++]) : -1;
+ int compression = args.length > argIdx + 1 ? Integer.parseInt(args[argIdx++]) : 0;
+
+ if (args.length <= argIdx) {
+ System.err.println("No file specified");
+ System.exit(1);
+ }
+
+ File file = new File(args[argIdx++]);
+
+ BufferedImage original;
+// BufferedImage original = ImageIO.read(file);
+ ImageInputStream inputStream = ImageIO.createImageInputStream(file);
+ try {
+ Iterator readers = ImageIO.getImageReaders(inputStream);
+
+ if (!readers.hasNext()) {
+ System.err.println("No reader for: " + file);
+ System.exit(1);
+ }
+
+ ImageReader reader = readers.next();
+ reader.setInput(inputStream);
+
+ ImageReadParam param = reader.getDefaultReadParam();
+ param.setDestinationType(reader.getRawImageType(0));
+
+ if (param.getDestinationType() == null) {
+ Iterator types = reader.getImageTypes(0);
+
+ while (types.hasNext()) {
+ ImageTypeSpecifier typeSpecifier = types.next();
+
+ if (typeSpecifier.getColorModel().getColorSpace().getType() == ColorSpace.TYPE_CMYK) {
+ param.setDestinationType(typeSpecifier);
+ }
+ }
+ }
+
+ System.err.println("param.getDestinationType(): " + param.getDestinationType());
+
+ original = reader.read(0, param);
+ }
+ finally {
+ inputStream.close();
+ }
+
+ System.err.println("original: " + original);
+
+// BufferedImage image = original;
+// BufferedImage image = new BufferedImage(original.getWidth(), original.getHeight(), BufferedImage.TYPE_INT_ARGB);
+// BufferedImage image = new BufferedImage(original.getWidth(), original.getHeight(), BufferedImage.TYPE_INT_RGB);
+// BufferedImage image = new BufferedImage(original.getWidth(), original.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
+// BufferedImage image = new BufferedImage(original.getWidth(), original.getHeight(), BufferedImage.TYPE_INT_BGR);
+// BufferedImage image = new BufferedImage(original.getWidth(), original.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
+ BufferedImage image;
+ if (type < 0 || type == original.getType()) {
+ image = original;
+ }
+ else if (type == BufferedImage.TYPE_BYTE_INDEXED) {
+// image = ImageUtil.createIndexed(original, 256, null, ImageUtil.COLOR_SELECTION_QUALITY | ImageUtil.DITHER_DIFFUSION_ALTSCANS);
+ image = ImageUtil.createIndexed(original, 256, null, ImageUtil.COLOR_SELECTION_FAST | ImageUtil.DITHER_DIFFUSION_ALTSCANS);
+ }
+ else {
+ image = new BufferedImage(original.getWidth(), original.getHeight(), type);
+ Graphics2D graphics = image.createGraphics();
+
+ try {
+ graphics.drawImage(original, 0, 0, null);
+ }
+ finally {
+ graphics.dispose();
+ }
+ }
+
+ original = null;
+
+ File output = File.createTempFile(file.getName().replace('.', '-'), ".tif");
+// output.deleteOnExit();
+
+ System.err.println("output: " + output);
+ TIFFImageWriter writer = new TIFFImageWriter(null);
+// ImageWriter writer = ImageIO.getImageWritersByFormatName("PNG").next();
+// ImageWriter writer = ImageIO.getImageWritersByFormatName("BMP").next();
+ ImageOutputStream stream = ImageIO.createImageOutputStream(output);
+
+ try {
+ writer.setOutput(stream);
+
+ ImageWriteParam param = writer.getDefaultWriteParam();
+ param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+// param.setCompressionType("None");
+// param.setCompressionType("PackBits");
+// param.setCompressionType("ZLib");
+ param.setCompressionType(param.getCompressionTypes()[compression]);
+// if (compression == 2) {
+// param.setCompressionQuality(0);
+// }
+ System.err.println("compression: " + param.getLocalizedCompressionTypeName());
+
+ long start = System.currentTimeMillis();
+ writer.write(null, new IIOImage(image, null, null), param);
+ System.err.println("Write time: " + (System.currentTimeMillis() - start) + " ms");
+ }
+ finally {
+ stream.close();
+ }
+
+ System.err.println("output.length: " + output.length());
+
+ // TODO: Support writing multipage TIFF
+// ImageOutputStream stream = ImageIO.createImageOutputStream(output);
+// try {
+// writer.setOutput(stream);
+// writer.prepareWriteSequence(null);
+// for(int i = 0; i < images.size(); i ++){
+// writer.writeToSequence(new IIOImage(images.get(i), null, null), null);
+// }
+// writer.endWriteSequence();
+// }
+// finally {
+// stream.close();
+// }
+// writer.dispose();
+
+
+ image = null;
+
+ BufferedImage read = ImageIO.read(output);
+ System.err.println("read: " + read);
+
+ TIFFImageReader.showIt(read, output.getName());
+ }
+
+}
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriterSpi.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriterSpi.java
new file mode 100644
index 00000000..7d85e568
--- /dev/null
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriterSpi.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2014, Harald Kuhr
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name "TwelveMonkeys" nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.twelvemonkeys.imageio.plugins.tiff;
+
+import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase;
+
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.ImageWriter;
+import java.io.IOException;
+import java.util.Locale;
+
+/**
+ * TIFFImageWriterSpi
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: TIFFImageWriterSpi.java,v 1.0 18.09.13 12:46 haraldk Exp$
+ */
+public final class TIFFImageWriterSpi extends ImageWriterSpiBase {
+ // TODO: Implement canEncodeImage better
+
+ public TIFFImageWriterSpi() {
+ super(new TIFFProviderInfo());
+ }
+
+ @Override
+ public boolean canEncodeImage(final ImageTypeSpecifier type) {
+ // TODO: Test bit depths compatibility
+
+ return true;
+ }
+
+ @Override
+ public ImageWriter createWriterInstance(final Object extension) throws IOException {
+ return new TIFFImageWriter(this);
+ }
+
+ @Override
+ public String getDescription(final Locale locale) {
+ return "Aldus/Adobe Tagged Image File Format (TIFF) image writer";
+ }
+}
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFProviderInfo.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFProviderInfo.java
new file mode 100644
index 00000000..c1e24e58
--- /dev/null
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFProviderInfo.java
@@ -0,0 +1,29 @@
+package com.twelvemonkeys.imageio.plugins.tiff;
+
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
+
+/**
+ * TIFFProviderInfo.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: TIFFProviderInfo.java,v 1.0 20/03/15 harald.kuhr Exp$
+ */
+final class TIFFProviderInfo extends ReaderWriterProviderInfo {
+ protected TIFFProviderInfo() {
+ super(
+ TIFFProviderInfo.class,
+ new String[] {"tiff", "TIFF"},
+ new String[] {"tif", "tiff"},
+ new String[] {
+ "image/tiff", "image/x-tiff"
+ },
+ "com.twelvemkonkeys.imageio.plugins.tiff.TIFFImageReader",
+ new String[] {"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReaderSpi"},
+ "com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriter",
+ new String[] {"com.twelvemkonkeys.imageio.plugins.tif.TIFFImageWriterSpi"},
+ false, null, null, null, null,
+ true, null, null, null, null
+ );
+ }
+}
diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/HorizontalDifferencingStreamTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/HorizontalDifferencingStreamTest.java
new file mode 100644
index 00000000..30fc6608
--- /dev/null
+++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/HorizontalDifferencingStreamTest.java
@@ -0,0 +1,574 @@
+/*
+ * Copyright (c) 2014, Harald Kuhr
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name "TwelveMonkeys" nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.twelvemonkeys.imageio.plugins.tiff;
+
+import com.twelvemonkeys.io.FastByteArrayOutputStream;
+import com.twelvemonkeys.io.LittleEndianDataInputStream;
+import com.twelvemonkeys.io.LittleEndianDataOutputStream;
+import org.junit.Test;
+
+import java.io.*;
+import java.nio.ByteOrder;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * HorizontalDifferencingStreamTest
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: HorizontalDifferencingStreamTest.java,v 1.0 02.12.13 09:50 haraldk Exp$
+ */
+public class HorizontalDifferencingStreamTest {
+
+ @Test
+ public void testWrite1SPP1BPS() throws IOException {
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ OutputStream stream = new HorizontalDifferencingStream(bytes, 24, 1, 1, ByteOrder.BIG_ENDIAN);
+
+ // Row 1
+ stream.write(0xff);
+ stream.write(0xff);
+ stream.write(0xff);
+
+ // Row 2
+ stream.write(0x5e);
+ stream.write(0x1e);
+ stream.write(0x78);
+
+
+ // 1 sample per pixel, 1 bits per sample (mono/indexed)
+ byte[] data = {
+ (byte) 0x80, 0x00, 0x00,
+ 0x71, 0x11, 0x44,
+ };
+
+ assertArrayEquals(data, bytes.toByteArray());
+ }
+
+ @Test
+ public void testWrite1SPP2BPS() throws IOException {
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ OutputStream stream = new HorizontalDifferencingStream(bytes, 16, 1, 2, ByteOrder.BIG_ENDIAN);
+
+ // Row 1
+ stream.write(0xff);
+ stream.write(0xff);
+ stream.write(0xff);
+ stream.write(0xff);
+
+ // Row 2
+ stream.write(0x41);
+ stream.write(0x6b);
+ stream.write(0x05);
+ stream.write(0x0f);
+
+ // 1 sample per pixel, 2 bits per sample (gray/indexed)
+ byte[] data = {
+ (byte) 0xc0, 0x00, 0x00, 0x00,
+ 0x71, 0x11, 0x44, (byte) 0xcc,
+ };
+
+ assertArrayEquals(data, bytes.toByteArray());
+ }
+
+ @Test
+ public void testWrite1SPP4BPS() throws IOException {
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ OutputStream stream = new HorizontalDifferencingStream(bytes, 8, 1, 4, ByteOrder.BIG_ENDIAN);
+
+ // Row 1
+ stream.write(0xff);
+ stream.write(0xff);
+ stream.write(0xff);
+ stream.write(0xff);
+
+ // Row 2
+ stream.write(0x77);
+ stream.write(0x89);
+ stream.write(0xd1);
+ stream.write(0xd9);
+
+ // Row 3
+ stream.write(0x00);
+ stream.write(0x01);
+ stream.write(0x22);
+ stream.write(0x00);
+
+ // 1 sample per pixel, 4 bits per sample (gray/indexed)
+ byte[] data = {
+ (byte) 0xf0, 0x00, 0x00, 0x00,
+ 0x70, 0x11, 0x44, (byte) 0xcc,
+ 0x00, 0x01, 0x10, (byte) 0xe0
+ };
+
+ assertArrayEquals(data, bytes.toByteArray());
+ }
+
+ @Test
+ public void testWrite1SPP8BPS() throws IOException {
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ OutputStream stream = new HorizontalDifferencingStream(bytes, 4, 1, 8, ByteOrder.BIG_ENDIAN);
+
+ // Row 1
+ stream.write(0xff);
+ stream.write(0xff);
+ stream.write(0xff);
+ stream.write(0xff);
+
+ // Row 2
+ stream.write(0x7f);
+ stream.write(0x80);
+ stream.write(0x84);
+ stream.write(0x80);
+
+ // Row 3
+ stream.write(0x00);
+ stream.write(0x7f);
+ stream.write(0xfe);
+ stream.write(0x7f);
+
+ // 1 sample per pixel, 8 bits per sample (gray/indexed)
+ byte[] data = {
+ (byte) 0xff, 0, 0, 0,
+ 0x7f, 1, 4, -4,
+ 0x00, 127, 127, -127
+ };
+
+ assertArrayEquals(data, bytes.toByteArray());
+ }
+
+ @Test
+ public void testWriteArray1SPP8BPS() throws IOException {
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+
+ OutputStream stream = new HorizontalDifferencingStream(bytes, 4, 1, 8, ByteOrder.BIG_ENDIAN);
+
+ stream.write(new byte[] {
+ (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ 0x7f, (byte) 0x80, (byte) 0x84, (byte) 0x80,
+ 0x00, 0x7f, (byte) 0xfe, 0x7f,
+ });
+
+ // 1 sample per pixel, 8 bits per sample (gray/indexed)
+ byte[] data = {
+ (byte) 0xff, 0, 0, 0,
+ 0x7f, 1, 4, -4,
+ 0x00, 127, 127, -127
+ };
+
+ assertArrayEquals(data, bytes.toByteArray());
+ }
+
+ @Test
+ public void testWrite1SPP32BPS() throws IOException {
+ // 1 sample per pixel, 32 bits per sample (gray)
+ FastByteArrayOutputStream bytes = new FastByteArrayOutputStream(16);
+ OutputStream out = new HorizontalDifferencingStream(bytes, 4, 1, 32, ByteOrder.BIG_ENDIAN);
+ DataOutput dataOut = new DataOutputStream(out);
+ dataOut.writeInt(0x00000000);
+ dataOut.writeInt(305419896);
+ dataOut.writeInt(305419896);
+ dataOut.writeInt(-610839792);
+
+ InputStream in = bytes.createInputStream();
+ DataInput dataIn = new DataInputStream(in);
+
+ // Row 1
+ assertEquals(0, dataIn.readInt());
+ assertEquals(305419896, dataIn.readInt());
+ assertEquals(0, dataIn.readInt());
+ assertEquals(-916259688, dataIn.readInt());
+
+ // EOF
+ assertEquals(-1, in.read());
+ }
+
+ @Test
+ public void testWrite1SPP32BPSLittleEndian() throws IOException {
+ // 1 sample per pixel, 32 bits per sample (gray)
+ FastByteArrayOutputStream bytes = new FastByteArrayOutputStream(16);
+ OutputStream out = new HorizontalDifferencingStream(bytes, 4, 1, 32, ByteOrder.LITTLE_ENDIAN);
+ DataOutput dataOut = new LittleEndianDataOutputStream(out);
+ dataOut.writeInt(0x00000000);
+ dataOut.writeInt(305419896);
+ dataOut.writeInt(305419896);
+ dataOut.writeInt(-610839792);
+
+ InputStream in = bytes.createInputStream();
+ DataInput dataIn = new LittleEndianDataInputStream(in);
+
+ // Row 1
+ assertEquals(0, dataIn.readInt());
+ assertEquals(305419896, dataIn.readInt());
+ assertEquals(0, dataIn.readInt());
+ assertEquals(-916259688, dataIn.readInt());
+
+ // EOF
+ assertEquals(-1, in.read());
+ }
+
+ @Test
+ public void testWrite1SPP64BPS() throws IOException {
+ // 1 sample per pixel, 64 bits per sample (gray)
+ FastByteArrayOutputStream bytes = new FastByteArrayOutputStream(32);
+
+ OutputStream out = new HorizontalDifferencingStream(bytes, 4, 1, 64, ByteOrder.BIG_ENDIAN);
+ DataOutput dataOut = new DataOutputStream(out);
+ dataOut.writeLong(0x00000000);
+ dataOut.writeLong(81985529216486895L);
+ dataOut.writeLong(81985529216486895L);
+ dataOut.writeLong(-163971058432973790L);
+
+ InputStream in = bytes.createInputStream();
+ DataInput dataIn = new DataInputStream(in);
+
+ // Row 1
+ assertEquals(0, dataIn.readLong());
+ assertEquals(81985529216486895L, dataIn.readLong());
+ assertEquals(0, dataIn.readLong());
+ assertEquals(-245956587649460685L, dataIn.readLong());
+
+ // EOF
+ assertEquals(-1, in.read());
+ }
+
+ @Test
+ public void testWrite1SPP64BPSLittleEndian() throws IOException {
+ // 1 sample per pixel, 64 bits per sample (gray)
+ FastByteArrayOutputStream bytes = new FastByteArrayOutputStream(32);
+
+ OutputStream out = new HorizontalDifferencingStream(bytes, 4, 1, 64, ByteOrder.LITTLE_ENDIAN);
+ DataOutput dataOut = new LittleEndianDataOutputStream(out);
+ dataOut.writeLong(0x00000000);
+ dataOut.writeLong(81985529216486895L);
+ dataOut.writeLong(81985529216486895L);
+ dataOut.writeLong(-163971058432973790L);
+
+ InputStream in = bytes.createInputStream();
+ DataInput dataIn = new LittleEndianDataInputStream(in);
+
+ // Row 1
+ assertEquals(0, dataIn.readLong());
+ assertEquals(81985529216486895L, dataIn.readLong());
+ assertEquals(0, dataIn.readLong());
+ assertEquals(-245956587649460685L, dataIn.readLong());
+
+ // EOF
+ assertEquals(-1, in.read());
+ }
+
+ @Test
+ public void testWrite3SPP8BPS() throws IOException {
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ OutputStream stream = new HorizontalDifferencingStream(bytes, 4, 3, 8, ByteOrder.BIG_ENDIAN);
+
+ // Row 1
+ stream.write(0xff);
+ stream.write(0x00);
+ stream.write(0x7f);
+
+ stream.write(0xfe);
+ stream.write(0xff);
+ stream.write(0x7e);
+
+ stream.write(0xfa);
+ stream.write(0xfb);
+ stream.write(0x7a);
+
+ stream.write(0xfe);
+ stream.write(0xff);
+ stream.write(0x7e);
+
+ // Row 2
+ stream.write(0x7f);
+ stream.write(0x7f);
+ stream.write(0x7f);
+
+ stream.write(0x80);
+ stream.write(0x80);
+ stream.write(0x80);
+
+ stream.write(0x84);
+ stream.write(0x84);
+ stream.write(0x84);
+
+ stream.write(0x80);
+ stream.write(0x80);
+ stream.write(0x80);
+
+ // Row 3
+ stream.write(0x00);
+ stream.write(0x00);
+ stream.write(0x00);
+
+ stream.write(0x7f);
+ stream.write(0x81);
+ stream.write(0x00);
+
+ stream.write(0x00);
+ stream.write(0x00);
+ stream.write(0x00);
+
+ stream.write(0x00);
+ stream.write(0x00);
+ stream.write(0x7f);
+
+ // 3 samples per pixel, 8 bits per sample (RGB)
+ byte[] data = {
+ (byte) 0xff, (byte) 0x00, (byte) 0x7f, -1, -1, -1, -4, -4, -4, 4, 4, 4,
+ 0x7f, 0x7f, 0x7f, 1, 1, 1, 4, 4, 4, -4, -4, -4,
+ 0x00, 0x00, 0x00, 127, -127, 0, -127, 127, 0, 0, 0, 127,
+ };
+
+ assertArrayEquals(data, bytes.toByteArray());
+
+ }
+
+ @Test
+ public void testWrite3SPP16BPS() throws IOException {
+ FastByteArrayOutputStream bytes = new FastByteArrayOutputStream(24);
+ OutputStream out = new HorizontalDifferencingStream(bytes, 4, 3, 16, ByteOrder.BIG_ENDIAN);
+
+ DataOutput dataOut = new DataOutputStream(out);
+ dataOut.writeShort(0x0000);
+ dataOut.writeShort(0x0000);
+ dataOut.writeShort(0x0000);
+ dataOut.writeShort(4660);
+ dataOut.writeShort(30292);
+ dataOut.writeShort(4660);
+ dataOut.writeShort(4660);
+ dataOut.writeShort(30292);
+ dataOut.writeShort(4660);
+ dataOut.writeShort(-9320);
+ dataOut.writeShort(-60584);
+ dataOut.writeShort(-9320);
+
+ dataOut.writeShort(0x0000);
+ dataOut.writeShort(0x0000);
+ dataOut.writeShort(0x0000);
+ dataOut.writeShort(30292);
+ dataOut.writeShort(30292);
+ dataOut.writeShort(30292);
+ dataOut.writeShort(30292);
+ dataOut.writeShort(30292);
+ dataOut.writeShort(30292);
+ dataOut.writeShort(-60584);
+ dataOut.writeShort(-60584);
+ dataOut.writeShort(-60584);
+
+ InputStream in = bytes.createInputStream();
+ DataInput dataIn = new DataInputStream(in);
+
+ // Row 1
+ assertEquals(0, dataIn.readUnsignedShort());
+ assertEquals(0, dataIn.readUnsignedShort());
+ assertEquals(0, dataIn.readUnsignedShort());
+ assertEquals(4660, dataIn.readUnsignedShort());
+ assertEquals(30292, dataIn.readUnsignedShort());
+ assertEquals(4660, dataIn.readUnsignedShort());
+ assertEquals(0, dataIn.readUnsignedShort());
+ assertEquals(0, dataIn.readUnsignedShort());
+ assertEquals(0, dataIn.readUnsignedShort());
+ assertEquals(51556, dataIn.readUnsignedShort());
+ assertEquals(40196, dataIn.readUnsignedShort());
+ assertEquals(51556, dataIn.readUnsignedShort());
+
+ // Row 2
+ assertEquals(0, dataIn.readUnsignedShort());
+ assertEquals(0, dataIn.readUnsignedShort());
+ assertEquals(0, dataIn.readUnsignedShort());
+ assertEquals(30292, dataIn.readUnsignedShort());
+ assertEquals(30292, dataIn.readUnsignedShort());
+ assertEquals(30292, dataIn.readUnsignedShort());
+ assertEquals(0, dataIn.readUnsignedShort());
+ assertEquals(0, dataIn.readUnsignedShort());
+ assertEquals(0, dataIn.readUnsignedShort());
+ assertEquals(40196, dataIn.readUnsignedShort());
+ assertEquals(40196, dataIn.readUnsignedShort());
+ assertEquals(40196, dataIn.readUnsignedShort());
+
+ // EOF
+ assertEquals(-1, in.read());
+ }
+
+ @Test
+ public void testWrite3SPP16BPSLittleEndian() throws IOException {
+ FastByteArrayOutputStream bytes = new FastByteArrayOutputStream(24);
+
+ OutputStream out = new HorizontalDifferencingStream(bytes, 4, 3, 16, ByteOrder.LITTLE_ENDIAN);
+ DataOutput dataOut = new LittleEndianDataOutputStream(out);
+ dataOut.writeShort(0x0000);
+ dataOut.writeShort(0x0000);
+ dataOut.writeShort(0x0000);
+ dataOut.writeShort(4660);
+ dataOut.writeShort(30292);
+ dataOut.writeShort(4660);
+ dataOut.writeShort(4660);
+ dataOut.writeShort(30292);
+ dataOut.writeShort(4660);
+ dataOut.writeShort(-9320);
+ dataOut.writeShort(-60584);
+ dataOut.writeShort(-9320);
+
+ dataOut.writeShort(0x0000);
+ dataOut.writeShort(0x0000);
+ dataOut.writeShort(0x0000);
+ dataOut.writeShort(30292);
+ dataOut.writeShort(30292);
+ dataOut.writeShort(30292);
+ dataOut.writeShort(30292);
+ dataOut.writeShort(30292);
+ dataOut.writeShort(30292);
+ dataOut.writeShort(-60584);
+ dataOut.writeShort(-60584);
+ dataOut.writeShort(-60584);
+
+ InputStream in = bytes.createInputStream();
+ DataInput dataIn = new LittleEndianDataInputStream(in);
+
+ // Row 1
+ assertEquals(0, dataIn.readUnsignedShort());
+ assertEquals(0, dataIn.readUnsignedShort());
+ assertEquals(0, dataIn.readUnsignedShort());
+ assertEquals(4660, dataIn.readUnsignedShort());
+ assertEquals(30292, dataIn.readUnsignedShort());
+ assertEquals(4660, dataIn.readUnsignedShort());
+ assertEquals(0, dataIn.readUnsignedShort());
+ assertEquals(0, dataIn.readUnsignedShort());
+ assertEquals(0, dataIn.readUnsignedShort());
+ assertEquals(51556, dataIn.readUnsignedShort());
+ assertEquals(40196, dataIn.readUnsignedShort());
+ assertEquals(51556, dataIn.readUnsignedShort());
+
+ // Row 2
+ assertEquals(0, dataIn.readUnsignedShort());
+ assertEquals(0, dataIn.readUnsignedShort());
+ assertEquals(0, dataIn.readUnsignedShort());
+ assertEquals(30292, dataIn.readUnsignedShort());
+ assertEquals(30292, dataIn.readUnsignedShort());
+ assertEquals(30292, dataIn.readUnsignedShort());
+ assertEquals(0, dataIn.readUnsignedShort());
+ assertEquals(0, dataIn.readUnsignedShort());
+ assertEquals(0, dataIn.readUnsignedShort());
+ assertEquals(40196, dataIn.readUnsignedShort());
+ assertEquals(40196, dataIn.readUnsignedShort());
+ assertEquals(40196, dataIn.readUnsignedShort());
+
+ // EOF
+ assertEquals(-1, in.read());
+ }
+
+ @Test
+ public void testWrite4SPP8BPS() throws IOException {
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ OutputStream stream = new HorizontalDifferencingStream(bytes, 4, 4, 8, ByteOrder.BIG_ENDIAN);
+
+ // Row 1
+ stream.write(0xff);
+ stream.write(0x00);
+ stream.write(0x7f);
+ stream.write(0x00);
+
+ stream.write(0xfe);
+ stream.write(0xff);
+ stream.write(0x7e);
+ stream.write(0xff);
+
+ stream.write(0xfa);
+ stream.write(0xfb);
+ stream.write(0x7a);
+ stream.write(0xfb);
+
+ stream.write(0xfe);
+ stream.write(0xff);
+ stream.write(0x7e);
+ stream.write(0xff);
+
+ // Row 2
+ stream.write(0x7f);
+ stream.write(0x7f);
+ stream.write(0x7f);
+ stream.write(0x7f);
+
+ stream.write(0x80);
+ stream.write(0x80);
+ stream.write(0x80);
+ stream.write(0x80);
+
+ stream.write(0x84);
+ stream.write(0x84);
+ stream.write(0x84);
+ stream.write(0x84);
+
+ stream.write(0x80);
+ stream.write(0x80);
+ stream.write(0x80);
+ stream.write(0x80);
+
+ // 4 samples per pixel, 8 bits per sample (RGBA)
+ byte[] data = {
+ (byte) 0xff, (byte) 0x00, (byte) 0x7f, 0x00, -1, -1, -1, -1, -4, -4, -4, -4, 4, 4, 4, 4,
+ 0x7f, 0x7f, 0x7f, 0x7f, 1, 1, 1, 1, 4, 4, 4, 4, -4, -4, -4, -4,
+ };
+
+ assertArrayEquals(data, bytes.toByteArray());
+ }
+
+ @Test
+ public void testWriteArray4SPP8BPS() throws IOException {
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ OutputStream stream = new HorizontalDifferencingStream(bytes, 4, 4, 8, ByteOrder.BIG_ENDIAN);
+
+ stream.write(
+ new byte[] {
+ (byte) 0xff, 0x00, 0x7f, 0x00,
+ (byte) 0xfe, (byte) 0xff, 0x7e, (byte) 0xff,
+ (byte) 0xfa, (byte) 0xfb, 0x7a, (byte) 0xfb,
+ (byte) 0xfe, (byte) 0xff, 0x7e, (byte) 0xff,
+
+ 0x7f, 0x7f, 0x7f, 0x7f,
+ (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
+ (byte) 0x84, (byte) 0x84, (byte) 0x84, (byte) 0x84,
+ (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
+ }
+ );
+
+ // 4 samples per pixel, 8 bits per sample (RGBA)
+ byte[] data = {
+ (byte) 0xff, (byte) 0x00, (byte) 0x7f, 0x00, -1, -1, -1, -1, -4, -4, -4, -4, 4, 4, 4, 4,
+ 0x7f, 0x7f, 0x7f, 0x7f, 1, 1, 1, 1, 4, 4, 4, 4, -4, -4, -4, -4,
+ };
+
+ assertArrayEquals(data, bytes.toByteArray());
+ }
+
+
+}
diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoderTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoderTest.java
index f826ec82..4cb9538a 100644
--- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoderTest.java
+++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoderTest.java
@@ -26,15 +26,18 @@ package com.twelvemonkeys.imageio.plugins.tiff;/*
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+import com.twelvemonkeys.io.FileUtil;
import com.twelvemonkeys.io.enc.Decoder;
import com.twelvemonkeys.io.enc.DecoderAbstractTestCase;
import com.twelvemonkeys.io.enc.DecoderStream;
import com.twelvemonkeys.io.enc.Encoder;
+import org.junit.Ignore;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.ByteBuffer;
import static org.junit.Assert.*;
@@ -47,6 +50,8 @@ import static org.junit.Assert.*;
*/
public class LZWDecoderTest extends DecoderAbstractTestCase {
+ public static final int SPEED_TEST_ITERATIONS = 1024;
+
@Test
public void testIsOldBitReversedStreamTrue() throws IOException {
assertTrue(LZWDecoder.isOldBitReversedStream(getClass().getResourceAsStream("/lzw/lzw-short.bin")));
@@ -78,13 +83,6 @@ public class LZWDecoderTest extends DecoderAbstractTestCase {
int data;
try {
-// long toSkip = 3800;
-// while ((toSkip -= expected.skip(toSkip)) > 0) {
-// }
-// toSkip = 3800;
-// while ((toSkip -= actual.skip(toSkip)) > 0) {
-// }
-
while ((data = actual.read()) != -1) {
count++;
@@ -106,7 +104,28 @@ public class LZWDecoderTest extends DecoderAbstractTestCase {
@Override
public Encoder createCompatibleEncoder() {
- // Don't have an encoder yet
+ // TODO: Need to know length of data to compress in advance...
return null;
}
+
+ @Ignore
+ @Test(timeout = 3000)
+ public void testSpeed() throws IOException {
+ byte[] bytes = FileUtil.read(getClass().getResourceAsStream("/lzw/lzw-long.bin"));
+
+
+ for (int i = 0; i < SPEED_TEST_ITERATIONS; i++) {
+ ByteBuffer buffer = ByteBuffer.allocate(1024);
+ ByteArrayInputStream input = new ByteArrayInputStream(bytes);
+ LZWDecoder decoder = new LZWDecoder.LZWSpecDecoder();
+
+ int read, total = 0;
+ while((read = decoder.decode(input, buffer)) > 0) {
+ buffer.clear();
+ total += read;
+ }
+
+ assertEquals(49152, total);
+ }
+ }
}
diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/LZWEncoderTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/LZWEncoderTest.java
new file mode 100644
index 00000000..ced15e6d
--- /dev/null
+++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/LZWEncoderTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2014, Harald Kuhr
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name "TwelveMonkeys" nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.twelvemonkeys.imageio.plugins.tiff;
+
+import com.twelvemonkeys.io.FastByteArrayOutputStream;
+import com.twelvemonkeys.io.enc.Decoder;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.Random;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * LZWEncoderTest
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: LZWEncoderTest.java,v 1.0 06.12.13 13:48 haraldk Exp$
+ */
+public class LZWEncoderTest {
+
+ static final int SPEED_TEST_RUNS = 1024;
+ static final int LENGTH = 1024;
+ static final int ITERATIONS = 4;
+
+ private final Random random = new Random(2451348571893475l);
+
+ @Test
+ public void testExample() throws IOException {
+ byte[] bytes = new byte[] {7, 7, 7, 8, 8, 7, 7, 6, 6};
+ LZWEncoder encoder = new LZWEncoder(bytes.length);
+
+ OutputStream stream = new FastByteArrayOutputStream(10);
+ encoder.encode(stream, ByteBuffer.wrap(bytes));
+ }
+
+ @Test
+ public void testExampleEncodeDecode() throws IOException {
+ byte[] bytes = new byte[] {7, 7, 7, 8, 8, 7, 7, 6, 6};
+ LZWEncoder encoder = new LZWEncoder(bytes.length);
+
+ FastByteArrayOutputStream stream = new FastByteArrayOutputStream(10);
+ encoder.encode(stream, ByteBuffer.wrap(bytes));
+
+ ByteArrayInputStream inputStream = stream.createInputStream();
+ Decoder decoder = LZWDecoder.create(false);
+ ByteBuffer buffer = ByteBuffer.allocate(bytes.length);
+ int index = 0;
+
+ while (decoder.decode(inputStream, buffer) > 0) {
+ buffer.flip();
+
+ while (buffer.hasRemaining()) {
+ assertEquals(String.format("Diff at index %s", index), bytes[index], buffer.get());
+ index++;
+ }
+
+ buffer.clear();
+ }
+
+ assertEquals(9, index);
+ assertEquals(-1, inputStream.read());
+ }
+
+ @Test
+ public void testEncodeDecode() throws IOException {
+ byte[] bytes = new byte[LENGTH];
+ LZWEncoder encoder = new LZWEncoder(bytes.length);
+
+ for (int i = 0; i < bytes.length; i++) {
+ bytes[i] = (byte) i;
+ }
+
+ FastByteArrayOutputStream stream = new FastByteArrayOutputStream((LENGTH * 3) / 4);
+
+ for (int i = 0; i < ITERATIONS; i++) {
+ encoder.encode(stream, ByteBuffer.wrap(bytes, i * LENGTH / ITERATIONS, LENGTH / ITERATIONS));
+ }
+
+ ByteArrayInputStream inputStream = stream.createInputStream();
+ LZWDecoder decoder = new LZWDecoder.LZWSpecDecoder(); // Strict mode
+ ByteBuffer buffer = ByteBuffer.allocate(LENGTH / ITERATIONS);
+
+ int index = 0;
+
+ for (int i = 0; i < ITERATIONS; i++) {
+ while (decoder.decode(inputStream, buffer) > 0) {
+ buffer.flip();
+
+ while (buffer.hasRemaining()) {
+ byte expected = bytes[index];
+ byte actual = buffer.get();
+ assertEquals(String.format("Diff at index %s: 0x%02x != 0x%02x", index, expected, actual), expected, actual);
+ index++;
+ }
+
+ buffer.clear();
+ }
+ }
+
+ assertEquals(LENGTH, index);
+ assertEquals(-1, inputStream.read());
+ }
+
+ @Test
+ public void testEncodeDecodeRandom() throws IOException {
+ byte[] bytes = new byte[LENGTH];
+ LZWEncoder encoder = new LZWEncoder(bytes.length);
+
+ random.nextBytes(bytes);
+
+ FastByteArrayOutputStream stream = new FastByteArrayOutputStream((LENGTH * 3) / 4);
+
+ for (int i = 0; i < ITERATIONS; i++) {
+ encoder.encode(stream, ByteBuffer.wrap(bytes, i * LENGTH / ITERATIONS, LENGTH / ITERATIONS));
+ }
+
+ ByteArrayInputStream inputStream = stream.createInputStream();
+ LZWDecoder decoder = new LZWDecoder.LZWSpecDecoder(); // Strict mode
+ ByteBuffer buffer = ByteBuffer.allocate(LENGTH / ITERATIONS);
+
+ int index = 0;
+
+ for (int i = 0; i < ITERATIONS; i++) {
+ while (decoder.decode(inputStream, buffer) > 0) {
+ buffer.flip();
+
+ while (buffer.hasRemaining()) {
+ byte expected = bytes[index];
+ byte actual = buffer.get();
+ assertEquals(String.format("Diff at index %s: 0x%02x != 0x%02x", index, expected, actual), expected, actual);
+// System.err.println(String.format("Equal at index %s: 0x%02x (%d)", index, expected & 0xff, expected));
+ index++;
+ }
+
+ buffer.clear();
+ }
+ }
+
+ assertEquals(LENGTH, index);
+ assertEquals(-1, inputStream.read());
+ }
+
+ @Ignore
+ @Test(timeout = 10000)
+ public void testSpeed() throws IOException {
+ for (int run = 0; run < SPEED_TEST_RUNS; run++) {
+ byte[] bytes = new byte[LENGTH];
+ LZWEncoder encoder = new LZWEncoder(bytes.length);
+
+ for (int i = 0; i < bytes.length; i++) {
+ bytes[i] = (byte) i;
+ }
+
+ FastByteArrayOutputStream stream = new FastByteArrayOutputStream((LENGTH * 3) / 4);
+
+ for (int i = 0; i < ITERATIONS; i++) {
+ encoder.encode(stream, ByteBuffer.wrap(bytes, i * LENGTH / ITERATIONS, LENGTH / ITERATIONS));
+ }
+
+ assertEquals(719, stream.size());
+ }
+ }
+}
diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriterTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriterTest.java
new file mode 100644
index 00000000..8c42c5a9
--- /dev/null
+++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriterTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2014, Harald Kuhr
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name "TwelveMonkeys" nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.twelvemonkeys.imageio.plugins.tiff;
+
+import com.twelvemonkeys.imageio.util.ImageWriterAbstractTestCase;
+
+import javax.imageio.ImageWriter;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * TIFFImageWriterTest
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: TIFFImageWriterTest.java,v 1.0 19.09.13 13:22 haraldk Exp$
+ */
+public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
+
+ public static final TIFFImageWriterSpi PROVIDER = new TIFFImageWriterSpi();
+
+ @Override
+ protected ImageWriter createImageWriter() {
+ return new TIFFImageWriter(PROVIDER);
+ }
+
+ @Override
+ protected List extends RenderedImage> getTestData() {
+ BufferedImage image = new BufferedImage(300, 200, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D graphics = image.createGraphics();
+ try {
+ graphics.setColor(Color.RED);
+ graphics.fillRect(0, 0, 100, 200);
+ graphics.setColor(Color.BLUE);
+ graphics.fillRect(100, 0, 100, 200);
+ graphics.clearRect(200, 0, 100, 200);
+ }
+ finally {
+ graphics.dispose();
+ }
+
+ return Arrays.asList(image);
+ }
+}
diff --git a/pom.xml b/pom.xml
index 55f4be05..db1f544c 100755
--- a/pom.xml
+++ b/pom.xml
@@ -130,8 +130,8 @@
2.3.2
true
- 1.5
- 1.5
+ 1.7
+ 1.7
false
-g:lines
@@ -160,8 +160,8 @@
true
2.2
- 1.5
- 1.5
+ 1.7
+ 1.7
true
@@ -206,7 +206,7 @@
maven-pmd-plugin
3.0.1
- 1.5
+ 1.7
diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/MappedImageFactory.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/MappedImageFactory.java
index 4f8cc7c0..2dd4ac58 100644
--- a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/MappedImageFactory.java
+++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/image/MappedImageFactory.java
@@ -28,8 +28,11 @@
package com.twelvemonkeys.image;
+import com.twelvemonkeys.lang.Validate;
+
import javax.imageio.ImageTypeSpecifier;
import java.awt.*;
+import java.awt.color.ColorSpace;
import java.awt.image.*;
import java.io.IOException;
import java.lang.reflect.Constructor;
@@ -53,6 +56,22 @@ public final class MappedImageFactory {
// - Might be possible (but slow) to copy parts to memory and do CCOp on these copies
private static final boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.image.mapped.debug"));
+
+ /* Constants for DirectColorModel masks, from BufferedImage. */
+ private static final int DCM_RED_MASK = 0x00ff0000;
+ private static final int DCM_GREEN_MASK = 0x0000ff00;
+ private static final int DCM_BLUE_MASK = 0x000000ff;
+ private static final int DCM_ALPHA_MASK = 0xff000000;
+ private static final int DCM_565_RED_MASK = 0xf800;
+ private static final int DCM_565_GRN_MASK = 0x07E0;
+ private static final int DCM_565_BLU_MASK = 0x001F;
+ private static final int DCM_555_RED_MASK = 0x7C00;
+ private static final int DCM_555_GRN_MASK = 0x03E0;
+ private static final int DCM_555_BLU_MASK = 0x001F;
+ private static final int DCM_BGR_RED_MASK = 0x0000ff;
+ private static final int DCM_BGR_GRN_MASK = 0x00ff00;
+ private static final int DCM_BGR_BLU_MASK = 0xff0000;
+
static final RasterFactory RASTER_FACTORY = createRasterFactory();
private MappedImageFactory() {}
@@ -80,6 +99,148 @@ public final class MappedImageFactory {
return new BufferedImage(cm, RASTER_FACTORY.createRaster(sm, buffer, new Point()), cm.isAlphaPremultiplied(), null);
}
+ /**
+ *
+ * Returns the {@code BufferedImage} image type that is compatible with the data in {@code image}.
+ * This method will return compatible types, even if {@code BufferedImage.getType()} returns
+ * {@code BufferedImage.TYPE_CUSTOM}.
+ *
+ *
+ * This method is defined to work so that, for any valid {@code BufferedImage} type
+ * (except {@code BufferedImage.TYPE_CUSTOM}), the following is {@code true}:
+ *
+ * {@code getCompatibleBufferedImageType(createCompatibleMappedImage(w, h, type)) == type}
+ *
+ *
+ * If no standard type is compatible with the image data, {@code BufferedImage.TYPE_CUSTOM} is returned.
+ *
+ *
+ * @param image the image to test, may not be {@code null}.
+ *
+ * @return the {@code BufferedImage} type.
+ *
+ * @throws java.lang.IllegalArgumentException if {@code image} is {@code null}.
+ *
+ * @see java.awt.image.BufferedImage#getType()
+ */
+ public static int getCompatibleBufferedImageType(final BufferedImage image) {
+ Validate.notNull(image, "image");
+
+ WritableRaster raster = image.getRaster();
+ SampleModel sm = raster.getSampleModel();
+ int numBands = raster.getNumBands();
+
+ ColorModel cm = image.getColorModel();
+ ColorSpace cs = cm.getColorSpace();
+ boolean isAlphaPre = cm.isAlphaPremultiplied();
+ int csType = cs.getType();
+
+ int dataType = raster.getDataBuffer().getDataType();
+
+ if (csType != ColorSpace.TYPE_RGB) {
+ if (csType == ColorSpace.TYPE_GRAY && cm instanceof ComponentColorModel) {
+ if (sm instanceof ComponentSampleModel && ((ComponentSampleModel) sm).getPixelStride() != numBands) {
+ return BufferedImage.TYPE_CUSTOM;
+ }
+ else if (dataType == DataBuffer.TYPE_BYTE && raster.getNumBands() == 1 &&
+ cm.getComponentSize(0) == 8 && ((ComponentSampleModel) sm).getPixelStride() == 1) {
+ return BufferedImage.TYPE_BYTE_GRAY;
+ }
+ else if (dataType == DataBuffer.TYPE_USHORT && raster.getNumBands() == 1 &&
+ cm.getComponentSize(0) == 16 && ((ComponentSampleModel) sm).getPixelStride() == 1) {
+ return BufferedImage.TYPE_USHORT_GRAY;
+ }
+ }
+ else {
+ return BufferedImage.TYPE_CUSTOM;
+ }
+ }
+
+ if ((dataType == DataBuffer.TYPE_INT) && (numBands == 3 || numBands == 4)) {
+ // Check if the raster params and the color model are correct
+ int pixSize = cm.getPixelSize();
+
+ if (cm instanceof DirectColorModel && sm.getNumDataElements() == 1 && (pixSize == 32 || pixSize == 24)) {
+ // Now check on the DirectColorModel params
+ DirectColorModel dcm = (DirectColorModel) cm;
+ int rmask = dcm.getRedMask();
+ int gmask = dcm.getGreenMask();
+ int bmask = dcm.getBlueMask();
+
+ if (rmask == DCM_RED_MASK && gmask == DCM_GREEN_MASK && bmask == DCM_BLUE_MASK) {
+ if (dcm.getAlphaMask() == DCM_ALPHA_MASK) {
+ return isAlphaPre ? BufferedImage.TYPE_INT_ARGB_PRE : BufferedImage.TYPE_INT_ARGB;
+ }
+ else if (!dcm.hasAlpha()) {
+ // No Alpha
+ return BufferedImage.TYPE_INT_RGB;
+ }
+ }
+ else if (rmask == DCM_BGR_RED_MASK && gmask == DCM_BGR_GRN_MASK && bmask == DCM_BGR_BLU_MASK) {
+ if (!dcm.hasAlpha()) {
+ return BufferedImage.TYPE_INT_BGR;
+ }
+ }
+ }
+ }
+ else if ((cm instanceof IndexColorModel) && (numBands == 1) && (!cm.hasAlpha() || !isAlphaPre)) {
+ IndexColorModel icm = (IndexColorModel) cm;
+ int pixSize = icm.getPixelSize();
+
+ if (dataType == DataBuffer.TYPE_BYTE && sm instanceof MultiPixelPackedSampleModel) {
+ return BufferedImage.TYPE_BYTE_BINARY;
+ }
+ if (dataType == DataBuffer.TYPE_BYTE && sm instanceof ComponentSampleModel) {
+ ComponentSampleModel csm = (ComponentSampleModel) sm;
+
+ if (csm.getPixelStride() == 1 && pixSize <= 8) {
+ return BufferedImage.TYPE_BYTE_INDEXED;
+ }
+ }
+ }
+ else if ((dataType == DataBuffer.TYPE_USHORT) &&
+ (cm instanceof DirectColorModel) && (numBands == 3) && !cm.hasAlpha()) {
+ DirectColorModel dcm = (DirectColorModel) cm;
+
+ if (dcm.getRedMask() == DCM_565_RED_MASK &&
+ dcm.getGreenMask() == DCM_565_GRN_MASK && dcm.getBlueMask() == DCM_565_BLU_MASK) {
+ return BufferedImage.TYPE_USHORT_565_RGB;
+ }
+ else if (dcm.getRedMask() == DCM_555_RED_MASK &&
+ dcm.getGreenMask() == DCM_555_GRN_MASK && dcm.getBlueMask() == DCM_555_BLU_MASK) {
+ return BufferedImage.TYPE_USHORT_555_RGB;
+ }
+ }
+ else if (dataType == DataBuffer.TYPE_BYTE && cm instanceof ComponentColorModel &&
+ raster.getSampleModel() instanceof PixelInterleavedSampleModel && (numBands == 3 || numBands == 4)) {
+ ComponentColorModel ccm = (ComponentColorModel) cm;
+ PixelInterleavedSampleModel csm = (PixelInterleavedSampleModel) raster.getSampleModel();
+
+ int[] offs = csm.getBandOffsets();
+ int[] nBits = ccm.getComponentSize();
+ boolean is8bit = true;
+
+ for (int i = 0; i < numBands; i++) {
+ if (nBits[i] != 8) {
+ is8bit = false;
+ break;
+ }
+ }
+
+ if (is8bit && csm.getPixelStride() == numBands &&
+ offs[0] == numBands - 1 && offs[1] == numBands - 2 && offs[2] == numBands - 3) {
+ if (numBands == 3 && !ccm.hasAlpha()) {
+ return BufferedImage.TYPE_3BYTE_BGR;
+ }
+ else if (offs[3] == 0 && ccm.hasAlpha()) {
+ return isAlphaPre ? BufferedImage.TYPE_4BYTE_ABGR_PRE : BufferedImage.TYPE_4BYTE_ABGR;
+ }
+ }
+ }
+
+ return BufferedImage.TYPE_CUSTOM;
+ }
+
private static RasterFactory createRasterFactory() {
try {
// Try to instantiate, will throw LinkageError if it fails
diff --git a/sandbox/sandbox-common/src/test/java/com/twelvemonkeys/image/MappedImageFactoryTest.java b/sandbox/sandbox-common/src/test/java/com/twelvemonkeys/image/MappedImageFactoryTest.java
new file mode 100644
index 00000000..59b7ad5e
--- /dev/null
+++ b/sandbox/sandbox-common/src/test/java/com/twelvemonkeys/image/MappedImageFactoryTest.java
@@ -0,0 +1,26 @@
+package com.twelvemonkeys.image;
+
+import org.junit.Test;
+
+import java.awt.image.BufferedImage;
+
+import static com.twelvemonkeys.image.MappedImageFactory.createCompatibleMappedImage;
+import static com.twelvemonkeys.image.MappedImageFactory.getCompatibleBufferedImageType;
+import static org.junit.Assert.assertEquals;
+
+public class MappedImageFactoryTest {
+
+ @Test
+ public void testGetCompatibleBufferedImageTypeFromBufferedImage() throws Exception {
+ for (int type = BufferedImage.TYPE_INT_RGB; type <= BufferedImage.TYPE_BYTE_INDEXED; type++) { // 1 - 13
+ assertEquals(type, getCompatibleBufferedImageType(new BufferedImage(1, 1, type)));
+ }
+ }
+
+ @Test
+ public void testGetCompatibleBufferedImageType() throws Exception {
+ for (int type = BufferedImage.TYPE_INT_RGB; type <= BufferedImage.TYPE_BYTE_INDEXED; type++) { // 1 - 13
+ assertEquals(type, getCompatibleBufferedImageType(createCompatibleMappedImage(1, 1, type)));
+ }
+ }
+}
\ No newline at end of file