1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.commons.imaging.formats.jpeg.decoder;
17
18 import static org.apache.commons.imaging.common.BinaryFunctions.read2Bytes;
19 import static org.apache.commons.imaging.common.BinaryFunctions.readBytes;
20
21 import java.awt.image.BufferedImage;
22 import java.awt.image.ColorModel;
23 import java.awt.image.DataBuffer;
24 import java.awt.image.DirectColorModel;
25 import java.awt.image.Raster;
26 import java.awt.image.WritableRaster;
27 import java.io.ByteArrayInputStream;
28 import java.io.IOException;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.List;
32 import java.util.Properties;
33
34 import org.apache.commons.imaging.ImagingException;
35 import org.apache.commons.imaging.bytesource.ByteSource;
36 import org.apache.commons.imaging.color.ColorConversions;
37 import org.apache.commons.imaging.common.Allocator;
38 import org.apache.commons.imaging.common.BinaryFileParser;
39 import org.apache.commons.imaging.formats.jpeg.JpegConstants;
40 import org.apache.commons.imaging.formats.jpeg.JpegUtils;
41 import org.apache.commons.imaging.formats.jpeg.segments.DhtSegment;
42 import org.apache.commons.imaging.formats.jpeg.segments.DhtSegment.HuffmanTable;
43 import org.apache.commons.imaging.formats.jpeg.segments.DqtSegment;
44 import org.apache.commons.imaging.formats.jpeg.segments.DqtSegment.QuantizationTable;
45 import org.apache.commons.imaging.formats.jpeg.segments.SofnSegment;
46 import org.apache.commons.imaging.formats.jpeg.segments.SosSegment;
47
48 public class JpegDecoder extends BinaryFileParser implements JpegUtils.Visitor {
49
50 private static final int[] BAND_MASK_ARGB = { 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 };
51 private static final int[] BAND_MASK_RGB = { 0x00ff0000, 0x0000ff00, 0x000000ff };
52
53
54
55
56
57
58
59 private static int fastRound(final float x) {
60 return (int) (x + 0.5f);
61 }
62
63
64
65
66
67
68
69
70 static List<Integer> getIntervalStartPositions(final int[] scanPayload) {
71 final List<Integer> intervalStarts = new ArrayList<>();
72 intervalStarts.add(0);
73 boolean foundFF = false;
74 boolean foundD0toD7 = false;
75 int pos = 0;
76 while (pos < scanPayload.length) {
77 if (foundFF) {
78
79 if (scanPayload[pos] >= (0xff & JpegConstants.RST0_MARKER) && scanPayload[pos] <= (0xff & JpegConstants.RST7_MARKER)) {
80 foundD0toD7 = true;
81 } else {
82 foundFF = false;
83 }
84 }
85
86 if (scanPayload[pos] == 0xFF) {
87 foundFF = true;
88 }
89
90
91 if (foundFF && foundD0toD7) {
92
93
94 intervalStarts.add(pos + 1);
95 foundFF = foundD0toD7 = false;
96 }
97 pos++;
98 }
99 return intervalStarts;
100 }
101
102
103
104
105
106
107
108 static JpegInputStream[] splitByRstMarkers(final int[] scanPayload) {
109 final List<Integer> intervalStarts = getIntervalStartPositions(scanPayload);
110
111 final int intervalCount = intervalStarts.size();
112 final JpegInputStream[] streams = Allocator.array(intervalCount, JpegInputStream[]::new, JpegInputStream.SHALLOW_SIZE);
113 for (int i = 0; i < intervalCount; i++) {
114 final int from = intervalStarts.get(i);
115 int to;
116 if (i < intervalCount - 1) {
117
118
119 to = intervalStarts.get(i + 1) - 2;
120 } else {
121 to = scanPayload.length;
122 }
123 final int[] interval = Arrays.copyOfRange(scanPayload, from, to);
124 streams[i] = new JpegInputStream(interval);
125 }
126 return streams;
127 }
128
129 private final DqtSegment.QuantizationTable[] quantizationTables = new DqtSegment.QuantizationTable[4];
130 private final DhtSegment.HuffmanTable[] huffmanDCTables = new DhtSegment.HuffmanTable[4];
131 private final DhtSegment.HuffmanTable[] huffmanACTables = new DhtSegment.HuffmanTable[4];
132 private SofnSegment sofnSegment;
133 private SosSegment sosSegment;
134 private final float[][] scaledQuantizationTables = new float[4][];
135 private BufferedImage image;
136 private ImagingException imageReadException;
137 private IOException ioException;
138
139 private final int[] zz = new int[64];
140
141 private final int[] blockInt = new int[64];
142
143 private final float[] block = new float[64];
144
145 private boolean useTiffRgb;
146
147 private Block[] allocateMcuMemory() throws ImagingException {
148 final Block[] mcu = Allocator.array(sosSegment.numberOfComponents, Block[]::new, Block.SHALLOW_SIZE);
149 for (int i = 0; i < sosSegment.numberOfComponents; i++) {
150 final SosSegment.Component scanComponent = sosSegment.getComponents(i);
151 SofnSegment.Component frameComponent = null;
152 for (int j = 0; j < sofnSegment.numberOfComponents; j++) {
153 if (sofnSegment.getComponents(j).componentIdentifier == scanComponent.scanComponentSelector) {
154 frameComponent = sofnSegment.getComponents(j);
155 break;
156 }
157 }
158 if (frameComponent == null) {
159 throw new ImagingException("Invalid component");
160 }
161 final Block fullBlock = new Block(8 * frameComponent.horizontalSamplingFactor, 8 * frameComponent.verticalSamplingFactor);
162 mcu[i] = fullBlock;
163 }
164 return mcu;
165 }
166
167 @Override
168 public boolean beginSos() {
169 return true;
170 }
171
172 public BufferedImage decode(final ByteSource byteSource) throws IOException, ImagingException {
173 final JpegUtils jpegUtils = new JpegUtils();
174 jpegUtils.traverseJfif(byteSource, this);
175 if (imageReadException != null) {
176 throw imageReadException;
177 }
178 if (ioException != null) {
179 throw ioException;
180 }
181 return image;
182 }
183
184 private int decode(final JpegInputStream is, final DhtSegment.HuffmanTable huffmanTable) throws ImagingException {
185
186 int i = 1;
187 int code = is.nextBit();
188 while (code > huffmanTable.getMaxCode(i)) {
189 i++;
190 code = code << 1 | is.nextBit();
191 }
192 int j = huffmanTable.getValPtr(i);
193 j += code - huffmanTable.getMinCode(i);
194 return huffmanTable.getHuffVal(j);
195 }
196
197 private int extend(int v, final int t) {
198
199 int vt = 1 << t - 1;
200 if (v < vt) {
201 vt = (-1 << t) + 1;
202 v += vt;
203 }
204 return v;
205 }
206
207 private void readMcu(final JpegInputStream is, final int[] preds, final Block[] mcu) throws ImagingException {
208 for (int i = 0; i < sosSegment.numberOfComponents; i++) {
209 final SosSegment.Component scanComponent = sosSegment.getComponents(i);
210 SofnSegment.Component frameComponent = null;
211 for (int j = 0; j < sofnSegment.numberOfComponents; j++) {
212 if (sofnSegment.getComponents(j).componentIdentifier == scanComponent.scanComponentSelector) {
213 frameComponent = sofnSegment.getComponents(j);
214 break;
215 }
216 }
217 if (frameComponent == null) {
218 throw new ImagingException("Invalid component");
219 }
220 final Block fullBlock = mcu[i];
221 for (int y = 0; y < frameComponent.verticalSamplingFactor; y++) {
222 for (int x = 0; x < frameComponent.horizontalSamplingFactor; x++) {
223 Arrays.fill(zz, 0);
224
225 final int t = decode(is, huffmanDCTables[scanComponent.dcCodingTableSelector]);
226 int diff = receive(t, is);
227 diff = extend(diff, t);
228 zz[0] = preds[i] + diff;
229 preds[i] = zz[0];
230
231
232 int k = 1;
233 while (true) {
234 final int rs = decode(is, huffmanACTables[scanComponent.acCodingTableSelector]);
235 final int ssss = rs & 0xf;
236 final int rrrr = rs >> 4;
237 final int r = rrrr;
238
239 if (ssss == 0) {
240 if (r != 15) {
241 break;
242 }
243 k += 16;
244 } else {
245 k += r;
246
247
248 zz[k] = receive(ssss, is);
249 zz[k] = extend(zz[k], ssss);
250
251 if (k == 63) {
252 break;
253 }
254 k++;
255 }
256 }
257
258 final int shift = 1 << sofnSegment.precision - 1;
259 final int max = (1 << sofnSegment.precision) - 1;
260
261 final float[] scaledQuantizationTable = scaledQuantizationTables[frameComponent.quantTabDestSelector];
262 ZigZag.zigZagToBlock(zz, blockInt);
263 for (int j = 0; j < 64; j++) {
264 block[j] = blockInt[j] * scaledQuantizationTable[j];
265 }
266 Dct.inverseDct8x8(block);
267
268 int dstRowOffset = 8 * y * 8 * frameComponent.horizontalSamplingFactor + 8 * x;
269 int srcNext = 0;
270 for (int yy = 0; yy < 8; yy++) {
271 for (int xx = 0; xx < 8; xx++) {
272 float sample = block[srcNext++];
273 sample += shift;
274 int result;
275 if (sample < 0) {
276 result = 0;
277 } else if (sample > max) {
278 result = max;
279 } else {
280 result = fastRound(sample);
281 }
282 fullBlock.samples[dstRowOffset + xx] = result;
283 }
284 dstRowOffset += 8 * frameComponent.horizontalSamplingFactor;
285 }
286 }
287 }
288 }
289 }
290
291 private int receive(final int ssss, final JpegInputStream is) throws ImagingException {
292
293 int i = 0;
294 int v = 0;
295 while (i != ssss) {
296 i++;
297 v = (v << 1) + is.nextBit();
298 }
299 return v;
300 }
301
302 private void rescaleMcu(final Block[] dataUnits, final int hSize, final int vSize, final Block[] ret) {
303 for (int i = 0; i < dataUnits.length; i++) {
304 final Block dataUnit = dataUnits[i];
305 if (dataUnit.width == hSize && dataUnit.height == vSize) {
306 System.arraycopy(dataUnit.samples, 0, ret[i].samples, 0, hSize * vSize);
307 } else {
308 final int hScale = hSize / dataUnit.width;
309 final int vScale = vSize / dataUnit.height;
310 if (hScale == 2 && vScale == 2) {
311 int srcRowOffset = 0;
312 int dstRowOffset = 0;
313 for (int y = 0; y < dataUnit.height; y++) {
314 for (int x = 0; x < hSize; x++) {
315 final int sample = dataUnit.samples[srcRowOffset + (x >> 1)];
316 ret[i].samples[dstRowOffset + x] = sample;
317 ret[i].samples[dstRowOffset + hSize + x] = sample;
318 }
319 srcRowOffset += dataUnit.width;
320 dstRowOffset += 2 * hSize;
321 }
322 } else {
323
324 int dstRowOffset = 0;
325 for (int y = 0; y < vSize; y++) {
326 for (int x = 0; x < hSize; x++) {
327 ret[i].samples[dstRowOffset + x] = dataUnit.samples[y / vScale * dataUnit.width + x / hScale];
328 }
329 dstRowOffset += hSize;
330 }
331 }
332 }
333 }
334 }
335
336
337
338
339
340 public void setTiffRgb() {
341 useTiffRgb = true;
342 }
343
344 @Override
345 public boolean visitSegment(final int marker, final byte[] markerBytes, final int segmentLength, final byte[] segmentLengthBytes, final byte[] segmentData)
346 throws ImagingException, IOException {
347 final int[] sofnSegments = { JpegConstants.SOF0_MARKER, JpegConstants.SOF1_MARKER, JpegConstants.SOF2_MARKER, JpegConstants.SOF3_MARKER,
348 JpegConstants.SOF5_MARKER, JpegConstants.SOF6_MARKER, JpegConstants.SOF7_MARKER, JpegConstants.SOF9_MARKER, JpegConstants.SOF10_MARKER,
349 JpegConstants.SOF11_MARKER, JpegConstants.SOF13_MARKER, JpegConstants.SOF14_MARKER, JpegConstants.SOF15_MARKER, };
350
351 if (Arrays.binarySearch(sofnSegments, marker) >= 0) {
352 if (marker != JpegConstants.SOF0_MARKER) {
353 throw new ImagingException("Only sequential, baseline JPEGs " + "are supported at the moment");
354 }
355 sofnSegment = new SofnSegment(marker, segmentData);
356 } else if (marker == JpegConstants.DQT_MARKER) {
357 final DqtSegment dqtSegment = new DqtSegment(marker, segmentData);
358 for (final QuantizationTable table : dqtSegment.quantizationTables) {
359 if (0 > table.destinationIdentifier || table.destinationIdentifier >= quantizationTables.length) {
360 throw new ImagingException("Invalid quantization table identifier " + table.destinationIdentifier);
361 }
362 quantizationTables[table.destinationIdentifier] = table;
363 final int mSize = 64;
364 final int[] quantizationMatrixInt = Allocator.intArray(mSize);
365 ZigZag.zigZagToBlock(table.getElements(), quantizationMatrixInt);
366 final float[] quantizationMatrixFloat = Allocator.floatArray(mSize);
367 for (int j = 0; j < mSize; j++) {
368 quantizationMatrixFloat[j] = quantizationMatrixInt[j];
369 }
370 Dct.scaleDequantizationMatrix(quantizationMatrixFloat);
371 scaledQuantizationTables[table.destinationIdentifier] = quantizationMatrixFloat;
372 }
373 } else if (marker == JpegConstants.DHT_MARKER) {
374 final DhtSegment dhtSegment = new DhtSegment(marker, segmentData);
375 for (final HuffmanTable table : dhtSegment.huffmanTables) {
376 DhtSegment.HuffmanTable[] tables;
377 if (table.tableClass == 0) {
378 tables = huffmanDCTables;
379 } else if (table.tableClass == 1) {
380 tables = huffmanACTables;
381 } else {
382 throw new ImagingException("Invalid huffman table class " + table.tableClass);
383 }
384 if (0 > table.destinationIdentifier || table.destinationIdentifier >= tables.length) {
385 throw new ImagingException("Invalid huffman table identifier " + table.destinationIdentifier);
386 }
387 tables[table.destinationIdentifier] = table;
388 }
389 }
390 return true;
391 }
392
393 @Override
394 public void visitSos(final int marker, final byte[] markerBytes, final byte[] imageData) {
395 try (ByteArrayInputStream is = new ByteArrayInputStream(imageData)) {
396
397 final int segmentLength = read2Bytes("segmentLength", is, "Not a Valid JPEG File", getByteOrder());
398 final byte[] sosSegmentBytes = readBytes("SosSegment", is, segmentLength - 2, "Not a Valid JPEG File");
399 sosSegment = new SosSegment(marker, sosSegmentBytes);
400
401
402
403
404 final int[] scanPayload = Allocator.intArray(imageData.length - segmentLength);
405 int payloadReadCount = 0;
406 while (payloadReadCount < scanPayload.length) {
407 scanPayload[payloadReadCount] = is.read();
408 payloadReadCount++;
409 }
410
411 int hMax = 0;
412 int vMax = 0;
413 for (int i = 0; i < sofnSegment.numberOfComponents; i++) {
414 hMax = Math.max(hMax, sofnSegment.getComponents(i).horizontalSamplingFactor);
415 vMax = Math.max(vMax, sofnSegment.getComponents(i).verticalSamplingFactor);
416 }
417 final int hSize = 8 * hMax;
418 final int vSize = 8 * vMax;
419
420 final int xMCUs = (sofnSegment.width + hSize - 1) / hSize;
421 final int yMCUs = (sofnSegment.height + vSize - 1) / vSize;
422 final Block[] mcu = allocateMcuMemory();
423 final Block[] scaledMCU = Allocator.array(mcu.length, Block[]::new, Block.SHALLOW_SIZE);
424 Arrays.setAll(scaledMCU, i -> new Block(hSize, vSize));
425 final int[] preds = Allocator.intArray(sofnSegment.numberOfComponents);
426 ColorModel colorModel;
427 WritableRaster raster;
428 Allocator.check(Integer.BYTES * sofnSegment.width * sofnSegment.height);
429 switch (sofnSegment.numberOfComponents) {
430 case 4:
431
432
433
434
435 if (useTiffRgb) {
436 colorModel = new DirectColorModel(32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000);
437 raster = Raster.createPackedRaster(DataBuffer.TYPE_INT, sofnSegment.width, sofnSegment.height, BAND_MASK_ARGB, null);
438 } else {
439 colorModel = new DirectColorModel(24, 0x00ff0000, 0x0000ff00, 0x000000ff);
440 raster = Raster.createPackedRaster(DataBuffer.TYPE_INT, sofnSegment.width, sofnSegment.height, BAND_MASK_RGB, null);
441 }
442
443 break;
444 case 3:
445 colorModel = new DirectColorModel(24, 0x00ff0000, 0x0000ff00, 0x000000ff);
446 raster = Raster.createPackedRaster(DataBuffer.TYPE_INT, sofnSegment.width, sofnSegment.height, new int[] { 0x00ff0000, 0x0000ff00, 0x000000ff },
447 null);
448 break;
449 case 1:
450 colorModel = new DirectColorModel(24, 0x00ff0000, 0x0000ff00, 0x000000ff);
451 raster = Raster.createPackedRaster(DataBuffer.TYPE_INT, sofnSegment.width, sofnSegment.height, new int[] { 0x00ff0000, 0x0000ff00, 0x000000ff },
452 null);
453
454
455
456
457
458
459 break;
460 default:
461 throw new ImagingException(sofnSegment.numberOfComponents + " components are invalid or unsupported");
462 }
463 final DataBuffer dataBuffer = raster.getDataBuffer();
464
465 final JpegInputStream[] bitInputStreams = splitByRstMarkers(scanPayload);
466 int bitInputStreamCount = 0;
467 JpegInputStream bitInputStream = bitInputStreams[0];
468
469 for (int y1 = 0; y1 < vSize * yMCUs; y1 += vSize) {
470 for (int x1 = 0; x1 < hSize * xMCUs; x1 += hSize) {
471
472
473 if (!bitInputStream.hasNext()) {
474 bitInputStreamCount++;
475 if (bitInputStreamCount < bitInputStreams.length) {
476 bitInputStream = bitInputStreams[bitInputStreamCount];
477 }
478 }
479
480 readMcu(bitInputStream, preds, mcu);
481 rescaleMcu(mcu, hSize, vSize, scaledMCU);
482 int srcRowOffset = 0;
483 int dstRowOffset = y1 * sofnSegment.width + x1;
484
485
486
487
488 if (useTiffRgb && (scaledMCU.length == 3 || scaledMCU.length == 4)) {
489
490
491
492
493
494 final int x2Limit;
495 if (x1 + hSize <= sofnSegment.width) {
496 x2Limit = hSize;
497 } else {
498 x2Limit = sofnSegment.width - x1;
499 }
500 final int y2Limit;
501 if (y1 + vSize <= sofnSegment.height) {
502 y2Limit = vSize;
503 } else {
504 y2Limit = sofnSegment.height - y1;
505 }
506
507 if (scaledMCU.length == 4) {
508
509
510
511
512
513
514
515
516
517 for (int y2 = 0; y2 < y2Limit; y2++) {
518 for (int x2 = 0; x2 < x2Limit; x2++) {
519 final int r = scaledMCU[0].samples[srcRowOffset + x2];
520 final int g = scaledMCU[1].samples[srcRowOffset + x2];
521 final int b = scaledMCU[2].samples[srcRowOffset + x2];
522 final int a = scaledMCU[3].samples[srcRowOffset + x2];
523 final int rgb = a << 24 | r << 16 | g << 8 | b;
524 dataBuffer.setElem(dstRowOffset + x2, rgb);
525 }
526 srcRowOffset += hSize;
527 dstRowOffset += sofnSegment.width;
528 }
529 } else {
530
531 for (int y2 = 0; y2 < y2Limit; y2++) {
532 for (int x2 = 0; x2 < x2Limit; x2++) {
533 final int r = scaledMCU[0].samples[srcRowOffset + x2];
534 final int g = scaledMCU[1].samples[srcRowOffset + x2];
535 final int b = scaledMCU[2].samples[srcRowOffset + x2];
536 final int rgb = r << 16 | g << 8 | b;
537 dataBuffer.setElem(dstRowOffset + x2, rgb);
538 }
539 srcRowOffset += hSize;
540 dstRowOffset += sofnSegment.width;
541 }
542 }
543 } else {
544 for (int y2 = 0; y2 < vSize && y1 + y2 < sofnSegment.height; y2++) {
545 for (int x2 = 0; x2 < hSize && x1 + x2 < sofnSegment.width; x2++) {
546 if (scaledMCU.length == 4) {
547 final int c = scaledMCU[0].samples[srcRowOffset + x2];
548 final int m = scaledMCU[1].samples[srcRowOffset + x2];
549 final int y = scaledMCU[2].samples[srcRowOffset + x2];
550 final int k = scaledMCU[3].samples[srcRowOffset + x2];
551 final int rgb = ColorConversions.convertCmykToRgb(c, m, y, k);
552 dataBuffer.setElem(dstRowOffset + x2, rgb);
553 } else if (scaledMCU.length == 3) {
554 final int y = scaledMCU[0].samples[srcRowOffset + x2];
555 final int cb = scaledMCU[1].samples[srcRowOffset + x2];
556 final int cr = scaledMCU[2].samples[srcRowOffset + x2];
557 final int rgb = YCbCrConverter.convertYCbCrToRgb(y, cb, cr);
558 dataBuffer.setElem(dstRowOffset + x2, rgb);
559 } else if (mcu.length == 1) {
560 final int y = scaledMCU[0].samples[srcRowOffset + x2];
561 dataBuffer.setElem(dstRowOffset + x2, y << 16 | y << 8 | y);
562 } else {
563 throw new ImagingException("Unsupported JPEG with " + mcu.length + " components");
564 }
565 }
566 srcRowOffset += hSize;
567 dstRowOffset += sofnSegment.width;
568 }
569 }
570 }
571 }
572 image = new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), new Properties());
573
574
575
576
577
578
579 } catch (final ImagingException imageReadEx) {
580 imageReadException = imageReadEx;
581 } catch (final IOException ioEx) {
582 ioException = ioEx;
583 } catch (final RuntimeException ex) {
584
585 imageReadException = new ImagingException("Error parsing JPEG", ex);
586 }
587 }
588 }