From d2d7569a7f2f79ebad8d524874a0614e29422142 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Mon, 23 Mar 2015 10:28:58 +0100 Subject: [PATCH] TMI-115: Now downsamples and replaces 16 bit DQTs with 8 bit variants. --- .../jpeg/JPEGSegmentImageInputStream.java | 56 ++++++++++++++++++ .../plugins/jpeg/JPEGImageReaderTest.java | 3 +- .../test/resources/jpeg/jfif-16bit-dqt.jpg | Bin 0 -> 4204 bytes 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 imageio/imageio-jpeg/src/test/resources/jpeg/jfif-16bit-dqt.jpg diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStream.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStream.java index 0b26ccfd..a5f11ecb 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStream.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStream.java @@ -37,6 +37,7 @@ import java.io.EOFException; import java.io.IOException; import java.nio.charset.Charset; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import static com.twelvemonkeys.lang.Validate.notNull; @@ -149,6 +150,19 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl { // Need to rewrite this segment, so that it gets length 16 and discard the remaining bytes... segment = new AdobeAPP14Replacement(realPosition, segment.end(), length, stream); } + else if (marker == JPEG.DQT) { + // TODO: Do we need to know SOF precision before determining if the DQT precision is bad? + // Inspect segment, see if we have 16 bit precision (assuming segments will not contain + // multiple quality tables with varying precision) + int qtInfo = stream.read(); + if ((qtInfo & 0x10) == 0x10) { + // TODO: Warning! + segment = new DownsampledDQTReplacement(realPosition, segment.end(), length, qtInfo, stream); + } + else { + segment = new Segment(marker, realPosition, segment.end(), length); + } + } else { segment = new Segment(marker, realPosition, segment.end(), length); } @@ -375,6 +389,48 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl { } } + /** + * Workaround for a known bug in com.sun.imageio.plugins.jpeg.DQTMarkerSegment, throwing exception, + * if the DQT precision is 16 bits (not 8 bits). Native reader seems to cope fine though. + * This downsampling of the quality tables, creates visually same results, with no exceptions thrown. + */ + static final class DownsampledDQTReplacement extends ReplacementSegment { + + DownsampledDQTReplacement(final long realStart, final long start, final long realLength, final int qtInfo, final ImageInputStream stream) throws IOException { + super(JPEG.DQT, realStart, start, realLength, createMarkerFixedLength((int) realLength, qtInfo, stream)); + } + + private static byte[] createMarkerFixedLength(final int length, final int qtInfo, final ImageInputStream stream) throws IOException { + byte[] replacementData = new byte[length]; + + int numQTs = length / 128; + int newSegmentLength = 2 + 1 + 64 * numQTs; + + replacementData[0] = (byte) ((JPEG.DQT >> 8) & 0xff); + replacementData[1] = (byte) (JPEG.DQT & 0xff); + replacementData[2] = (byte) ((newSegmentLength >> 8) & 0xff); + replacementData[3] = (byte) (newSegmentLength & 0xff); + replacementData[4] = (byte) (qtInfo & 0x0f); + stream.readFully(replacementData, 5, replacementData.length - 5); + + // Downsample tables to 8 bits by discarding lower 8 bits... + int newOff = 4; + int oldOff = 4; + for (int q = 0; q < numQTs; q++) { + replacementData[newOff++] = (byte) (replacementData[oldOff++] & 0x0f); + + for (int i = 0; i < 64; i++) { + replacementData[newOff + i] = replacementData[oldOff + 1 + i * 2]; + } + + newOff += 64; + oldOff += 128; + } + + return Arrays.copyOfRange(replacementData, 0, newSegmentLength + 2); + } + } + static class ReplacementSegment extends Segment { final long realLength; final byte[] data; diff --git a/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java b/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java index 80c7d33e..3ab02410 100644 --- a/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java +++ b/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java @@ -91,7 +91,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCaseaz=Y6fO>-=)g!PLP5fU>f%v;aU51bWy69Lxd? z6rco%15$u2n=x!%7QnIjD4@#LH31XA4=`iz$Jk^7L;!ApCt$7xJ;3WY*oFgO&#`L74aj)S75VH$)(4mi({G(pXXV)zkL$IX+E+O#eU z$(Wr_ANj_P6lRZ~eF6WyKnRp=kQVqChq4)j|DP2UfJkeg37$}#rfI~%48Y6ohFwK~ zJtSLAxC-AwEyEA~R(j9oR}uPr4%m*LhYP!kxDg+zf-o<)b82YvF#R9h#I8kHe_|W{ zkPee;disZxy3W&xNA~=yBK=%DPAUcin&38}IOTBe7T)^{5*AFiuJ3EX|Akr#xo@xZ z%Q6+VRV`|}Z8wx-uGabr&zTVg%B1{?os*&jrWHQ#g|f)MJ>lpha3Gt|UPRuvLM$gF zF3)q=n^wKC3Zzzsz*MT3!eL_Y7mDvD90L0gSCHW!@PD;(YVUf&!+7ZsPVH=ox1<{B zS#L<(Rrj(`!#yR2#u8(Xo1W4izACu5M7Ju+TM*5JGg6J$83hHe5r>zEHu2&gAcbk& zB}G%J>AH#0ua0EydK7r*%dqJbn424+!zs{&)>5Qxi8#Y$@O^j%kvxia1p> z?o}&b*gH=2-QlyCI~xr18i5q7G+in1$t58luj{S`S@tWcD<-Rj@+1G#!{-?*uIqOMr+*sh}yOfzzoIJrOU`fr=%ust}EqBdo}T z*G3KWpu$bMm`^mmsU3>!+Fysq32XqU;%8EDH@1FvC^G^L7CAk5i4Z&yb z*KLMLKU6m`m#yV!03)_>azdCenY@Vd;_^D+qEnfIuBAFd_D1=R6fZw(F7fm4V<{gU zV@b4J&PLaC#HdVWp_|e(?yT6RK75auveIcH%&-H&V;MUky^~}`RSyLe5Rcxpar-au zxZ}Q@8Uq$r?u`lg^91l7iW=eIdXGB{MxSsbV>iG5M($pAG5ln2w>tF$wJ7zGSCQK} z=`&TzZQ;1as&`PC-jZ*u=l1I1kUE`aX4*F2uOf>Y=Uy|N`+6yqdSxSdL#Gv1aSsYu z3^?X>IfsPtudO1f;t&fJ=f8~CIXR8ZACkEd^mwo30`7V7gaq~FuL_;`^76O62}f9pffte5OjaH2Y=n+dQ6*8E)ppm`$jZhdSSi8feRyBW!w z3OR+q8CJB6OT)fyhk&5)hBfZFoHiBsa zC}LZ5!&Y7j<#d_{Gue7Vtfm5NV${>?D>ODp8!jjGyvVCXLtMKz)xt`7HqGlz%tUG0 z?<^mBTbSi?&!V+wrt=}a#lE6zDHn7)uJ$|6)IEr8qE(v=9-gbxc?h;=+?@M*vOFNA zcJ#bVd(!OaijvyhXyf`K?E>Bh=2hLwGoQxSCqjQt-B5JCrX&=N+D4N)HY)sQM^6Sc zA5Gs9pZB*=U6XXIv@DLAmzh`6*~vgwRtJ|@zdRaAEVk|}yg1gkqbier+1s~nvyBGqXK5rq&tX(nGPaTrW3&-#wOp{UsQ@7_Y|!qK~fmXRS>GxUGs30Gc&6(5*yIEG_Ke1+8uYc zzElj+x^#+Sf!I>eZ^mF92Cz_W9sV(mIu0hzl?%9U?_aU$+q@8@Yvw)KA|Z)gDCzR- zMb;gLmIiRH`!n}Gj>Ayy$(oH57Ch@sXmY4hX?0ut@g)Itjd{*8ijg5)= zctmDQt$0YG9>0Ear@84z(&QNuY)`5dYFNbN42e1fa-fE-8|>l^fH3OmiHZPnVaBxe zsxPmoNQ#a?xWG%7a?`Wl9Zh%msP2X{HgN?)%$v#?q+`9w0ST0lq4d>P)h45drkx!s zv73xSa_YmHP(nnAe2>a(67ds<13y2db)YP1(mMcd9?iPabY1i;1v9ei5vS}xXwMKQ zWUDt=@@Vb)l_VUaoko7ktT+?qG}9aLi! z3E?ODPu7Y2l5J}2h^i-DzmxmX04DEy4u=6Q&% z^~hVlQ}NZj#p-BdLgAFu>*9+2sv3@wp|E5ZEaNxfjY0m#RmX%oF^@W%Fh_8Q=MpB; z_X4rj#T+dsy^rBFMuD%a6~nrBFpN0b#i|28t(&G$g5y94w7;Jzfvg=r&or@XZ+5y- zl23?5Q~sc9H50_G$z38cydTa_#)e3djQzP;@^D`K<{Cqzm0+tu9|j25<@<|X8S8B? zDNV$#&AeqCfhB*^8$aW%NtXXDSM53)j$beo6o?8uZFW`jo=7Mn6{$OqG9OUkuzJIP z=EH*A=_Na!EBji&q;3tB7E`!r;1>0LNo2&gOBWj5IV_~mSSA{m>D_*B2Je{6an=3N zkgLDemLi>+CR2O4?wjJhU6$o9l~3nlIKmY5FW$-~duVizj9rR05c zj}N5W@YHrXo@f!my=LmV7%6i@f$7M*{eiaspf+?9;Nj};4x_%c{Mo{f+$2wtuYS9R znN7@(*iz0ok2L5mQ_8UyNw)fYJK27PrDP_|5f?p_`FWC@AfdQD_2Z1ApSz#fY>xY- zL_4!-qZ1M%iZ|koaPSVd(3SL7mU@8sOoJg^m~5q$$=ErU?P@u0X;-uH`wsGQbvchTIp2p-`H%~66Bc;7h$zpy}HtiX8*u*i>}UpqN9NDAP{@4K9A<(hBU zTmw7Do-C+6J?4`gP~&iK%J${!DIxs>AgiuWEz{u?{V@`%S!qHxB3e>kb$}vCS%-mj zOjpKDuA~@me=C};Q2=wo>psSnl7G$J?HhAK@hK)-Te|@5qWr1Mr>45Bjr~0EG0LhG z-%%AZ=}--MQreJL``i6_vq`1ZbkpJ|vygKU~`o>!^3H95sFHe@O!UGNpC>A`b(MC`p1vH+BTU6>@*^z*8wjxJa7raj0d{OnQI)0q_C+HtX-0IAm zXN~@@({wlT`V!Zk4}BUb6E8Q%|8QLDMYQH8>q5UKVz|#0iPQ7IEu+g`W!v8gieptD z^IezZ)R9)uO3R_^6(0%MIkF(+v(ec;*Y;HnRdiK_ebowpF-w(6^ag2S?g6gui_n+ZiXTGFTVm)O&`b$l_zQ2?YrQz@WMe-rzEO1xk4u^#C9ceNQnw3+_ z_T{{$StM^m!Z;FAa>2fGodDzVNEE(+Ljn9Zu6ZpEiivU+l?6) zR7@Eb+)ZdUmPpm2WQKb-pX0aisgTvgJT}dpkI=+A7F*$7=JHi3oa7+FDv16A7@sSU zTs{}mEECn%Et{B$@64|hoA~P0lYd%%$q^lkt$)mJvoq=3c1g<3|)BTzvpxS6#r{jbNt`Pa+~lNb_V;=3Y$EtiCNn*+W$C2(wO_R aw=>{0W)kzSX}3t0_5nbB@H0yCVERA79t(&7 literal 0 HcmV?d00001