1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 package com.sun.imageio.plugins.jpeg;
27
28 import javax.imageio.IIOException;
29 import javax.imageio.ImageReader;
30 import javax.imageio.ImageReadParam;
31 import javax.imageio.ImageTypeSpecifier;
32 import javax.imageio.metadata.IIOMetadata;
33 import javax.imageio.spi.ImageReaderSpi;
34 import javax.imageio.stream.ImageInputStream;
35 import javax.imageio.plugins.jpeg.JPEGImageReadParam;
36 import javax.imageio.plugins.jpeg.JPEGQTable;
37 import javax.imageio.plugins.jpeg.JPEGHuffmanTable;
38
39 import java.awt.Point;
40 import java.awt.Rectangle;
41 import java.awt.color.ColorSpace;
42 import java.awt.color.ICC_Profile;
43 import java.awt.color.ICC_ColorSpace;
44 import java.awt.color.CMMException;
45 import java.awt.image.BufferedImage;
46 import java.awt.image.Raster;
47 import java.awt.image.WritableRaster;
48 import java.awt.image.DataBuffer;
49 import java.awt.image.DataBufferByte;
50 import java.awt.image.ColorModel;
51 import java.awt.image.IndexColorModel;
52 import java.awt.image.ColorConvertOp;
53 import java.io.IOException;
54 import java.util.List;
55 import java.util.Iterator;
56 import java.util.ArrayList;
57 import java.util.NoSuchElementException;
58
59 import sun.java2d.Disposer;
60 import sun.java2d.DisposerRecord;
61
62 public class JPEGImageReader extends ImageReader {
63
64 private boolean debug = false;
65
66
67
68
69
70
71
72 private long structPointer = 0;
73
74
75 private ImageInputStream iis = null;
76
77
78
79
80
81 private List<Long> imagePositions = null;
82
83
84
85
86 private int numImages = 0;
87
88 static {
89 java.security.AccessController.doPrivileged(
90 new java.security.PrivilegedAction<Void>() {
91 public Void run() {
92 System.loadLibrary("javajpeg");
93 return null;
94 }
95 });
96 initReaderIDs(ImageInputStream.class,
97 JPEGQTable.class,
98 JPEGHuffmanTable.class);
99 }
100
101
102
103
104
105
106
107
108
109
110
111 protected static final int WARNING_NO_EOI = 0;
112
113
114
115
116
117
118 protected static final int WARNING_NO_JFIF_IN_THUMB = 1;
119
120
121
122
123
124 protected static final int WARNING_IGNORE_INVALID_ICC = 2;
125
126 private static final int MAX_WARNING = WARNING_IGNORE_INVALID_ICC;
127
128
129
130
131
132 private int currentImage = -1;
133
134
135
136
137
138
139 private int width;
140
141 private int height;
142
143
144
145
146 private int colorSpaceCode;
147
148
149
150
151 private int outColorSpaceCode;
152
153 private int numComponents;
154
155 private ColorSpace iccCS = null;
156
157
158
159 private ColorConvertOp convert = null;
160
161
162 private BufferedImage image = null;
163
164
165 private WritableRaster raster = null;
166
167
168 private WritableRaster target = null;
169
170
171 private DataBufferByte buffer = null;
172
173
174 private Rectangle destROI = null;
175
176
177 private int [] destinationBands = null;
178
179
180 private JPEGMetadata streamMetadata = null;
181
182
183 private JPEGMetadata imageMetadata = null;
184 private int imageMetadataIndex = -1;
185
186
187
188
189
190 private boolean haveSeeked = false;
191
192
193
194
195
196 private JPEGQTable [] abbrevQTables = null;
197 private JPEGHuffmanTable[] abbrevDCHuffmanTables = null;
198 private JPEGHuffmanTable[] abbrevACHuffmanTables = null;
199
200 private int minProgressivePass = 0;
201 private int maxProgressivePass = Integer.MAX_VALUE;
202
203
204
205
206 private static final int UNKNOWN = -1;
207 private static final int MIN_ESTIMATED_PASSES = 10;
208 private int knownPassCount = UNKNOWN;
209 private int pass = 0;
210 private float percentToDate = 0.0F;
211 private float previousPassPercentage = 0.0F;
212 private int progInterval = 0;
213
214
215
216
217 private boolean tablesOnlyChecked = false;
218
219
220 private Object disposerReferent = new Object();
221
222
223 private DisposerRecord disposerRecord;
224
225
226 private static native void initReaderIDs(Class<?> iisClass,
227 Class<?> qTableClass,
228 Class<?> huffClass);
229
230 public JPEGImageReader(ImageReaderSpi originator) {
231 super(originator);
232 structPointer = initJPEGImageReader();
233 disposerRecord = new JPEGReaderDisposerRecord(structPointer);
234 Disposer.addRecord(disposerReferent, disposerRecord);
235 }
236
237
238 private native long initJPEGImageReader();
239
240
241
242
243
244
245 protected void warningOccurred(int code) {
246 cbLock.lock();
247 try {
248 if ((code < 0) || (code > MAX_WARNING)){
249 throw new InternalError("Invalid warning index");
250 }
251 processWarningOccurred
252 ("com.sun.imageio.plugins.jpeg.JPEGImageReaderResources",
253 Integer.toString(code));
254 } finally {
255 cbLock.unlock();
256 }
257 }
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272 protected void warningWithMessage(String msg) {
273 cbLock.lock();
274 try {
275 processWarningOccurred(msg);
276 } finally {
277 cbLock.unlock();
278 }
279 }
280
281 public void setInput(Object input,
282 boolean seekForwardOnly,
283 boolean ignoreMetadata)
284 {
285 setThreadLock();
286 try {
287 cbLock.check();
288
289 super.setInput(input, seekForwardOnly, ignoreMetadata);
290 this.ignoreMetadata = ignoreMetadata;
291 resetInternalState();
292 iis = (ImageInputStream) input;
293 setSource(structPointer);
294 } finally {
295 clearThreadLock();
296 }
297 }
298
299
300
301
302
303
304
305
306
307
308
309 private int readInputData(byte[] buf, int off, int len) throws IOException {
310 cbLock.lock();
311 try {
312 return iis.read(buf, off, len);
313 } finally {
314 cbLock.unlock();
315 }
316 }
317
318
319
320
321
322
323
324
325
326 private long skipInputBytes(long n) throws IOException {
327 cbLock.lock();
328 try {
329 return iis.skipBytes(n);
330 } finally {
331 cbLock.unlock();
332 }
333 }
334
335 private native void setSource(long structPointer);
336
337 private void checkTablesOnly() throws IOException {
338 if (debug) {
339 System.out.println("Checking for tables-only image");
340 }
341 long savePos = iis.getStreamPosition();
342 if (debug) {
343 System.out.println("saved pos is " + savePos);
344 System.out.println("length is " + iis.length());
345 }
346
347 boolean tablesOnly = readNativeHeader(true);
348 if (tablesOnly) {
349 if (debug) {
350 System.out.println("tables-only image found");
351 long pos = iis.getStreamPosition();
352 System.out.println("pos after return from native is " + pos);
353 }
354
355
356 if (ignoreMetadata == false) {
357 iis.seek(savePos);
358 haveSeeked = true;
359 streamMetadata = new JPEGMetadata(true, false,
360 iis, this);
361 long pos = iis.getStreamPosition();
362 if (debug) {
363 System.out.println
364 ("pos after constructing stream metadata is " + pos);
365 }
366 }
367
368
369 if (hasNextImage()) {
370 imagePositions.add(iis.getStreamPosition());
371 }
372 } else {
373 imagePositions.add(savePos);
374
375 currentImage = 0;
376 }
377 if (seekForwardOnly) {
378 Long pos = imagePositions.get(imagePositions.size()-1);
379 iis.flushBefore(pos.longValue());
380 }
381 tablesOnlyChecked = true;
382 }
383
384 public int getNumImages(boolean allowSearch) throws IOException {
385 setThreadLock();
386 try {
387 cbLock.check();
388
389 return getNumImagesOnThread(allowSearch);
390 } finally {
391 clearThreadLock();
392 }
393 }
394
395 private void skipPastImage(int imageIndex) {
396 cbLock.lock();
397 try {
398 gotoImage(imageIndex);
399 skipImage();
400 } catch (IOException | IndexOutOfBoundsException e) {
401 } finally {
402 cbLock.unlock();
403 }
404 }
405
406 @SuppressWarnings("fallthrough")
407 private int getNumImagesOnThread(boolean allowSearch)
408 throws IOException {
409 if (numImages != 0) {
410 return numImages;
411 }
412 if (iis == null) {
413 throw new IllegalStateException("Input not set");
414 }
415 if (allowSearch == true) {
416 if (seekForwardOnly) {
417 throw new IllegalStateException(
418 "seekForwardOnly and allowSearch can't both be true!");
419 }
420
421
422 if (!tablesOnlyChecked) {
423 checkTablesOnly();
424 }
425
426 iis.mark();
427
428 gotoImage(0);
429
430 JPEGBuffer buffer = new JPEGBuffer(iis);
431 buffer.loadBuf(0);
432
433 boolean done = false;
434 while (!done) {
435 done = buffer.scanForFF(this);
436 switch (buffer.buf[buffer.bufPtr] & 0xff) {
437 case JPEG.SOI:
438 numImages++;
439
440
441 case 0:
442 case JPEG.RST0:
443 case JPEG.RST1:
444 case JPEG.RST2:
445 case JPEG.RST3:
446 case JPEG.RST4:
447 case JPEG.RST5:
448 case JPEG.RST6:
449 case JPEG.RST7:
450 case JPEG.EOI:
451 buffer.bufAvail--;
452 buffer.bufPtr++;
453 break;
454
455 default:
456 buffer.bufAvail--;
457 buffer.bufPtr++;
458 buffer.loadBuf(2);
459 int length = ((buffer.buf[buffer.bufPtr++] & 0xff) << 8) |
460 (buffer.buf[buffer.bufPtr++] & 0xff);
461 buffer.bufAvail -= 2;
462 length -= 2;
463 buffer.skipData(length);
464 }
465 }
466
467
468 iis.reset();
469
470 return numImages;
471 }
472
473 return -1;
474 }
475
476
477
478
479
480
481
482
483
484
485 private void gotoImage(int imageIndex) throws IOException {
486 if (iis == null) {
487 throw new IllegalStateException("Input not set");
488 }
489 if (imageIndex < minIndex) {
490 throw new IndexOutOfBoundsException();
491 }
492 if (!tablesOnlyChecked) {
493 checkTablesOnly();
494 }
495 if (imageIndex < imagePositions.size()) {
496 iis.seek(imagePositions.get(imageIndex).longValue());
497 } else {
498
499
500
501 Long pos = imagePositions.get(imagePositions.size()-1);
502 iis.seek(pos.longValue());
503 skipImage();
504
505 for (int index = imagePositions.size();
506 index <= imageIndex;
507 index++) {
508
509 if (!hasNextImage()) {
510 throw new IndexOutOfBoundsException();
511 }
512 pos = iis.getStreamPosition();
513 imagePositions.add(pos);
514 if (seekForwardOnly) {
515 iis.flushBefore(pos.longValue());
516 }
517 if (index < imageIndex) {
518 skipImage();
519 }
520 }
521 }
522
523 if (seekForwardOnly) {
524 minIndex = imageIndex;
525 }
526
527 haveSeeked = true;
528 }
529
530
531
532
533
534
535
536
537
538
539
540 private void skipImage() throws IOException {
541 if (debug) {
542 System.out.println("skipImage called");
543 }
544
545 int initialFF = iis.read();
546 if (initialFF == 0xff) {
547 int soiMarker = iis.read();
548 if (soiMarker != JPEG.SOI) {
549 throw new IOException("skipImage : Invalid image doesn't "
550 + "start with SOI marker");
551 }
552 } else {
553 throw new IOException("skipImage : Invalid image doesn't start "
554 + "with 0xff");
555 }
556 boolean foundFF = false;
557 String IOOBE = "skipImage : Reached EOF before we got EOI marker";
558 int markerLength = 2;
559 for (int byteval = iis.read();
560 byteval != -1;
561 byteval = iis.read()) {
562
563 if (foundFF == true) {
564 switch (byteval) {
565 case JPEG.EOI:
566 if (debug) {
567 System.out.println("skipImage : Found EOI at " +
568 (iis.getStreamPosition() - markerLength));
569 }
570 return;
571 case JPEG.SOI:
572 throw new IOException("skipImage : Found extra SOI"
573 + " marker before getting to EOI");
574 case 0:
575 case 255:
576
577 case JPEG.RST0:
578 case JPEG.RST1:
579 case JPEG.RST2:
580 case JPEG.RST3:
581 case JPEG.RST4:
582 case JPEG.RST5:
583 case JPEG.RST6:
584 case JPEG.RST7:
585 case JPEG.TEM:
586 break;
587
588 case JPEG.SOF0:
589 case JPEG.SOF1:
590 case JPEG.SOF2:
591 case JPEG.SOF3:
592 case JPEG.DHT:
593 case JPEG.SOF5:
594 case JPEG.SOF6:
595 case JPEG.SOF7:
596 case JPEG.JPG:
597 case JPEG.SOF9:
598 case JPEG.SOF10:
599 case JPEG.SOF11:
600 case JPEG.DAC:
601 case JPEG.SOF13:
602 case JPEG.SOF14:
603 case JPEG.SOF15:
604 case JPEG.SOS:
605 case JPEG.DQT:
606 case JPEG.DNL:
607 case JPEG.DRI:
608 case JPEG.DHP:
609 case JPEG.EXP:
610 case JPEG.APP0:
611 case JPEG.APP1:
612 case JPEG.APP2:
613 case JPEG.APP3:
614 case JPEG.APP4:
615 case JPEG.APP5:
616 case JPEG.APP6:
617 case JPEG.APP7:
618 case JPEG.APP8:
619 case JPEG.APP9:
620 case JPEG.APP10:
621 case JPEG.APP11:
622 case JPEG.APP12:
623 case JPEG.APP13:
624 case JPEG.APP14:
625 case JPEG.APP15:
626 case JPEG.COM:
627
628 int lengthHigherBits, lengthLowerBits, length;
629 lengthHigherBits = iis.read();
630 if (lengthHigherBits != (-1)) {
631 lengthLowerBits = iis.read();
632 if (lengthLowerBits != (-1)) {
633 length = (lengthHigherBits << 8) |
634 lengthLowerBits;
635
636 length -= 2;
637 } else {
638 throw new IndexOutOfBoundsException(IOOBE);
639 }
640 } else {
641 throw new IndexOutOfBoundsException(IOOBE);
642 }
643
644 iis.skipBytes(length);
645 break;
646 case (-1):
647 throw new IndexOutOfBoundsException(IOOBE);
648 default:
649 throw new IOException("skipImage : Invalid marker "
650 + "starting with ff "
651 + Integer.toHexString(byteval));
652 }
653 }
654 foundFF = (byteval == 0xff);
655 }
656 throw new IndexOutOfBoundsException(IOOBE);
657 }
658
659
660
661
662
663
664 private boolean hasNextImage() throws IOException {
665 if (debug) {
666 System.out.print("hasNextImage called; returning ");
667 }
668 iis.mark();
669 boolean foundFF = false;
670 for (int byteval = iis.read();
671 byteval != -1;
672 byteval = iis.read()) {
673
674 if (foundFF == true) {
675 if (byteval == JPEG.SOI) {
676 iis.reset();
677 if (debug) {
678 System.out.println("true");
679 }
680 return true;
681 }
682 }
683 foundFF = (byteval == 0xff) ? true : false;
684 }
685
686 iis.reset();
687 if (debug) {
688 System.out.println("false");
689 }
690 return false;
691 }
692
693
694
695
696
697
698 private void pushBack(int num) throws IOException {
699 if (debug) {
700 System.out.println("pushing back " + num + " bytes");
701 }
702 cbLock.lock();
703 try {
704 iis.seek(iis.getStreamPosition()-num);
705
706 } finally {
707 cbLock.unlock();
708 }
709 }
710
711
712
713
714 private void readHeader(int imageIndex, boolean reset)
715 throws IOException {
716 gotoImage(imageIndex);
717 readNativeHeader(reset);
718 currentImage = imageIndex;
719 }
720
721 private boolean readNativeHeader(boolean reset) throws IOException {
722 boolean retval = false;
723 retval = readImageHeader(structPointer, haveSeeked, reset);
724 haveSeeked = false;
725 return retval;
726 }
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741 private native boolean readImageHeader(long structPointer,
742 boolean clearBuffer,
743 boolean reset)
744 throws IOException;
745
746
747
748
749
750
751
752 private void setImageData(int width,
753 int height,
754 int colorSpaceCode,
755 int outColorSpaceCode,
756 int numComponents,
757 byte [] iccData) {
758 this.width = width;
759 this.height = height;
760 this.colorSpaceCode = colorSpaceCode;
761 this.outColorSpaceCode = outColorSpaceCode;
762 this.numComponents = numComponents;
763
764 if (iccData == null) {
765 iccCS = null;
766 return;
767 }
768
769 ICC_Profile newProfile = null;
770 try {
771 newProfile = ICC_Profile.getInstance(iccData);
772 } catch (IllegalArgumentException e) {
773
774
775
776
777 iccCS = null;
778 warningOccurred(WARNING_IGNORE_INVALID_ICC);
779
780 return;
781 }
782 byte[] newData = newProfile.getData();
783
784 ICC_Profile oldProfile = null;
785 if (iccCS instanceof ICC_ColorSpace) {
786 oldProfile = ((ICC_ColorSpace)iccCS).getProfile();
787 }
788 byte[] oldData = null;
789 if (oldProfile != null) {
790 oldData = oldProfile.getData();
791 }
792
793
794
795
796
797
798
799
800
801
802 if (oldData == null ||
803 !java.util.Arrays.equals(oldData, newData))
804 {
805 iccCS = new ICC_ColorSpace(newProfile);
806
807 try {
808 float[] colors = iccCS.fromRGB(new float[] {1f, 0f, 0f});
809 } catch (CMMException e) {
810
811
812
813
814 iccCS = null;
815 cbLock.lock();
816 try {
817 warningOccurred(WARNING_IGNORE_INVALID_ICC);
818 } finally {
819 cbLock.unlock();
820 }
821 }
822 }
823 }
824
825 public int getWidth(int imageIndex) throws IOException {
826 setThreadLock();
827 try {
828 if (currentImage != imageIndex) {
829 cbLock.check();
830 readHeader(imageIndex, true);
831 }
832 return width;
833 } finally {
834 clearThreadLock();
835 }
836 }
837
838 public int getHeight(int imageIndex) throws IOException {
839 setThreadLock();
840 try {
841 if (currentImage != imageIndex) {
842 cbLock.check();
843 readHeader(imageIndex, true);
844 }
845 return height;
846 } finally {
847 clearThreadLock();
848 }
849 }
850
851
852
853
854
855
856
857 private ImageTypeProducer getImageType(int code) {
858 ImageTypeProducer ret = null;
859
860 if ((code > 0) && (code < JPEG.NUM_JCS_CODES)) {
861 ret = ImageTypeProducer.getTypeProducer(code);
862 }
863 return ret;
864 }
865
866 public ImageTypeSpecifier getRawImageType(int imageIndex)
867 throws IOException {
868 setThreadLock();
869 try {
870 if (currentImage != imageIndex) {
871 cbLock.check();
872
873 readHeader(imageIndex, true);
874 }
875
876
877 return getImageType(colorSpaceCode).getType();
878 } finally {
879 clearThreadLock();
880 }
881 }
882
883 public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex)
884 throws IOException {
885 setThreadLock();
886 try {
887 return getImageTypesOnThread(imageIndex);
888 } finally {
889 clearThreadLock();
890 }
891 }
892
893 private Iterator<ImageTypeSpecifier> getImageTypesOnThread(int imageIndex)
894 throws IOException {
895 if (currentImage != imageIndex) {
896 cbLock.check();
897 readHeader(imageIndex, true);
898 }
899
900
901
902
903
904
905
906
907
908
909
910
911
912 ImageTypeProducer raw = getImageType(colorSpaceCode);
913
914
915
916
917
918 ArrayList<ImageTypeProducer> list = new ArrayList<ImageTypeProducer>(1);
919
920 switch (colorSpaceCode) {
921 case JPEG.JCS_GRAYSCALE:
922 list.add(raw);
923 list.add(getImageType(JPEG.JCS_RGB));
924 break;
925 case JPEG.JCS_RGB:
926 list.add(raw);
927 list.add(getImageType(JPEG.JCS_GRAYSCALE));
928 list.add(getImageType(JPEG.JCS_YCC));
929 break;
930 case JPEG.JCS_RGBA:
931 list.add(raw);
932 break;
933 case JPEG.JCS_YCC:
934 if (raw != null) {
935 list.add(raw);
936 list.add(getImageType(JPEG.JCS_RGB));
937 }
938 break;
939 case JPEG.JCS_YCCA:
940 if (raw != null) {
941 list.add(raw);
942 }
943 break;
944 case JPEG.JCS_YCbCr:
945
946
947
948
949
950 list.add(getImageType(JPEG.JCS_RGB));
951
952 if (iccCS != null) {
953 list.add(new ImageTypeProducer() {
954 protected ImageTypeSpecifier produce() {
955 return ImageTypeSpecifier.createInterleaved
956 (iccCS,
957 JPEG.bOffsRGB,
958 DataBuffer.TYPE_BYTE,
959 false,
960 false);
961 }
962 });
963
964 }
965
966 list.add(getImageType(JPEG.JCS_GRAYSCALE));
967 list.add(getImageType(JPEG.JCS_YCC));
968 break;
969 case JPEG.JCS_YCbCrA:
970
971
972 list.add(getImageType(JPEG.JCS_RGBA));
973 break;
974 }
975
976 return new ImageTypeIterator(list.iterator());
977 }
978
979
980
981
982
983
984
985
986
987
988
989 private void checkColorConversion(BufferedImage image,
990 ImageReadParam param)
991 throws IIOException {
992
993
994
995
996
997 if (param != null) {
998 if ((param.getSourceBands() != null) ||
999 (param.getDestinationBands() != null)) {
1000
1001 return;
1002 }
1003 }
1004
1005
1006
1007
1008
1009
1010 ColorModel cm = image.getColorModel();
1011
1012 if (cm instanceof IndexColorModel) {
1013 throw new IIOException("IndexColorModel not supported");
1014 }
1015
1016
1017
1018 ColorSpace cs = cm.getColorSpace();
1019 int csType = cs.getType();
1020 convert = null;
1021 switch (outColorSpaceCode) {
1022 case JPEG.JCS_GRAYSCALE:
1023 if (csType == ColorSpace.TYPE_RGB) {
1024
1025 setOutColorSpace(structPointer, JPEG.JCS_RGB);
1026
1027
1028 outColorSpaceCode = JPEG.JCS_RGB;
1029 numComponents = 3;
1030 } else if (csType != ColorSpace.TYPE_GRAY) {
1031 throw new IIOException("Incompatible color conversion");
1032 }
1033 break;
1034 case JPEG.JCS_RGB:
1035 if (csType == ColorSpace.TYPE_GRAY) {
1036 if (colorSpaceCode == JPEG.JCS_YCbCr) {
1037
1038 setOutColorSpace(structPointer, JPEG.JCS_GRAYSCALE);
1039
1040
1041 outColorSpaceCode = JPEG.JCS_GRAYSCALE;
1042 numComponents = 1;
1043 }
1044 } else if ((iccCS != null) &&
1045 (cm.getNumComponents() == numComponents) &&
1046 (cs != iccCS)) {
1047
1048
1049 convert = new ColorConvertOp(iccCS, cs, null);
1050
1051 } else if ((iccCS == null) &&
1052 (!cs.isCS_sRGB()) &&
1053 (cm.getNumComponents() == numComponents)) {
1054
1055 convert = new ColorConvertOp(JPEG.JCS.sRGB, cs, null);
1056 } else if (csType != ColorSpace.TYPE_RGB) {
1057 throw new IIOException("Incompatible color conversion");
1058 }
1059 break;
1060 case JPEG.JCS_RGBA:
1061
1062 if ((csType != ColorSpace.TYPE_RGB) ||
1063 (cm.getNumComponents() != numComponents)) {
1064 throw new IIOException("Incompatible color conversion");
1065 }
1066 break;
1067 case JPEG.JCS_YCC:
1068 {
1069 ColorSpace YCC = JPEG.JCS.getYCC();
1070 if (YCC == null) {
1071 throw new IIOException("Incompatible color conversion");
1072 }
1073 if ((cs != YCC) &&
1074 (cm.getNumComponents() == numComponents)) {
1075 convert = new ColorConvertOp(YCC, cs, null);
1076 }
1077 }
1078 break;
1079 case JPEG.JCS_YCCA:
1080 {
1081 ColorSpace YCC = JPEG.JCS.getYCC();
1082
1083 if ((YCC == null) ||
1084 (cs != YCC) ||
1085 (cm.getNumComponents() != numComponents)) {
1086 throw new IIOException("Incompatible color conversion");
1087 }
1088 }
1089 break;
1090 default:
1091
1092 throw new IIOException("Incompatible color conversion");
1093 }
1094 }
1095
1096
1097
1098
1099
1100 private native void setOutColorSpace(long structPointer, int id);
1101
1102
1103
1104 public ImageReadParam getDefaultReadParam() {
1105 return new JPEGImageReadParam();
1106 }
1107
1108 public IIOMetadata getStreamMetadata() throws IOException {
1109 setThreadLock();
1110 try {
1111 if (!tablesOnlyChecked) {
1112 cbLock.check();
1113 checkTablesOnly();
1114 }
1115 return streamMetadata;
1116 } finally {
1117 clearThreadLock();
1118 }
1119 }
1120
1121 public IIOMetadata getImageMetadata(int imageIndex)
1122 throws IOException {
1123 setThreadLock();
1124 try {
1125
1126
1127
1128 if ((imageMetadataIndex == imageIndex)
1129 && (imageMetadata != null)) {
1130 return imageMetadata;
1131 }
1132
1133 cbLock.check();
1134
1135 gotoImage(imageIndex);
1136
1137 imageMetadata = new JPEGMetadata(false, false, iis, this);
1138
1139 imageMetadataIndex = imageIndex;
1140
1141 return imageMetadata;
1142 } finally {
1143 clearThreadLock();
1144 }
1145 }
1146
1147 public BufferedImage read(int imageIndex, ImageReadParam param)
1148 throws IOException {
1149 setThreadLock();
1150 try {
1151 cbLock.check();
1152 try {
1153 readInternal(imageIndex, param, false);
1154 } catch (RuntimeException e) {
1155 resetLibraryState(structPointer);
1156 throw e;
1157 } catch (IOException e) {
1158 resetLibraryState(structPointer);
1159 throw e;
1160 }
1161
1162 BufferedImage ret = image;
1163 image = null;
1164 return ret;
1165 } finally {
1166 clearThreadLock();
1167 }
1168 }
1169
1170 private Raster readInternal(int imageIndex,
1171 ImageReadParam param,
1172 boolean wantRaster) throws IOException {
1173 readHeader(imageIndex, false);
1174
1175 WritableRaster imRas = null;
1176 int numImageBands = 0;
1177
1178 if (!wantRaster){
1179
1180 Iterator<ImageTypeSpecifier> imageTypes = getImageTypes(imageIndex);
1181 if (imageTypes.hasNext() == false) {
1182 throw new IIOException("Unsupported Image Type");
1183 }
1184
1185 image = getDestination(param, imageTypes, width, height);
1186 imRas = image.getRaster();
1187
1188
1189
1190 numImageBands = image.getSampleModel().getNumBands();
1191
1192
1193
1194
1195
1196
1197 checkColorConversion(image, param);
1198
1199
1200 checkReadParamBandSettings(param, numComponents, numImageBands);
1201 } else {
1202
1203
1204 setOutColorSpace(structPointer, colorSpaceCode);
1205 image = null;
1206 }
1207
1208
1209
1210
1211
1212
1213
1214
1215 int [] srcBands = JPEG.bandOffsets[numComponents-1];
1216 int numRasterBands = (wantRaster ? numComponents : numImageBands);
1217 destinationBands = null;
1218
1219 Rectangle srcROI = new Rectangle(0, 0, 0, 0);
1220 destROI = new Rectangle(0, 0, 0, 0);
1221 computeRegions(param, width, height, image, srcROI, destROI);
1222
1223 int periodX = 1;
1224 int periodY = 1;
1225
1226 minProgressivePass = 0;
1227 maxProgressivePass = Integer.MAX_VALUE;
1228
1229 if (param != null) {
1230 periodX = param.getSourceXSubsampling();
1231 periodY = param.getSourceYSubsampling();
1232
1233 int[] sBands = param.getSourceBands();
1234 if (sBands != null) {
1235 srcBands = sBands;
1236 numRasterBands = srcBands.length;
1237 }
1238 if (!wantRaster) {
1239 destinationBands = param.getDestinationBands();
1240 }
1241
1242 minProgressivePass = param.getSourceMinProgressivePass();
1243 maxProgressivePass = param.getSourceMaxProgressivePass();
1244
1245 if (param instanceof JPEGImageReadParam) {
1246 JPEGImageReadParam jparam = (JPEGImageReadParam) param;
1247 if (jparam.areTablesSet()) {
1248 abbrevQTables = jparam.getQTables();
1249 abbrevDCHuffmanTables = jparam.getDCHuffmanTables();
1250 abbrevACHuffmanTables = jparam.getACHuffmanTables();
1251 }
1252 }
1253 }
1254
1255 int lineSize = destROI.width*numRasterBands;
1256
1257 buffer = new DataBufferByte(lineSize);
1258
1259 int [] bandOffs = JPEG.bandOffsets[numRasterBands-1];
1260
1261 raster = Raster.createInterleavedRaster(buffer,
1262 destROI.width, 1,
1263 lineSize,
1264 numRasterBands,
1265 bandOffs,
1266 null);
1267
1268
1269
1270 if (wantRaster) {
1271 target = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
1272 destROI.width,
1273 destROI.height,
1274 lineSize,
1275 numRasterBands,
1276 bandOffs,
1277 null);
1278 } else {
1279 target = imRas;
1280 }
1281 int [] bandSizes = target.getSampleModel().getSampleSize();
1282 for (int i = 0; i < bandSizes.length; i++) {
1283 if (bandSizes[i] <= 0 || bandSizes[i] > 8) {
1284 throw new IIOException("Illegal band size: should be 0 < size <= 8");
1285 }
1286 }
1287
1288
1289
1290
1291
1292
1293
1294
1295 boolean callbackUpdates = ((updateListeners != null)
1296 || (progressListeners != null));
1297
1298
1299 initProgressData();
1300
1301
1302 if (imageIndex == imageMetadataIndex) {
1303 knownPassCount = 0;
1304 for (Iterator<MarkerSegment> iter =
1305 imageMetadata.markerSequence.iterator(); iter.hasNext();) {
1306 if (iter.next() instanceof SOSMarkerSegment) {
1307 knownPassCount++;
1308 }
1309 }
1310 }
1311 progInterval = Math.max((target.getHeight()-1) / 20, 1);
1312 if (knownPassCount > 0) {
1313 progInterval *= knownPassCount;
1314 } else if (maxProgressivePass != Integer.MAX_VALUE) {
1315 progInterval *= (maxProgressivePass - minProgressivePass + 1);
1316 }
1317
1318 if (debug) {
1319 System.out.println("**** Read Data *****");
1320 System.out.println("numRasterBands is " + numRasterBands);
1321 System.out.print("srcBands:");
1322 for (int i = 0; i<srcBands.length;i++)
1323 System.out.print(" " + srcBands[i]);
1324 System.out.println();
1325 System.out.println("destination bands is " + destinationBands);
1326 if (destinationBands != null) {
1327 for (int i = 0; i < destinationBands.length; i++) {
1328 System.out.print(" " + destinationBands[i]);
1329 }
1330 System.out.println();
1331 }
1332 System.out.println("sourceROI is " + srcROI);
1333 System.out.println("destROI is " + destROI);
1334 System.out.println("periodX is " + periodX);
1335 System.out.println("periodY is " + periodY);
1336 System.out.println("minProgressivePass is " + minProgressivePass);
1337 System.out.println("maxProgressivePass is " + maxProgressivePass);
1338 System.out.println("callbackUpdates is " + callbackUpdates);
1339 }
1340
1341
1342
1343
1344
1345
1346
1347 clearNativeReadAbortFlag(structPointer);
1348 processImageStarted(currentImage);
1349
1350
1351
1352
1353
1354 boolean aborted = readImage(imageIndex,
1355 structPointer,
1356 buffer.getData(),
1357 numRasterBands,
1358 srcBands,
1359 bandSizes,
1360 srcROI.x, srcROI.y,
1361 srcROI.width, srcROI.height,
1362 periodX, periodY,
1363 abbrevQTables,
1364 abbrevDCHuffmanTables,
1365 abbrevACHuffmanTables,
1366 minProgressivePass, maxProgressivePass,
1367 callbackUpdates);
1368
1369 if (aborted) {
1370 processReadAborted();
1371 } else {
1372 processImageComplete();
1373 }
1374
1375 return target;
1376
1377 }
1378
1379
1380
1381
1382
1383
1384
1385 private void acceptPixels(int y, boolean progressive) {
1386 if (convert != null) {
1387 convert.filter(raster, raster);
1388 }
1389 target.setRect(destROI.x, destROI.y + y, raster);
1390
1391 cbLock.lock();
1392 try {
1393 processImageUpdate(image,
1394 destROI.x, destROI.y+y,
1395 raster.getWidth(), 1,
1396 1, 1,
1397 destinationBands);
1398 if ((y > 0) && (y%progInterval == 0)) {
1399 int height = target.getHeight()-1;
1400 float percentOfPass = ((float)y)/height;
1401 if (progressive) {
1402 if (knownPassCount != UNKNOWN) {
1403 processImageProgress((pass + percentOfPass)*100.0F
1404 / knownPassCount);
1405 } else if (maxProgressivePass != Integer.MAX_VALUE) {
1406
1407 processImageProgress((pass + percentOfPass)*100.0F
1408 / (maxProgressivePass - minProgressivePass + 1));
1409 } else {
1410
1411
1412
1413
1414
1415
1416
1417
1418 int remainingPasses =
1419 Math.max(2, MIN_ESTIMATED_PASSES-pass);
1420 int totalPasses = pass + remainingPasses-1;
1421 progInterval = Math.max(height/20*totalPasses,
1422 totalPasses);
1423 if (y%progInterval == 0) {
1424 percentToDate = previousPassPercentage +
1425 (1.0F - previousPassPercentage)
1426 * (percentOfPass)/remainingPasses;
1427 if (debug) {
1428 System.out.print("pass= " + pass);
1429 System.out.print(", y= " + y);
1430 System.out.print(", progInt= " + progInterval);
1431 System.out.print(", % of pass: " + percentOfPass);
1432 System.out.print(", rem. passes: "
1433 + remainingPasses);
1434 System.out.print(", prev%: "
1435 + previousPassPercentage);
1436 System.out.print(", %ToDate: " + percentToDate);
1437 System.out.print(" ");
1438 }
1439 processImageProgress(percentToDate*100.0F);
1440 }
1441 }
1442 } else {
1443 processImageProgress(percentOfPass * 100.0F);
1444 }
1445 }
1446 } finally {
1447 cbLock.unlock();
1448 }
1449 }
1450
1451 private void initProgressData() {
1452 knownPassCount = UNKNOWN;
1453 pass = 0;
1454 percentToDate = 0.0F;
1455 previousPassPercentage = 0.0F;
1456 progInterval = 0;
1457 }
1458
1459 private void passStarted (int pass) {
1460 cbLock.lock();
1461 try {
1462 this.pass = pass;
1463 previousPassPercentage = percentToDate;
1464 processPassStarted(image,
1465 pass,
1466 minProgressivePass,
1467 maxProgressivePass,
1468 0, 0,
1469 1,1,
1470 destinationBands);
1471 } finally {
1472 cbLock.unlock();
1473 }
1474 }
1475
1476 private void passComplete () {
1477 cbLock.lock();
1478 try {
1479 processPassComplete(image);
1480 } finally {
1481 cbLock.unlock();
1482 }
1483 }
1484
1485 void thumbnailStarted(int thumbnailIndex) {
1486 cbLock.lock();
1487 try {
1488 processThumbnailStarted(currentImage, thumbnailIndex);
1489 } finally {
1490 cbLock.unlock();
1491 }
1492 }
1493
1494
1495 void thumbnailProgress(float percentageDone) {
1496 cbLock.lock();
1497 try {
1498 processThumbnailProgress(percentageDone);
1499 } finally {
1500 cbLock.unlock();
1501 }
1502 }
1503
1504
1505 void thumbnailComplete() {
1506 cbLock.lock();
1507 try {
1508 processThumbnailComplete();
1509 } finally {
1510 cbLock.unlock();
1511 }
1512 }
1513
1514
1515
1516
1517 private native boolean readImage(int imageIndex,
1518 long structPointer,
1519 byte [] buffer,
1520 int numRasterBands,
1521 int [] srcBands,
1522 int [] bandSizes,
1523 int sourceXOffset, int sourceYOffset,
1524 int sourceWidth, int sourceHeight,
1525 int periodX, int periodY,
1526 JPEGQTable [] abbrevQTables,
1527 JPEGHuffmanTable [] abbrevDCHuffmanTables,
1528 JPEGHuffmanTable [] abbrevACHuffmanTables,
1529 int minProgressivePass,
1530 int maxProgressivePass,
1531 boolean wantUpdates);
1532
1533
1534
1535
1536
1537 private native void clearNativeReadAbortFlag(long structPointer);
1538
1539 public void abort() {
1540 setThreadLock();
1541 try {
1542
1543
1544
1545
1546
1547 super.abort();
1548 abortRead(structPointer);
1549 } finally {
1550 clearThreadLock();
1551 }
1552 }
1553
1554
1555 private native void abortRead(long structPointer);
1556
1557
1558 private native void resetLibraryState(long structPointer);
1559
1560 public boolean canReadRaster() {
1561 return true;
1562 }
1563
1564 public Raster readRaster(int imageIndex, ImageReadParam param)
1565 throws IOException {
1566 setThreadLock();
1567 Raster retval = null;
1568 try {
1569 cbLock.check();
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579 Point saveDestOffset = null;
1580 if (param != null) {
1581 saveDestOffset = param.getDestinationOffset();
1582 param.setDestinationOffset(new Point(0, 0));
1583 }
1584 retval = readInternal(imageIndex, param, true);
1585
1586 if (saveDestOffset != null) {
1587 target = target.createWritableTranslatedChild(saveDestOffset.x,
1588 saveDestOffset.y);
1589 }
1590 } catch (RuntimeException e) {
1591 resetLibraryState(structPointer);
1592 throw e;
1593 } catch (IOException e) {
1594 resetLibraryState(structPointer);
1595 throw e;
1596 } finally {
1597 clearThreadLock();
1598 }
1599 return retval;
1600 }
1601
1602 public boolean readerSupportsThumbnails() {
1603 return true;
1604 }
1605
1606 public int getNumThumbnails(int imageIndex) throws IOException {
1607 setThreadLock();
1608 try {
1609 cbLock.check();
1610
1611 getImageMetadata(imageIndex);
1612
1613 JFIFMarkerSegment jfif =
1614 (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1615 (JFIFMarkerSegment.class, true);
1616 int retval = 0;
1617 if (jfif != null) {
1618 retval = (jfif.thumb == null) ? 0 : 1;
1619 retval += jfif.extSegments.size();
1620 }
1621 return retval;
1622 } finally {
1623 clearThreadLock();
1624 }
1625 }
1626
1627 public int getThumbnailWidth(int imageIndex, int thumbnailIndex)
1628 throws IOException {
1629 setThreadLock();
1630 try {
1631 cbLock.check();
1632
1633 if ((thumbnailIndex < 0)
1634 || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
1635 throw new IndexOutOfBoundsException("No such thumbnail");
1636 }
1637
1638 JFIFMarkerSegment jfif =
1639 (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1640 (JFIFMarkerSegment.class, true);
1641 return jfif.getThumbnailWidth(thumbnailIndex);
1642 } finally {
1643 clearThreadLock();
1644 }
1645 }
1646
1647 public int getThumbnailHeight(int imageIndex, int thumbnailIndex)
1648 throws IOException {
1649 setThreadLock();
1650 try {
1651 cbLock.check();
1652
1653 if ((thumbnailIndex < 0)
1654 || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
1655 throw new IndexOutOfBoundsException("No such thumbnail");
1656 }
1657
1658 JFIFMarkerSegment jfif =
1659 (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1660 (JFIFMarkerSegment.class, true);
1661 return jfif.getThumbnailHeight(thumbnailIndex);
1662 } finally {
1663 clearThreadLock();
1664 }
1665 }
1666
1667 public BufferedImage readThumbnail(int imageIndex,
1668 int thumbnailIndex)
1669 throws IOException {
1670 setThreadLock();
1671 try {
1672 cbLock.check();
1673
1674 if ((thumbnailIndex < 0)
1675 || (thumbnailIndex >= getNumThumbnails(imageIndex))) {
1676 throw new IndexOutOfBoundsException("No such thumbnail");
1677 }
1678
1679 JFIFMarkerSegment jfif =
1680 (JFIFMarkerSegment) imageMetadata.findMarkerSegment
1681 (JFIFMarkerSegment.class, true);
1682 return jfif.getThumbnail(iis, thumbnailIndex, this);
1683 } finally {
1684 clearThreadLock();
1685 }
1686 }
1687
1688 private void resetInternalState() {
1689
1690 resetReader(structPointer);
1691
1692
1693 numImages = 0;
1694 imagePositions = new ArrayList<>();
1695 currentImage = -1;
1696 image = null;
1697 raster = null;
1698 target = null;
1699 buffer = null;
1700 destROI = null;
1701 destinationBands = null;
1702 streamMetadata = null;
1703 imageMetadata = null;
1704 imageMetadataIndex = -1;
1705 haveSeeked = false;
1706 tablesOnlyChecked = false;
1707 iccCS = null;
1708 initProgressData();
1709 }
1710
1711 public void reset() {
1712 setThreadLock();
1713 try {
1714 cbLock.check();
1715 super.reset();
1716 } finally {
1717 clearThreadLock();
1718 }
1719 }
1720
1721 private native void resetReader(long structPointer);
1722
1723 public void dispose() {
1724 setThreadLock();
1725 try {
1726 cbLock.check();
1727
1728 if (structPointer != 0) {
1729 disposerRecord.dispose();
1730 structPointer = 0;
1731 }
1732 } finally {
1733 clearThreadLock();
1734 }
1735 }
1736
1737 private static native void disposeReader(long structPointer);
1738
1739 private static class JPEGReaderDisposerRecord implements DisposerRecord {
1740 private long pData;
1741
1742 public JPEGReaderDisposerRecord(long pData) {
1743 this.pData = pData;
1744 }
1745
1746 public synchronized void dispose() {
1747 if (pData != 0) {
1748 disposeReader(pData);
1749 pData = 0;
1750 }
1751 }
1752 }
1753
1754 private Thread theThread = null;
1755 private int theLockCount = 0;
1756
1757 private synchronized void setThreadLock() {
1758 Thread currThread = Thread.currentThread();
1759 if (theThread != null) {
1760 if (theThread != currThread) {
1761
1762
1763 throw new IllegalStateException("Attempt to use instance of " +
1764 this + " locked on thread " +
1765 theThread + " from thread " +
1766 currThread);
1767 } else {
1768 theLockCount ++;
1769 }
1770 } else {
1771 theThread = currThread;
1772 theLockCount = 1;
1773 }
1774 }
1775
1776 private synchronized void clearThreadLock() {
1777 Thread currThread = Thread.currentThread();
1778 if (theThread == null || theThread != currThread) {
1779 throw new IllegalStateException("Attempt to clear thread lock " +
1780 " form wrong thread." +
1781 " Locked thread: " + theThread +
1782 "; current thread: " + currThread);
1783 }
1784 theLockCount --;
1785 if (theLockCount == 0) {
1786 theThread = null;
1787 }
1788 }
1789
1790 private CallBackLock cbLock = new CallBackLock();
1791
1792 private static class CallBackLock {
1793
1794 private State lockState;
1795
1796 CallBackLock() {
1797 lockState = State.Unlocked;
1798 }
1799
1800 void check() {
1801 if (lockState != State.Unlocked) {
1802 throw new IllegalStateException("Access to the reader is not allowed");
1803 }
1804 }
1805
1806 private void lock() {
1807 lockState = State.Locked;
1808 }
1809
1810 private void unlock() {
1811 lockState = State.Unlocked;
1812 }
1813
1814 private static enum State {
1815 Unlocked,
1816 Locked
1817 }
1818 }
1819 }
1820
1821
1822
1823
1824
1825 class ImageTypeIterator implements Iterator<ImageTypeSpecifier> {
1826 private Iterator<ImageTypeProducer> producers;
1827 private ImageTypeSpecifier theNext = null;
1828
1829 public ImageTypeIterator(Iterator<ImageTypeProducer> producers) {
1830 this.producers = producers;
1831 }
1832
1833 public boolean hasNext() {
1834 if (theNext != null) {
1835 return true;
1836 }
1837 if (!producers.hasNext()) {
1838 return false;
1839 }
1840 do {
1841 theNext = producers.next().getType();
1842 } while (theNext == null && producers.hasNext());
1843
1844 return (theNext != null);
1845 }
1846
1847 public ImageTypeSpecifier next() {
1848 if (theNext != null || hasNext()) {
1849 ImageTypeSpecifier t = theNext;
1850 theNext = null;
1851 return t;
1852 } else {
1853 throw new NoSuchElementException();
1854 }
1855 }
1856
1857 public void remove() {
1858 producers.remove();
1859 }
1860 }
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873 class ImageTypeProducer {
1874
1875 private ImageTypeSpecifier type = null;
1876 boolean failed = false;
1877 private int csCode;
1878
1879 public ImageTypeProducer(int csCode) {
1880 this.csCode = csCode;
1881 }
1882
1883 public ImageTypeProducer() {
1884 csCode = -1;
1885 }
1886
1887 public synchronized ImageTypeSpecifier getType() {
1888 if (!failed && type == null) {
1889 try {
1890 type = produce();
1891 } catch (Throwable e) {
1892 failed = true;
1893 }
1894 }
1895 return type;
1896 }
1897
1898 private static final ImageTypeProducer [] defaultTypes =
1899 new ImageTypeProducer [JPEG.NUM_JCS_CODES];
1900
1901 public static synchronized ImageTypeProducer getTypeProducer(int csCode) {
1902 if (csCode < 0 || csCode >= JPEG.NUM_JCS_CODES) {
1903 return null;
1904 }
1905 if (defaultTypes[csCode] == null) {
1906 defaultTypes[csCode] = new ImageTypeProducer(csCode);
1907 }
1908 return defaultTypes[csCode];
1909 }
1910
1911 protected ImageTypeSpecifier produce() {
1912 switch (csCode) {
1913 case JPEG.JCS_GRAYSCALE:
1914 return ImageTypeSpecifier.createFromBufferedImageType
1915 (BufferedImage.TYPE_BYTE_GRAY);
1916 case JPEG.JCS_YCbCr:
1917
1918 case JPEG.JCS_RGB:
1919 return ImageTypeSpecifier.createInterleaved(JPEG.JCS.sRGB,
1920 JPEG.bOffsRGB,
1921 DataBuffer.TYPE_BYTE,
1922 false,
1923 false);
1924 case JPEG.JCS_RGBA:
1925 return ImageTypeSpecifier.createPacked(JPEG.JCS.sRGB,
1926 0xff000000,
1927 0x00ff0000,
1928 0x0000ff00,
1929 0x000000ff,
1930 DataBuffer.TYPE_INT,
1931 false);
1932 case JPEG.JCS_YCC:
1933 if (JPEG.JCS.getYCC() != null) {
1934 return ImageTypeSpecifier.createInterleaved(
1935 JPEG.JCS.getYCC(),
1936 JPEG.bandOffsets[2],
1937 DataBuffer.TYPE_BYTE,
1938 false,
1939 false);
1940 } else {
1941 return null;
1942 }
1943 case JPEG.JCS_YCCA:
1944 if (JPEG.JCS.getYCC() != null) {
1945 return ImageTypeSpecifier.createInterleaved(
1946 JPEG.JCS.getYCC(),
1947 JPEG.bandOffsets[3],
1948 DataBuffer.TYPE_BYTE,
1949 true,
1950 false);
1951 } else {
1952 return null;
1953 }
1954 default:
1955 return null;
1956 }
1957 }
1958 }