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 final 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
148
149
150 public JpegDecoder() {
151
152 }
153
154 private Block[] allocateMcuMemory() throws ImagingException {
155 final Block[] mcu = Allocator.array(sosSegment.numberOfComponents, Block[]::new, Block.SHALLOW_SIZE);
156 for (int i = 0; i < sosSegment.numberOfComponents; i++) {
157 final SosSegment.Component scanComponent = sosSegment.getComponents(i);
158 SofnSegment.Component frameComponent = null;
159 for (int j = 0; j < sofnSegment.numberOfComponents; j++) {
160 if (sofnSegment.getComponents(j).componentIdentifier == scanComponent.scanComponentSelector) {
161 frameComponent = sofnSegment.getComponents(j);
162 break;
163 }
164 }
165 if (frameComponent == null) {
166 throw new ImagingException("Invalid component");
167 }
168 final Block fullBlock = new Block(8 * frameComponent.horizontalSamplingFactor, 8 * frameComponent.verticalSamplingFactor);
169 mcu[i] = fullBlock;
170 }
171 return mcu;
172 }
173
174 @Override
175 public boolean beginSos() {
176 return true;
177 }
178
179 public BufferedImage decode(final ByteSource byteSource) throws IOException, ImagingException {
180 final JpegUtils jpegUtils = new JpegUtils();
181 jpegUtils.traverseJfif(byteSource, this);
182 if (imageReadException != null) {
183 throw imageReadException;
184 }
185 if (ioException != null) {
186 throw ioException;
187 }
188 return image;
189 }
190
191 private int decode(final JpegInputStream is, final DhtSegment.HuffmanTable huffmanTable) throws ImagingException {
192
193 int i = 1;
194 int code = is.nextBit();
195 while (code > huffmanTable.getMaxCode(i)) {
196 i++;
197 code = code << 1 | is.nextBit();
198 }
199 int j = huffmanTable.getValPtr(i);
200 j += code - huffmanTable.getMinCode(i);
201 return huffmanTable.getHuffVal(j);
202 }
203
204 private int extend(int v, final int t) {
205
206 int vt = 1 << t - 1;
207 if (v < vt) {
208 vt = (-1 << t) + 1;
209 v += vt;
210 }
211 return v;
212 }
213
214 private void readMcu(final JpegInputStream is, final int[] preds, final Block[] mcu) throws ImagingException {
215 for (int i = 0; i < sosSegment.numberOfComponents; i++) {
216 final SosSegment.Component scanComponent = sosSegment.getComponents(i);
217 SofnSegment.Component frameComponent = null;
218 for (int j = 0; j < sofnSegment.numberOfComponents; j++) {
219 if (sofnSegment.getComponents(j).componentIdentifier == scanComponent.scanComponentSelector) {
220 frameComponent = sofnSegment.getComponents(j);
221 break;
222 }
223 }
224 if (frameComponent == null) {
225 throw new ImagingException("Invalid component");
226 }
227 final Block fullBlock = mcu[i];
228 for (int y = 0; y < frameComponent.verticalSamplingFactor; y++) {
229 for (int x = 0; x < frameComponent.horizontalSamplingFactor; x++) {
230 Arrays.fill(zz, 0);
231
232 final int t = decode(is, huffmanDCTables[scanComponent.dcCodingTableSelector]);
233 int diff = receive(t, is);
234 diff = extend(diff, t);
235 zz[0] = preds[i] + diff;
236 preds[i] = zz[0];
237
238
239 int k = 1;
240 while (true) {
241 final int rs = decode(is, huffmanACTables[scanComponent.acCodingTableSelector]);
242 final int ssss = rs & 0xf;
243 final int rrrr = rs >> 4;
244 final int r = rrrr;
245
246 if (ssss == 0) {
247 if (r != 15) {
248 break;
249 }
250 k += 16;
251 } else {
252 k += r;
253
254
255 zz[k] = receive(ssss, is);
256 zz[k] = extend(zz[k], ssss);
257
258 if (k == 63) {
259 break;
260 }
261 k++;
262 }
263 }
264
265 final int shift = 1 << sofnSegment.precision - 1;
266 final int max = (1 << sofnSegment.precision) - 1;
267
268 final float[] scaledQuantizationTable = scaledQuantizationTables[frameComponent.quantTabDestSelector];
269 ZigZag.zigZagToBlock(zz, blockInt);
270 for (int j = 0; j < 64; j++) {
271 block[j] = blockInt[j] * scaledQuantizationTable[j];
272 }
273 Dct.inverseDct8x8(block);
274
275 int dstRowOffset = 8 * y * 8 * frameComponent.horizontalSamplingFactor + 8 * x;
276 int srcNext = 0;
277 for (int yy = 0; yy < 8; yy++) {
278 for (int xx = 0; xx < 8; xx++) {
279 float sample = block[srcNext++];
280 sample += shift;
281 final int result;
282 if (sample < 0) {
283 result = 0;
284 } else if (sample > max) {
285 result = max;
286 } else {
287 result = fastRound(sample);
288 }
289 fullBlock.samples[dstRowOffset + xx] = result;
290 }
291 dstRowOffset += 8 * frameComponent.horizontalSamplingFactor;
292 }
293 }
294 }
295 }
296 }
297
298 private int receive(final int ssss, final JpegInputStream is) throws ImagingException {
299
300 int i = 0;
301 int v = 0;
302 while (i != ssss) {
303 i++;
304 v = (v << 1) + is.nextBit();
305 }
306 return v;
307 }
308
309 private void rescaleMcu(final Block[] dataUnits, final int hSize, final int vSize, final Block[] ret) {
310 for (int i = 0; i < dataUnits.length; i++) {
311 final Block dataUnit = dataUnits[i];
312 if (dataUnit.width == hSize && dataUnit.height == vSize) {
313 System.arraycopy(dataUnit.samples, 0, ret[i].samples, 0, hSize * vSize);
314 } else {
315 final int hScale = hSize / dataUnit.width;
316 final int vScale = vSize / dataUnit.height;
317 if (hScale == 2 && vScale == 2) {
318 int srcRowOffset = 0;
319 int dstRowOffset = 0;
320 for (int y = 0; y < dataUnit.height; y++) {
321 for (int x = 0; x < hSize; x++) {
322 final int sample = dataUnit.samples[srcRowOffset + (x >> 1)];
323 ret[i].samples[dstRowOffset + x] = sample;
324 ret[i].samples[dstRowOffset + hSize + x] = sample;
325 }
326 srcRowOffset += dataUnit.width;
327 dstRowOffset += 2 * hSize;
328 }
329 } else {
330
331 int dstRowOffset = 0;
332 for (int y = 0; y < vSize; y++) {
333 for (int x = 0; x < hSize; x++) {
334 ret[i].samples[dstRowOffset + x] = dataUnit.samples[y / vScale * dataUnit.width + x / hScale];
335 }
336 dstRowOffset += hSize;
337 }
338 }
339 }
340 }
341 }
342
343
344
345
346
347 public void setTiffRgb() {
348 useTiffRgb = true;
349 }
350
351 @Override
352 public boolean visitSegment(final int marker, final byte[] markerBytes, final int segmentLength, final byte[] segmentLengthBytes, final byte[] segmentData)
353 throws ImagingException, IOException {
354 final int[] sofnSegments = { JpegConstants.SOF0_MARKER, JpegConstants.SOF1_MARKER, JpegConstants.SOF2_MARKER, JpegConstants.SOF3_MARKER,
355 JpegConstants.SOF5_MARKER, JpegConstants.SOF6_MARKER, JpegConstants.SOF7_MARKER, JpegConstants.SOF9_MARKER, JpegConstants.SOF10_MARKER,
356 JpegConstants.SOF11_MARKER, JpegConstants.SOF13_MARKER, JpegConstants.SOF14_MARKER, JpegConstants.SOF15_MARKER, };
357
358 if (Arrays.binarySearch(sofnSegments, marker) >= 0) {
359 if (marker != JpegConstants.SOF0_MARKER) {
360 throw new ImagingException("Only sequential, baseline JPEGs " + "are supported at the moment");
361 }
362 sofnSegment = new SofnSegment(marker, segmentData);
363 } else if (marker == JpegConstants.DQT_MARKER) {
364 final DqtSegment dqtSegment = new DqtSegment(marker, segmentData);
365 for (final QuantizationTable table : dqtSegment.quantizationTables) {
366 if (0 > table.destinationIdentifier || table.destinationIdentifier >= quantizationTables.length) {
367 throw new ImagingException("Invalid quantization table identifier " + table.destinationIdentifier);
368 }
369 quantizationTables[table.destinationIdentifier] = table;
370 final int mSize = 64;
371 final int[] quantizationMatrixInt = Allocator.intArray(mSize);
372 ZigZag.zigZagToBlock(table.getElements(), quantizationMatrixInt);
373 final float[] quantizationMatrixFloat = Allocator.floatArray(mSize);
374 for (int j = 0; j < mSize; j++) {
375 quantizationMatrixFloat[j] = quantizationMatrixInt[j];
376 }
377 Dct.scaleDequantizationMatrix(quantizationMatrixFloat);
378 scaledQuantizationTables[table.destinationIdentifier] = quantizationMatrixFloat;
379 }
380 } else if (marker == JpegConstants.DHT_MARKER) {
381 final DhtSegment dhtSegment = new DhtSegment(marker, segmentData);
382 for (final HuffmanTable table : dhtSegment.huffmanTables) {
383 final DhtSegment.HuffmanTable[] tables;
384 if (table.tableClass == 0) {
385 tables = huffmanDCTables;
386 } else if (table.tableClass == 1) {
387 tables = huffmanACTables;
388 } else {
389 throw new ImagingException("Invalid huffman table class " + table.tableClass);
390 }
391 if (0 > table.destinationIdentifier || table.destinationIdentifier >= tables.length) {
392 throw new ImagingException("Invalid huffman table identifier " + table.destinationIdentifier);
393 }
394 tables[table.destinationIdentifier] = table;
395 }
396 }
397 return true;
398 }
399
400 @Override
401 public void visitSos(final int marker, final byte[] markerBytes, final byte[] imageData) {
402 try (ByteArrayInputStream is = new ByteArrayInputStream(imageData)) {
403
404 final int segmentLength = read2Bytes("segmentLength", is, "Not a Valid JPEG File", getByteOrder());
405 final byte[] sosSegmentBytes = readBytes("SosSegment", is, segmentLength - 2, "Not a Valid JPEG File");
406 sosSegment = new SosSegment(marker, sosSegmentBytes);
407
408
409
410
411 final int[] scanPayload = Allocator.intArray(imageData.length - segmentLength);
412 int payloadReadCount = 0;
413 while (payloadReadCount < scanPayload.length) {
414 scanPayload[payloadReadCount] = is.read();
415 payloadReadCount++;
416 }
417
418 int hMax = 0;
419 int vMax = 0;
420 for (int i = 0; i < sofnSegment.numberOfComponents; i++) {
421 hMax = Math.max(hMax, sofnSegment.getComponents(i).horizontalSamplingFactor);
422 vMax = Math.max(vMax, sofnSegment.getComponents(i).verticalSamplingFactor);
423 }
424 final int hSize = 8 * hMax;
425 final int vSize = 8 * vMax;
426
427 final int xMCUs = (sofnSegment.width + hSize - 1) / hSize;
428 final int yMCUs = (sofnSegment.height + vSize - 1) / vSize;
429 final Block[] mcu = allocateMcuMemory();
430 final Block[] scaledMCU = Allocator.array(mcu.length, Block[]::new, Block.SHALLOW_SIZE);
431 Arrays.setAll(scaledMCU, i -> new Block(hSize, vSize));
432 final int[] preds = Allocator.intArray(sofnSegment.numberOfComponents);
433 final ColorModel colorModel;
434 final WritableRaster raster;
435 Allocator.check(Integer.BYTES * sofnSegment.width * sofnSegment.height);
436 switch (sofnSegment.numberOfComponents) {
437 case 4:
438
439
440
441
442 if (useTiffRgb) {
443 colorModel = new DirectColorModel(32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000);
444 raster = Raster.createPackedRaster(DataBuffer.TYPE_INT, sofnSegment.width, sofnSegment.height, BAND_MASK_ARGB, null);
445 } else {
446 colorModel = new DirectColorModel(24, 0x00ff0000, 0x0000ff00, 0x000000ff);
447 raster = Raster.createPackedRaster(DataBuffer.TYPE_INT, sofnSegment.width, sofnSegment.height, BAND_MASK_RGB, null);
448 }
449
450 break;
451 case 3:
452 colorModel = new DirectColorModel(24, 0x00ff0000, 0x0000ff00, 0x000000ff);
453 raster = Raster.createPackedRaster(DataBuffer.TYPE_INT, sofnSegment.width, sofnSegment.height, new int[] { 0x00ff0000, 0x0000ff00, 0x000000ff },
454 null);
455 break;
456 case 1:
457 colorModel = new DirectColorModel(24, 0x00ff0000, 0x0000ff00, 0x000000ff);
458 raster = Raster.createPackedRaster(DataBuffer.TYPE_INT, sofnSegment.width, sofnSegment.height, new int[] { 0x00ff0000, 0x0000ff00, 0x000000ff },
459 null);
460
461
462
463
464
465
466 break;
467 default:
468 throw new ImagingException(sofnSegment.numberOfComponents + " components are invalid or unsupported");
469 }
470 final DataBuffer dataBuffer = raster.getDataBuffer();
471
472 final JpegInputStream[] bitInputStreams = splitByRstMarkers(scanPayload);
473 int bitInputStreamCount = 0;
474 JpegInputStream bitInputStream = bitInputStreams[0];
475
476 for (int y1 = 0; y1 < vSize * yMCUs; y1 += vSize) {
477 for (int x1 = 0; x1 < hSize * xMCUs; x1 += hSize) {
478
479
480 if (!bitInputStream.hasNext()) {
481 bitInputStreamCount++;
482 if (bitInputStreamCount < bitInputStreams.length) {
483 bitInputStream = bitInputStreams[bitInputStreamCount];
484 }
485 }
486
487 readMcu(bitInputStream, preds, mcu);
488 rescaleMcu(mcu, hSize, vSize, scaledMCU);
489 int srcRowOffset = 0;
490 int dstRowOffset = y1 * sofnSegment.width + x1;
491
492
493
494
495 if (useTiffRgb && (scaledMCU.length == 3 || scaledMCU.length == 4)) {
496
497
498
499
500
501 final int x2Limit;
502 if (x1 + hSize <= sofnSegment.width) {
503 x2Limit = hSize;
504 } else {
505 x2Limit = sofnSegment.width - x1;
506 }
507 final int y2Limit;
508 if (y1 + vSize <= sofnSegment.height) {
509 y2Limit = vSize;
510 } else {
511 y2Limit = sofnSegment.height - y1;
512 }
513
514 if (scaledMCU.length == 4) {
515
516
517
518
519
520
521
522
523
524 for (int y2 = 0; y2 < y2Limit; y2++) {
525 for (int x2 = 0; x2 < x2Limit; x2++) {
526 final int r = scaledMCU[0].samples[srcRowOffset + x2];
527 final int g = scaledMCU[1].samples[srcRowOffset + x2];
528 final int b = scaledMCU[2].samples[srcRowOffset + x2];
529 final int a = scaledMCU[3].samples[srcRowOffset + x2];
530 final int rgb = a << 24 | r << 16 | g << 8 | b;
531 dataBuffer.setElem(dstRowOffset + x2, rgb);
532 }
533 srcRowOffset += hSize;
534 dstRowOffset += sofnSegment.width;
535 }
536 } else {
537
538 for (int y2 = 0; y2 < y2Limit; y2++) {
539 for (int x2 = 0; x2 < x2Limit; x2++) {
540 final int r = scaledMCU[0].samples[srcRowOffset + x2];
541 final int g = scaledMCU[1].samples[srcRowOffset + x2];
542 final int b = scaledMCU[2].samples[srcRowOffset + x2];
543 final int rgb = r << 16 | g << 8 | b;
544 dataBuffer.setElem(dstRowOffset + x2, rgb);
545 }
546 srcRowOffset += hSize;
547 dstRowOffset += sofnSegment.width;
548 }
549 }
550 } else {
551 for (int y2 = 0; y2 < vSize && y1 + y2 < sofnSegment.height; y2++) {
552 for (int x2 = 0; x2 < hSize && x1 + x2 < sofnSegment.width; x2++) {
553 if (scaledMCU.length == 4) {
554 final int c = scaledMCU[0].samples[srcRowOffset + x2];
555 final int m = scaledMCU[1].samples[srcRowOffset + x2];
556 final int y = scaledMCU[2].samples[srcRowOffset + x2];
557 final int k = scaledMCU[3].samples[srcRowOffset + x2];
558 final int rgb = ColorConversions.convertCmykToRgb(c, m, y, k);
559 dataBuffer.setElem(dstRowOffset + x2, rgb);
560 } else if (scaledMCU.length == 3) {
561 final int y = scaledMCU[0].samples[srcRowOffset + x2];
562 final int cb = scaledMCU[1].samples[srcRowOffset + x2];
563 final int cr = scaledMCU[2].samples[srcRowOffset + x2];
564 final int rgb = YCbCrConverter.convertYCbCrToRgb(y, cb, cr);
565 dataBuffer.setElem(dstRowOffset + x2, rgb);
566 } else if (mcu.length == 1) {
567 final int y = scaledMCU[0].samples[srcRowOffset + x2];
568 dataBuffer.setElem(dstRowOffset + x2, y << 16 | y << 8 | y);
569 } else {
570 throw new ImagingException("Unsupported JPEG with " + mcu.length + " components");
571 }
572 }
573 srcRowOffset += hSize;
574 dstRowOffset += sofnSegment.width;
575 }
576 }
577 }
578 }
579 image = new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), new Properties());
580
581
582
583
584
585
586 } catch (final ImagingException imageReadEx) {
587 imageReadException = imageReadEx;
588 } catch (final IOException ioEx) {
589 ioException = ioEx;
590 } catch (final RuntimeException ex) {
591
592 imageReadException = new ImagingException("Error parsing JPEG", ex);
593 }
594 }
595 }