|
1 /* |
|
2 * Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. Oracle designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Oracle in the LICENSE file that accompanied this code. |
|
10 * |
|
11 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
14 * version 2 for more details (a copy is included in the LICENSE file that |
|
15 * accompanied this code). |
|
16 * |
|
17 * You should have received a copy of the GNU General Public License version |
|
18 * 2 along with this work; if not, write to the Free Software Foundation, |
|
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
20 * |
|
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
22 * or visit www.oracle.com if you need additional information or have any |
|
23 * questions. |
|
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 * The following variable contains a pointer to the IJG library |
|
68 * structure for this reader. It is assigned in the constructor |
|
69 * and then is passed in to every native call. It is set to 0 |
|
70 * by dispose to avoid disposing twice. |
|
71 */ |
|
72 private long structPointer = 0; |
|
73 |
|
74 /** The input stream we read from */ |
|
75 private ImageInputStream iis = null; |
|
76 |
|
77 /** |
|
78 * List of stream positions for images, reinitialized every time |
|
79 * a new input source is set. |
|
80 */ |
|
81 private List<Long> imagePositions = null; |
|
82 |
|
83 /** |
|
84 * The number of images in the stream, or 0. |
|
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 // The following warnings are converted to strings when used |
|
102 // as keys to get localized resources from JPEGImageReaderResources |
|
103 // and its children. |
|
104 |
|
105 /** |
|
106 * Warning code to be passed to warningOccurred to indicate |
|
107 * that the EOI marker is missing from the end of the stream. |
|
108 * This usually signals that the stream is corrupted, but |
|
109 * everything up to the last MCU should be usable. |
|
110 */ |
|
111 protected static final int WARNING_NO_EOI = 0; |
|
112 |
|
113 /** |
|
114 * Warning code to be passed to warningOccurred to indicate |
|
115 * that a JFIF segment was encountered inside a JFXX JPEG |
|
116 * thumbnail and is being ignored. |
|
117 */ |
|
118 protected static final int WARNING_NO_JFIF_IN_THUMB = 1; |
|
119 |
|
120 /** |
|
121 * Warning code to be passed to warningOccurred to indicate |
|
122 * that embedded ICC profile is invalid and will be ignored. |
|
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 * Image index of image for which header information |
|
130 * is available. |
|
131 */ |
|
132 private int currentImage = -1; |
|
133 |
|
134 // The following is copied out from C after reading the header. |
|
135 // Unlike metadata, which may never be retrieved, we need this |
|
136 // if we are to read an image at all. |
|
137 |
|
138 /** Set by setImageData native code callback */ |
|
139 private int width; |
|
140 /** Set by setImageData native code callback */ |
|
141 private int height; |
|
142 /** |
|
143 * Set by setImageData native code callback. A modified |
|
144 * IJG+NIFTY colorspace code. |
|
145 */ |
|
146 private int colorSpaceCode; |
|
147 /** |
|
148 * Set by setImageData native code callback. A modified |
|
149 * IJG+NIFTY colorspace code. |
|
150 */ |
|
151 private int outColorSpaceCode; |
|
152 /** Set by setImageData native code callback */ |
|
153 private int numComponents; |
|
154 /** Set by setImageData native code callback */ |
|
155 private ColorSpace iccCS = null; |
|
156 |
|
157 |
|
158 /** If we need to post-convert in Java, convert with this op */ |
|
159 private ColorConvertOp convert = null; |
|
160 |
|
161 /** The image we are going to fill */ |
|
162 private BufferedImage image = null; |
|
163 |
|
164 /** An intermediate Raster to hold decoded data */ |
|
165 private WritableRaster raster = null; |
|
166 |
|
167 /** A view of our target Raster that we can setRect to */ |
|
168 private WritableRaster target = null; |
|
169 |
|
170 /** The databuffer for the above Raster */ |
|
171 private DataBufferByte buffer = null; |
|
172 |
|
173 /** The region in the destination where we will write pixels */ |
|
174 private Rectangle destROI = null; |
|
175 |
|
176 /** The list of destination bands, if any */ |
|
177 private int [] destinationBands = null; |
|
178 |
|
179 /** Stream metadata, cached, even when the stream is changed. */ |
|
180 private JPEGMetadata streamMetadata = null; |
|
181 |
|
182 /** Image metadata, valid for the imageMetadataIndex only. */ |
|
183 private JPEGMetadata imageMetadata = null; |
|
184 private int imageMetadataIndex = -1; |
|
185 |
|
186 /** |
|
187 * Set to true every time we seek in the stream; used to |
|
188 * invalidate the native buffer contents in C. |
|
189 */ |
|
190 private boolean haveSeeked = false; |
|
191 |
|
192 /** |
|
193 * Tables that have been read from a tables-only image at the |
|
194 * beginning of a stream. |
|
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 * Variables used by progress monitoring. |
|
205 */ |
|
206 private static final int UNKNOWN = -1; // Number of passes |
|
207 private static final int MIN_ESTIMATED_PASSES = 10; // IJG default |
|
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 * Set to true once stream has been checked for stream metadata |
|
216 */ |
|
217 private boolean tablesOnlyChecked = false; |
|
218 |
|
219 /** The referent to be registered with the Disposer. */ |
|
220 private Object disposerReferent = new Object(); |
|
221 |
|
222 /** The DisposerRecord that handles the actual disposal of this reader. */ |
|
223 private DisposerRecord disposerRecord; |
|
224 |
|
225 /** Sets up static C structures. */ |
|
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 /** Sets up per-reader C structure and returns a pointer to it. */ |
|
238 private native long initJPEGImageReader(); |
|
239 |
|
240 /** |
|
241 * Called by the native code or other classes to signal a warning. |
|
242 * The code is used to lookup a localized message to be used when |
|
243 * sending warnings to listeners. |
|
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 * The library has it's own error facility that emits warning messages. |
|
261 * This routine is called by the native code when it has already |
|
262 * formatted a string for output. |
|
263 * XXX For truly complete localization of all warning messages, |
|
264 * the sun_jpeg_output_message routine in the native code should |
|
265 * send only the codes and parameters to a method here in Java, |
|
266 * which will then format and send the warnings, using localized |
|
267 * strings. This method will have to deal with all the parameters |
|
268 * and formats (%u with possibly large numbers, %02d, %02x, etc.) |
|
269 * that actually occur in the JPEG library. For now, this prevents |
|
270 * library warnings from being printed to stderr. |
|
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; // Always works |
|
293 setSource(structPointer); |
|
294 } finally { |
|
295 clearThreadLock(); |
|
296 } |
|
297 } |
|
298 |
|
299 /** |
|
300 * This method is called from native code in order to fill |
|
301 * native input buffer. |
|
302 * |
|
303 * We block any attempt to change the reading state during this |
|
304 * method, in order to prevent a corruption of the native decoder |
|
305 * state. |
|
306 * |
|
307 * @return number of bytes read from the stream. |
|
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 * This method is called from the native code in order to |
|
320 * skip requested number of bytes in the input stream. |
|
321 * |
|
322 * @param n |
|
323 * @return |
|
324 * @throws IOException |
|
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 // Read the first header |
|
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 // This reads the tables-only image twice, once from C |
|
355 // and once from Java, but only if ignoreMetadata is false |
|
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 // Now we are at the first image if there are any, so add it |
|
368 // to the list |
|
369 if (hasNextImage()) { |
|
370 imagePositions.add(iis.getStreamPosition()); |
|
371 } |
|
372 } else { // Not tables only, so add original pos to the list |
|
373 imagePositions.add(savePos); |
|
374 // And set current image since we've read it now |
|
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 { // locked thread |
|
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 // Otherwise we have to read the entire stream |
|
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 // FALL THROUGH to decrement buffer vars |
|
440 // This first set doesn't have a length |
|
441 case 0: // not a marker, just a data 0xff |
|
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 // All the others have a length |
|
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; // length includes itself |
|
463 buffer.skipData(length); |
|
464 } |
|
465 } |
|
466 |
|
467 |
|
468 iis.reset(); |
|
469 |
|
470 return numImages; |
|
471 } |
|
472 |
|
473 return -1; // Search is necessary for JPEG |
|
474 } |
|
475 |
|
476 /** |
|
477 * Sets the input stream to the start of the requested image. |
|
478 * <pre> |
|
479 * @exception IllegalStateException if the input source has not been |
|
480 * set. |
|
481 * @exception IndexOutOfBoundsException if the supplied index is |
|
482 * out of bounds. |
|
483 * </pre> |
|
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 // read to start of image, saving positions |
|
499 // First seek to the last position we already have, and skip the |
|
500 // entire image |
|
501 Long pos = imagePositions.get(imagePositions.size()-1); |
|
502 iis.seek(pos.longValue()); |
|
503 skipImage(); |
|
504 // Now add all intervening positions, skipping images |
|
505 for (int index = imagePositions.size(); |
|
506 index <= imageIndex; |
|
507 index++) { |
|
508 // Is there an image? |
|
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 } // Otherwise we are where we want to be |
|
520 } |
|
521 } |
|
522 |
|
523 if (seekForwardOnly) { |
|
524 minIndex = imageIndex; |
|
525 } |
|
526 |
|
527 haveSeeked = true; // No way is native buffer still valid |
|
528 } |
|
529 |
|
530 /** |
|
531 * Skip over a complete image in the stream, leaving the stream |
|
532 * positioned such that the next byte to be read is the first |
|
533 * byte of the next image. For JPEG, this means that we read |
|
534 * until we encounter an EOI marker or until the end of the stream. |
|
535 * We can find data same as EOI marker in some headers |
|
536 * or comments, so we have to skip bytes related to these headers. |
|
537 * If the stream ends before an EOI marker is encountered, |
|
538 * an IndexOutOfBoundsException is thrown. |
|
539 */ |
|
540 private void skipImage() throws IOException { |
|
541 if (debug) { |
|
542 System.out.println("skipImage called"); |
|
543 } |
|
544 // verify if image starts with an SOI marker |
|
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 // markers which doesn't contain length data |
|
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 // markers which contains length data |
|
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 // read length of header from next 2 bytes |
|
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 // length contains already read 2 bytes |
|
636 length -= 2; |
|
637 } else { |
|
638 throw new IndexOutOfBoundsException(IOOBE); |
|
639 } |
|
640 } else { |
|
641 throw new IndexOutOfBoundsException(IOOBE); |
|
642 } |
|
643 // skip the length specified in marker |
|
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 * Returns {@code true} if there is an image beyond |
|
661 * the current stream position. Does not disturb the |
|
662 * stream position. |
|
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 // We hit the end of the stream before we hit an SOI, so no image |
|
686 iis.reset(); |
|
687 if (debug) { |
|
688 System.out.println("false"); |
|
689 } |
|
690 return false; |
|
691 } |
|
692 |
|
693 /** |
|
694 * Push back the given number of bytes to the input stream. |
|
695 * Called by the native code at the end of each image so |
|
696 * that the next one can be identified from Java. |
|
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 // The buffer is clear after this, so no need to set haveSeeked. |
|
706 } finally { |
|
707 cbLock.unlock(); |
|
708 } |
|
709 } |
|
710 |
|
711 /** |
|
712 * Reads header information for the given image, if possible. |
|
713 */ |
|
714 private void readHeader(int imageIndex, boolean reset) |
|
715 throws IOException { |
|
716 gotoImage(imageIndex); |
|
717 readNativeHeader(reset); // Ignore return |
|
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 * Read in the header information starting from the current |
|
730 * stream position, returning {@code true} if the |
|
731 * header was a tables-only image. After this call, the |
|
732 * native IJG decompression struct will contain the image |
|
733 * information required by most query calls below |
|
734 * (e.g. getWidth, getHeight, etc.), if the header was not |
|
735 * a tables-only image. |
|
736 * If reset is {@code true}, the state of the IJG |
|
737 * object is reset so that it can read a header again. |
|
738 * This happens automatically if the header was a tables-only |
|
739 * image. |
|
740 */ |
|
741 private native boolean readImageHeader(long structPointer, |
|
742 boolean clearBuffer, |
|
743 boolean reset) |
|
744 throws IOException; |
|
745 |
|
746 /* |
|
747 * Called by the native code whenever an image header has been |
|
748 * read. Whether we read metadata or not, we always need this |
|
749 * information, so it is passed back independently of |
|
750 * metadata, which may never be read. |
|
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 * Color profile data seems to be invalid. |
|
775 * Ignore this profile. |
|
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 * At the moment we can't rely on the ColorSpace.equals() |
|
795 * and ICC_Profile.equals() because they do not detect |
|
796 * the case when two profiles are created from same data. |
|
797 * |
|
798 * So, we have to do data comparison in order to avoid |
|
799 * creation of different ColorSpace instances for the same |
|
800 * embedded data. |
|
801 */ |
|
802 if (oldData == null || |
|
803 !java.util.Arrays.equals(oldData, newData)) |
|
804 { |
|
805 iccCS = new ICC_ColorSpace(newProfile); |
|
806 // verify new color space |
|
807 try { |
|
808 float[] colors = iccCS.fromRGB(new float[] {1f, 0f, 0f}); |
|
809 } catch (CMMException e) { |
|
810 /* |
|
811 * Embedded profile seems to be corrupted. |
|
812 * Ignore this profile. |
|
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 /////////// Color Conversion and Image Types |
|
852 |
|
853 /** |
|
854 * Return an ImageTypeSpecifier corresponding to the given |
|
855 * color space code, or null if the color space is unsupported. |
|
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 // Returns null if it can't be represented |
|
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 // We return an iterator containing the default, any |
|
901 // conversions that the library provides, and |
|
902 // all the other default types with the same number |
|
903 // of components, as we can do these as a post-process. |
|
904 // As we convert Rasters rather than images, images |
|
905 // with alpha cannot be converted in a post-process. |
|
906 |
|
907 // If this image can't be interpreted, this method |
|
908 // returns an empty Iterator. |
|
909 |
|
910 // Get the raw ITS, if there is one. Note that this |
|
911 // won't always be the same as the default. |
|
912 ImageTypeProducer raw = getImageType(colorSpaceCode); |
|
913 |
|
914 // Given the encoded colorspace, build a list of ITS's |
|
915 // representing outputs you could handle starting |
|
916 // with the default. |
|
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) { // Might be null if PYCC.pf not installed |
|
935 list.add(raw); |
|
936 list.add(getImageType(JPEG.JCS_RGB)); |
|
937 } |
|
938 break; |
|
939 case JPEG.JCS_YCCA: |
|
940 if (raw != null) { // Might be null if PYCC.pf not installed |
|
941 list.add(raw); |
|
942 } |
|
943 break; |
|
944 case JPEG.JCS_YCbCr: |
|
945 // As there is no YCbCr ColorSpace, we can't support |
|
946 // the raw type. |
|
947 |
|
948 // due to 4705399, use RGB as default in order to avoid |
|
949 // slowing down of drawing operations with result image. |
|
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, // Assume it's for RGB |
|
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: // Default is to convert to RGBA |
|
970 // As there is no YCbCr ColorSpace, we can't support |
|
971 // the raw type. |
|
972 list.add(getImageType(JPEG.JCS_RGBA)); |
|
973 break; |
|
974 } |
|
975 |
|
976 return new ImageTypeIterator(list.iterator()); |
|
977 } |
|
978 |
|
979 /** |
|
980 * Checks the implied color conversion between the stream and |
|
981 * the target image, altering the IJG output color space if necessary. |
|
982 * If a java color conversion is required, then this sets up |
|
983 * {@code convert}. |
|
984 * If bands are being rearranged at all (either source or destination |
|
985 * bands are specified in the param), then the default color |
|
986 * conversions are assumed to be correct. |
|
987 * Throws an IIOException if there is no conversion available. |
|
988 */ |
|
989 private void checkColorConversion(BufferedImage image, |
|
990 ImageReadParam param) |
|
991 throws IIOException { |
|
992 |
|
993 // If we are rearranging channels at all, the default |
|
994 // conversions remain in place. If the user wants |
|
995 // raw channels then he should do this while reading |
|
996 // a Raster. |
|
997 if (param != null) { |
|
998 if ((param.getSourceBands() != null) || |
|
999 (param.getDestinationBands() != null)) { |
|
1000 // Accept default conversions out of decoder, silently |
|
1001 return; |
|
1002 } |
|
1003 } |
|
1004 |
|
1005 // XXX - We do not currently support any indexed color models, |
|
1006 // though we could, as IJG will quantize for us. |
|
1007 // This is a performance and memory-use issue, as |
|
1008 // users can read RGB and then convert to indexed in Java. |
|
1009 |
|
1010 ColorModel cm = image.getColorModel(); |
|
1011 |
|
1012 if (cm instanceof IndexColorModel) { |
|
1013 throw new IIOException("IndexColorModel not supported"); |
|
1014 } |
|
1015 |
|
1016 // Now check the ColorSpace type against outColorSpaceCode |
|
1017 // We may want to tweak the default |
|
1018 ColorSpace cs = cm.getColorSpace(); |
|
1019 int csType = cs.getType(); |
|
1020 convert = null; |
|
1021 switch (outColorSpaceCode) { |
|
1022 case JPEG.JCS_GRAYSCALE: // Its gray in the file |
|
1023 if (csType == ColorSpace.TYPE_RGB) { // We want RGB |
|
1024 // IJG can do this for us more efficiently |
|
1025 setOutColorSpace(structPointer, JPEG.JCS_RGB); |
|
1026 // Update java state according to changes |
|
1027 // in the native part of decoder. |
|
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: // IJG wants to go to RGB |
|
1035 if (csType == ColorSpace.TYPE_GRAY) { // We want gray |
|
1036 if (colorSpaceCode == JPEG.JCS_YCbCr) { |
|
1037 // If the jpeg space is YCbCr, IJG can do it |
|
1038 setOutColorSpace(structPointer, JPEG.JCS_GRAYSCALE); |
|
1039 // Update java state according to changes |
|
1040 // in the native part of decoder. |
|
1041 outColorSpaceCode = JPEG.JCS_GRAYSCALE; |
|
1042 numComponents = 1; |
|
1043 } |
|
1044 } else if ((iccCS != null) && |
|
1045 (cm.getNumComponents() == numComponents) && |
|
1046 (cs != iccCS)) { |
|
1047 // We have an ICC profile but it isn't used in the dest |
|
1048 // image. So convert from the profile cs to the target cs |
|
1049 convert = new ColorConvertOp(iccCS, cs, null); |
|
1050 // Leave IJG conversion in place; we still need it |
|
1051 } else if ((iccCS == null) && |
|
1052 (!cs.isCS_sRGB()) && |
|
1053 (cm.getNumComponents() == numComponents)) { |
|
1054 // Target isn't sRGB, so convert from sRGB to the target |
|
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 // No conversions available; image must be RGBA |
|
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) { // We can't do YCC at all |
|
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 // No conversions available; image must be YCCA |
|
1083 if ((YCC == null) || // We can't do YCC at all |
|
1084 (cs != YCC) || |
|
1085 (cm.getNumComponents() != numComponents)) { |
|
1086 throw new IIOException("Incompatible color conversion"); |
|
1087 } |
|
1088 } |
|
1089 break; |
|
1090 default: |
|
1091 // Anything else we can't handle at all |
|
1092 throw new IIOException("Incompatible color conversion"); |
|
1093 } |
|
1094 } |
|
1095 |
|
1096 /** |
|
1097 * Set the IJG output space to the given value. The library will |
|
1098 * perform the appropriate colorspace conversions. |
|
1099 */ |
|
1100 private native void setOutColorSpace(long structPointer, int id); |
|
1101 |
|
1102 /////// End of Color Conversion & Image Types |
|
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 // imageMetadataIndex will always be either a valid index or |
|
1126 // -1, in which case imageMetadata will not be null. |
|
1127 // So we can leave checking imageIndex for gotoImage. |
|
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; // don't keep a reference here |
|
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 // Can we read this image? |
|
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 // The destination may still be incompatible. |
|
1189 |
|
1190 numImageBands = image.getSampleModel().getNumBands(); |
|
1191 |
|
1192 // Check whether we can handle any implied color conversion |
|
1193 |
|
1194 // Throws IIOException if the stream and the image are |
|
1195 // incompatible, and sets convert if a java conversion |
|
1196 // is necessary |
|
1197 checkColorConversion(image, param); |
|
1198 |
|
1199 // Check the source and destination bands in the param |
|
1200 checkReadParamBandSettings(param, numComponents, numImageBands); |
|
1201 } else { |
|
1202 // Set the output color space equal to the input colorspace |
|
1203 // This disables all conversions |
|
1204 setOutColorSpace(structPointer, colorSpaceCode); |
|
1205 image = null; |
|
1206 } |
|
1207 |
|
1208 // Create an intermediate 1-line Raster that will hold the decoded, |
|
1209 // subsampled, clipped, band-selected image data in a single |
|
1210 // byte-interleaved buffer. The above transformations |
|
1211 // will occur in C for performance. Every time this Raster |
|
1212 // is filled we will call back to acceptPixels below to copy |
|
1213 // this to whatever kind of buffer our image has. |
|
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) { // ignore dest bands for Raster |
|
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 // Now that we have the Raster we'll decode to, get a view of the |
|
1269 // target Raster that will permit a simple setRect for each scanline |
|
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 * If the process is sequential, and we have restart markers, |
|
1290 * we could skip to the correct restart marker, if the library |
|
1291 * lets us. That's an optimization to investigate later. |
|
1292 */ |
|
1293 |
|
1294 // Check for update listeners (don't call back if none) |
|
1295 boolean callbackUpdates = ((updateListeners != null) |
|
1296 || (progressListeners != null)); |
|
1297 |
|
1298 // Set up progression data |
|
1299 initProgressData(); |
|
1300 // if we have a metadata object, we can count the scans |
|
1301 // and set knownPassCount |
|
1302 if (imageIndex == imageMetadataIndex) { // We have metadata |
|
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 * All the Jpeg processing happens in native, we should clear |
|
1343 * abortFlag of imageIODataStruct in imageioJPEG.c. And we need to |
|
1344 * clear abortFlag because if in previous read() if we had called |
|
1345 * reader.abort() that will continue to be valid for present call also. |
|
1346 */ |
|
1347 clearNativeReadAbortFlag(structPointer); |
|
1348 processImageStarted(currentImage); |
|
1349 /* |
|
1350 * Note that getData disables acceleration on buffer, but it is |
|
1351 * just a 1-line intermediate data transfer buffer that will not |
|
1352 * affect the acceleration of the resulting image. |
|
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 * This method is called back from C when the intermediate Raster |
|
1381 * is full. The parameter indicates the scanline in the target |
|
1382 * Raster to which the intermediate Raster should be copied. |
|
1383 * After the copy, we notify update listeners. |
|
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 // Use the range of allowed progressive passes |
|
1407 processImageProgress((pass + percentOfPass)*100.0F |
|
1408 / (maxProgressivePass - minProgressivePass + 1)); |
|
1409 } else { |
|
1410 // Assume there are a minimum of MIN_ESTIMATED_PASSES |
|
1411 // and that there is always one more pass |
|
1412 // Compute the percentage as the percentage at the end |
|
1413 // of the previous pass, plus the percentage of this |
|
1414 // pass scaled to be the percentage of the total remaining, |
|
1415 // assuming a minimum of MIN_ESTIMATED_PASSES passes and |
|
1416 // that there is always one more pass. This is monotonic |
|
1417 // and asymptotic to 1.0, which is what we need. |
|
1418 int remainingPasses = // including this one |
|
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 // Provide access to protected superclass method |
|
1495 void thumbnailProgress(float percentageDone) { |
|
1496 cbLock.lock(); |
|
1497 try { |
|
1498 processThumbnailProgress(percentageDone); |
|
1499 } finally { |
|
1500 cbLock.unlock(); |
|
1501 } |
|
1502 } |
|
1503 |
|
1504 // Provide access to protected superclass method |
|
1505 void thumbnailComplete() { |
|
1506 cbLock.lock(); |
|
1507 try { |
|
1508 processThumbnailComplete(); |
|
1509 } finally { |
|
1510 cbLock.unlock(); |
|
1511 } |
|
1512 } |
|
1513 |
|
1514 /** |
|
1515 * Returns {@code true} if the read was aborted. |
|
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 * We should call clearNativeReadAbortFlag() before we start reading |
|
1535 * jpeg image as image processing happens at native side. |
|
1536 */ |
|
1537 private native void clearNativeReadAbortFlag(long structPointer); |
|
1538 |
|
1539 public void abort() { |
|
1540 setThreadLock(); |
|
1541 try { |
|
1542 /** |
|
1543 * NB: we do not check the call back lock here, |
|
1544 * we allow to abort the reader any time. |
|
1545 */ |
|
1546 |
|
1547 super.abort(); |
|
1548 abortRead(structPointer); |
|
1549 } finally { |
|
1550 clearThreadLock(); |
|
1551 } |
|
1552 } |
|
1553 |
|
1554 /** Set the C level abort flag. Keep it atomic for thread safety. */ |
|
1555 private native void abortRead(long structPointer); |
|
1556 |
|
1557 /** Resets library state when an exception occurred during a read. */ |
|
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 * This could be further optimized by not resetting the dest. |
|
1572 * offset and creating a translated raster in readInternal() |
|
1573 * (see bug 4994702 for more info). |
|
1574 */ |
|
1575 |
|
1576 // For Rasters, destination offset is logical, not physical, so |
|
1577 // set it to 0 before calling computeRegions, so that the destination |
|
1578 // region is not clipped. |
|
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 // Apply the destination offset, if any, as a logical offset |
|
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); // checks iis state for us |
|
1612 // Now check the jfif segments |
|
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 // Now we know that there is a jfif segment |
|
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 // Now we know that there is a jfif segment |
|
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 // Now we know that there is a jfif segment and that iis is good |
|
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 // reset C structures |
|
1690 resetReader(structPointer); |
|
1691 |
|
1692 // reset local Java structures |
|
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 // it looks like that this reader instance is used |
|
1762 // by multiple threads. |
|
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 * An internal helper class that wraps producer's iterator |
|
1823 * and extracts specifier instances on demand. |
|
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 * An internal helper class that provides means for deferred creation |
|
1864 * of ImageTypeSpecifier instance required to describe available |
|
1865 * destination types. |
|
1866 * |
|
1867 * This implementation only supports standard |
|
1868 * jpeg color spaces (defined by corresponding JCS color space code). |
|
1869 * |
|
1870 * To support other color spaces one can override produce() method to |
|
1871 * return custom instance of ImageTypeSpecifier. |
|
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; // undefined |
|
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 //there is no YCbCr raw type so by default we assume it as RGB |
|
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 } |