View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.imaging.formats.jpeg;
18  
19  import java.awt.Dimension;
20  import java.awt.image.BufferedImage;
21  import java.io.IOException;
22  import java.io.PrintWriter;
23  import java.io.UnsupportedEncodingException;
24  import java.nio.ByteOrder;
25  import java.text.NumberFormat;
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.Collections;
29  import java.util.HashMap;
30  import java.util.List;
31  import java.util.Map;
32  
33  import org.apache.commons.imaging.ImageFormat;
34  import org.apache.commons.imaging.ImageFormats;
35  import org.apache.commons.imaging.ImageInfo;
36  import org.apache.commons.imaging.ImageParser;
37  import org.apache.commons.imaging.ImageReadException;
38  import org.apache.commons.imaging.common.ImageMetadata;
39  import org.apache.commons.imaging.common.bytesource.ByteSource;
40  import org.apache.commons.imaging.formats.jpeg.decoder.JpegDecoder;
41  import org.apache.commons.imaging.formats.jpeg.iptc.IptcParser;
42  import org.apache.commons.imaging.formats.jpeg.iptc.PhotoshopApp13Data;
43  import org.apache.commons.imaging.formats.jpeg.segments.App13Segment;
44  import org.apache.commons.imaging.formats.jpeg.segments.App14Segment;
45  import org.apache.commons.imaging.formats.jpeg.segments.App2Segment;
46  import org.apache.commons.imaging.formats.jpeg.segments.ComSegment;
47  import org.apache.commons.imaging.formats.jpeg.segments.DqtSegment;
48  import org.apache.commons.imaging.formats.jpeg.segments.GenericSegment;
49  import org.apache.commons.imaging.formats.jpeg.segments.JfifSegment;
50  import org.apache.commons.imaging.formats.jpeg.segments.Segment;
51  import org.apache.commons.imaging.formats.jpeg.segments.SofnSegment;
52  import org.apache.commons.imaging.formats.jpeg.segments.UnknownSegment;
53  import org.apache.commons.imaging.formats.jpeg.xmp.JpegXmpParser;
54  import org.apache.commons.imaging.formats.tiff.TiffField;
55  import org.apache.commons.imaging.formats.tiff.TiffImageMetadata;
56  import org.apache.commons.imaging.formats.tiff.TiffImageParser;
57  import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
58  import org.apache.commons.imaging.util.Debug;
59  
60  import static org.apache.commons.imaging.ImagingConstants.*;
61  import static org.apache.commons.imaging.common.BinaryFunctions.*;
62  
63  public class JpegImageParser extends ImageParser {
64      private static final String DEFAULT_EXTENSION = ".jpg";
65      private static final String[] ACCEPTED_EXTENSIONS = { ".jpg", ".jpeg", };
66      
67      public JpegImageParser() {
68          setByteOrder(ByteOrder.BIG_ENDIAN);
69          // setDebug(true);
70      }
71  
72      @Override
73      protected ImageFormat[] getAcceptedTypes() {
74          return new ImageFormat[] { ImageFormats.JPEG, //
75          };
76      }
77  
78      @Override
79      public String getName() {
80          return "Jpeg-Custom";
81      }
82  
83      @Override
84      public String getDefaultExtension() {
85          return DEFAULT_EXTENSION;
86      }
87  
88  
89      @Override
90      protected String[] getAcceptedExtensions() {
91          return ACCEPTED_EXTENSIONS;
92      }
93  
94      @Override
95      public final BufferedImage getBufferedImage(final ByteSource byteSource,
96              final Map<String, Object> params) throws ImageReadException, IOException {
97          final JpegDecoder jpegDecoder = new JpegDecoder();
98          return jpegDecoder.decode(byteSource);
99      }
100 
101     private boolean keepMarker(final int marker, final int[] markers) {
102         if (markers == null) {
103             return true;
104         }
105 
106         for (final int marker2 : markers) {
107             if (marker2 == marker) {
108                 return true;
109             }
110         }
111 
112         return false;
113     }
114 
115     public List<Segment> readSegments(final ByteSource byteSource,
116             final int[] markers, final boolean returnAfterFirst,
117             final boolean readEverything) throws ImageReadException, IOException {
118         final List<Segment> result = new ArrayList<>();
119         final JpegImageParser parser = this;
120         final int[] sofnSegments = {
121                 // kJFIFMarker,
122                 JpegConstants.SOF0_MARKER,
123                 JpegConstants.SOF1_MARKER,
124                 JpegConstants.SOF2_MARKER,
125                 JpegConstants.SOF3_MARKER,
126                 JpegConstants.SOF5_MARKER,
127                 JpegConstants.SOF6_MARKER,
128                 JpegConstants.SOF7_MARKER,
129                 JpegConstants.SOF9_MARKER,
130                 JpegConstants.SOF10_MARKER,
131                 JpegConstants.SOF11_MARKER,
132                 JpegConstants.SOF13_MARKER,
133                 JpegConstants.SOF14_MARKER,
134                 JpegConstants.SOF15_MARKER,
135         };
136 
137         final JpegUtils.Visitor visitor = new JpegUtils.Visitor() {
138             // return false to exit before reading image data.
139             @Override
140             public boolean beginSOS() {
141                 return false;
142             }
143 
144             @Override
145             public void visitSOS(final int marker, final byte[] markerBytes,
146                     final byte[] imageData) {
147                 // don't need image data
148             }
149 
150             // return false to exit traversal.
151             @Override
152             public boolean visitSegment(final int marker, final byte[] markerBytes,
153                     final int markerLength, final byte[] markerLengthBytes,
154                     final byte[] segmentData) throws ImageReadException, IOException {
155                 if (marker == JpegConstants.EOI_MARKER) {
156                     return false;
157                 }
158 
159                 // Debug.debug("visitSegment marker", marker);
160                 // // Debug.debug("visitSegment keepMarker(marker, markers)",
161                 // keepMarker(marker, markers));
162                 // Debug.debug("visitSegment keepMarker(marker, markers)",
163                 // keepMarker(marker, markers));
164 
165                 if (!keepMarker(marker, markers)) {
166                     return true;
167                 }
168 
169                 if (marker == JpegConstants.JPEG_APP13_MARKER) {
170                     // Debug.debug("app 13 segment data", segmentData.length);
171                     result.add(new App13Segment(parser, marker, segmentData));
172                 } else if (marker == JpegConstants.JPEG_APP14_MARKER) {
173                     result.add(new App14Segment(marker, segmentData));
174                 } else if (marker == JpegConstants.JPEG_APP2_MARKER) {
175                     result.add(new App2Segment(marker, segmentData));
176                 } else if (marker == JpegConstants.JFIF_MARKER) {
177                     result.add(new JfifSegment(marker, segmentData));
178                 } else if (Arrays.binarySearch(sofnSegments, marker) >= 0) {
179                     result.add(new SofnSegment(marker, segmentData));
180                 } else if (marker == JpegConstants.DQT_MARKER) {
181                     result.add(new DqtSegment(marker, segmentData));
182                 } else if ((marker >= JpegConstants.JPEG_APP1_MARKER)
183                         && (marker <= JpegConstants.JPEG_APP15_MARKER)) {
184                     result.add(new UnknownSegment(marker, segmentData));
185                 } else if (marker == JpegConstants.COM_MARKER) {
186                     result.add(new ComSegment(marker, segmentData));
187                 }
188 
189                 if (returnAfterFirst) {
190                     return false;
191                 }
192 
193                 return true;
194             }
195         };
196 
197         new JpegUtils().traverseJFIF(byteSource, visitor);
198 
199         return result;
200     }
201 
202     private byte[] assembleSegments(final List<App2Segment> segments) throws ImageReadException {
203         try {
204             return assembleSegments(segments, false);
205         } catch (final ImageReadException e) {
206             return assembleSegments(segments, true);
207         }
208     }
209 
210     private byte[] assembleSegments(final List<App2Segment> segments, final boolean startWithZero)
211             throws ImageReadException {
212         if (segments.isEmpty()) {
213             throw new ImageReadException("No App2 Segments Found.");
214         }
215 
216         final int markerCount = segments.get(0).numMarkers;
217 
218         if (segments.size() != markerCount) {
219             throw new ImageReadException("App2 Segments Missing.  Found: "
220                     + segments.size() + ", Expected: " + markerCount + ".");
221         }
222 
223         Collections.sort(segments);
224 
225         final int offset = startWithZero ? 0 : 1;
226 
227         int total = 0;
228         for (int i = 0; i < segments.size(); i++) {
229             final App2Segment segment = segments.get(i);
230 
231             if ((i + offset) != segment.curMarker) {
232                 dumpSegments(segments);
233                 throw new ImageReadException(
234                         "Incoherent App2 Segment Ordering.  i: " + i
235                                 + ", segment[" + i + "].curMarker: "
236                                 + segment.curMarker + ".");
237             }
238 
239             if (markerCount != segment.numMarkers) {
240                 dumpSegments(segments);
241                 throw new ImageReadException(
242                         "Inconsistent App2 Segment Count info.  markerCount: "
243                                 + markerCount + ", segment[" + i
244                                 + "].numMarkers: " + segment.numMarkers + ".");
245             }
246 
247             total += segment.getIccBytes().length;
248         }
249 
250         final byte[] result = new byte[total];
251         int progress = 0;
252 
253         for (final App2Segment segment : segments) {
254             System.arraycopy(segment.getIccBytes(), 0, result, progress, segment.getIccBytes().length);
255             progress += segment.getIccBytes().length;
256         }
257 
258         return result;
259     }
260 
261     private void dumpSegments(final List<? extends Segment> v) {
262         Debug.debug();
263         Debug.debug("dumpSegments: " + v.size());
264 
265         for (int i = 0; i < v.size(); i++) {
266             final App2Segment segment = (App2Segment) v.get(i);
267 
268             Debug.debug(i + ": " + segment.curMarker + " / " + segment.numMarkers);
269         }
270         Debug.debug();
271     }
272 
273     public List<Segment> readSegments(final ByteSource byteSource, final int[] markers,
274             final boolean returnAfterFirst) throws ImageReadException, IOException {
275         return readSegments(byteSource, markers, returnAfterFirst, false);
276     }
277 
278     @Override
279     public byte[] getICCProfileBytes(final ByteSource byteSource, final Map<String, Object> params)
280             throws ImageReadException, IOException {
281         final List<Segment> segments = readSegments(byteSource,
282                 new int[] { JpegConstants.JPEG_APP2_MARKER, }, false);
283 
284         final List<App2Segment> filtered = new ArrayList<>();
285         if (segments != null) {
286             // throw away non-icc profile app2 segments.
287             for (final Segment s : segments) {
288                 final App2Segment segment = (App2Segment) s;
289                 if (segment.getIccBytes() != null) {
290                     filtered.add(segment);
291                 }
292             }
293         }
294 
295         if (filtered.isEmpty()) {
296             return null;
297         }
298 
299         final byte[] bytes = assembleSegments(filtered);
300 
301         if (getDebug()) {
302             System.out.println("bytes" + ": " + bytes.length);
303         }
304 
305         if (getDebug()) {
306             System.out.println("");
307         }
308 
309         return bytes;
310     }
311 
312     @Override
313     public ImageMetadata getMetadata(final ByteSource byteSource, final Map<String, Object> params)
314             throws ImageReadException, IOException {
315         final TiffImageMetadata exif = getExifMetadata(byteSource, params);
316 
317         final JpegPhotoshopMetadata photoshop = getPhotoshopMetadata(byteSource,
318                 params);
319 
320         if (null == exif && null == photoshop) {
321             return null;
322         }
323 
324         return new JpegImageMetadata(photoshop, exif);
325     }
326 
327     public static boolean isExifAPP1Segment(final GenericSegment segment) {
328         return startsWith(segment.getSegmentData(), JpegConstants.EXIF_IDENTIFIER_CODE);
329     }
330 
331     private List<Segment> filterAPP1Segments(final List<Segment> segments) {
332         final List<Segment> result = new ArrayList<>();
333 
334         for (final Segment s : segments) {
335             final GenericSegment segment = (GenericSegment) s;
336             if (isExifAPP1Segment(segment)) {
337                 result.add(segment);
338             }
339         }
340 
341         return result;
342     }
343 
344     public TiffImageMetadata getExifMetadata(final ByteSource byteSource, Map<String, Object> params)
345             throws ImageReadException, IOException {
346         final byte[] bytes = getExifRawData(byteSource);
347         if (null == bytes) {
348             return null;
349         }
350 
351         if (params == null) {
352             params = new HashMap<>();
353         }
354         if (!params.containsKey(PARAM_KEY_READ_THUMBNAILS)) {
355             params.put(PARAM_KEY_READ_THUMBNAILS, Boolean.TRUE);
356         }
357 
358         return (TiffImageMetadata) new TiffImageParser().getMetadata(bytes,
359                 params);
360     }
361 
362     public byte[] getExifRawData(final ByteSource byteSource)
363             throws ImageReadException, IOException {
364         final List<Segment> segments = readSegments(byteSource,
365                 new int[] { JpegConstants.JPEG_APP1_MARKER, }, false);
366 
367         if ((segments == null) || (segments.isEmpty())) {
368             return null;
369         }
370 
371         final List<Segment> exifSegments = filterAPP1Segments(segments);
372         if (getDebug()) {
373             System.out.println("exif_segments.size" + ": "
374                     + exifSegments.size());
375         }
376 
377         // Debug.debug("segments", segments);
378         // Debug.debug("exifSegments", exifSegments);
379 
380         // TODO: concatenate if multiple segments, need example.
381         if (exifSegments.isEmpty()) {
382             return null;
383         }
384         if (exifSegments.size() > 1) {
385             throw new ImageReadException(
386                     "Imaging currently can't parse EXIF metadata split across multiple APP1 segments.  "
387                             + "Please send this image to the Imaging project.");
388         }
389 
390         final GenericSegment segment = (GenericSegment) exifSegments.get(0);
391         final byte[] bytes = segment.getSegmentData();
392 
393         // byte head[] = readBytearray("exif head", bytes, 0, 6);
394         //
395         // Debug.debug("head", head);
396 
397         return remainingBytes("trimmed exif bytes", bytes, 6);
398     }
399 
400     public boolean hasExifSegment(final ByteSource byteSource)
401             throws ImageReadException, IOException {
402         final boolean[] result = { false, };
403 
404         final JpegUtils.Visitor visitor = new JpegUtils.Visitor() {
405             // return false to exit before reading image data.
406             @Override
407             public boolean beginSOS() {
408                 return false;
409             }
410 
411             @Override
412             public void visitSOS(final int marker, final byte[] markerBytes,
413                     final byte[] imageData) {
414                 // don't need image data
415             }
416 
417             // return false to exit traversal.
418             @Override
419             public boolean visitSegment(final int marker, final byte[] markerBytes,
420                     final int markerLength, final byte[] markerLengthBytes,
421                     final byte[] segmentData) throws ImageReadException, IOException {
422                 if (marker == 0xffd9) {
423                     return false;
424                 }
425 
426                 if (marker == JpegConstants.JPEG_APP1_MARKER) {
427                     if (startsWith(segmentData, JpegConstants.EXIF_IDENTIFIER_CODE)) {
428                         result[0] = true;
429                         return false;
430                     }
431                 }
432 
433                 return true;
434             }
435         };
436 
437         new JpegUtils().traverseJFIF(byteSource, visitor);
438 
439         return result[0];
440     }
441 
442     public boolean hasIptcSegment(final ByteSource byteSource)
443             throws ImageReadException, IOException {
444         final boolean[] result = { false, };
445 
446         final JpegUtils.Visitor visitor = new JpegUtils.Visitor() {
447             // return false to exit before reading image data.
448             @Override
449             public boolean beginSOS() {
450                 return false;
451             }
452 
453             @Override
454             public void visitSOS(final int marker, final byte[] markerBytes,
455                     final byte[] imageData) {
456                 // don't need image data
457             }
458 
459             // return false to exit traversal.
460             @Override
461             public boolean visitSegment(final int marker, final byte[] markerBytes,
462                     final int markerLength, final byte[] markerLengthBytes,
463                     final byte[] segmentData) throws ImageReadException, IOException {
464                 if (marker == 0xffd9) {
465                     return false;
466                 }
467 
468                 if (marker == JpegConstants.JPEG_APP13_MARKER) {
469                     if (new IptcParser().isPhotoshopJpegSegment(segmentData)) {
470                         result[0] = true;
471                         return false;
472                     }
473                 }
474 
475                 return true;
476             }
477         };
478 
479         new JpegUtils().traverseJFIF(byteSource, visitor);
480 
481         return result[0];
482     }
483 
484     public boolean hasXmpSegment(final ByteSource byteSource)
485             throws ImageReadException, IOException {
486         final boolean[] result = { false, };
487 
488         final JpegUtils.Visitor visitor = new JpegUtils.Visitor() {
489             // return false to exit before reading image data.
490             @Override
491             public boolean beginSOS() {
492                 return false;
493             }
494 
495             @Override
496             public void visitSOS(final int marker, final byte[] markerBytes,
497                     final byte[] imageData) {
498                 // don't need image data
499             }
500 
501             // return false to exit traversal.
502             @Override
503             public boolean visitSegment(final int marker, final byte[] markerBytes,
504                     final int markerLength, final byte[] markerLengthBytes,
505                     final byte[] segmentData) throws ImageReadException, IOException {
506                 if (marker == 0xffd9) {
507                     return false;
508                 }
509 
510                 if (marker == JpegConstants.JPEG_APP1_MARKER) {
511                     if (new JpegXmpParser().isXmpJpegSegment(segmentData)) {
512                         result[0] = true;
513                         return false;
514                     }
515                 }
516 
517                 return true;
518             }
519         };
520         new JpegUtils().traverseJFIF(byteSource, visitor);
521 
522         return result[0];
523     }
524 
525     /**
526      * Extracts embedded XML metadata as XML string.
527      * <p>
528      * 
529      * @param byteSource
530      *            File containing image data.
531      * @param params
532      *            Map of optional parameters, defined in ImagingConstants.
533      * @return Xmp Xml as String, if present. Otherwise, returns null.
534      */
535     @Override
536     public String getXmpXml(final ByteSource byteSource, final Map<String, Object> params)
537             throws ImageReadException, IOException {
538 
539         final List<String> result = new ArrayList<>();
540 
541         final JpegUtils.Visitor visitor = new JpegUtils.Visitor() {
542             // return false to exit before reading image data.
543             @Override
544             public boolean beginSOS() {
545                 return false;
546             }
547 
548             @Override
549             public void visitSOS(final int marker, final byte[] markerBytes,
550                     final byte[] imageData) {
551                 // don't need image data
552             }
553 
554             // return false to exit traversal.
555             @Override
556             public boolean visitSegment(final int marker, final byte[] markerBytes,
557                     final int markerLength, final byte[] markerLengthBytes,
558                     final byte[] segmentData) throws ImageReadException, IOException {
559                 if (marker == 0xffd9) {
560                     return false;
561                 }
562 
563                 if (marker == JpegConstants.JPEG_APP1_MARKER) {
564                     if (new JpegXmpParser().isXmpJpegSegment(segmentData)) {
565                         result.add(new JpegXmpParser().parseXmpJpegSegment(segmentData));
566                         return false;
567                     }
568                 }
569 
570                 return true;
571             }
572         };
573         new JpegUtils().traverseJFIF(byteSource, visitor);
574 
575         if (result.isEmpty()) {
576             return null;
577         }
578         if (result.size() > 1) {
579             throw new ImageReadException(
580                     "Jpeg file contains more than one XMP segment.");
581         }
582         return result.get(0);
583     }
584 
585     public JpegPhotoshopMetadata getPhotoshopMetadata(final ByteSource byteSource,
586             final Map<String, Object> params) throws ImageReadException, IOException {
587         final List<Segment> segments = readSegments(byteSource,
588                 new int[] { JpegConstants.JPEG_APP13_MARKER, }, false);
589 
590         if ((segments == null) || (segments.isEmpty())) {
591             return null;
592         }
593 
594         PhotoshopApp13Data photoshopApp13Data = null;
595 
596         for (final Segment s : segments) {
597             final App13Segment segment = (App13Segment) s;
598 
599             final PhotoshopApp13Data data = segment.parsePhotoshopSegment(params);
600             if (data != null && photoshopApp13Data != null) {
601                 throw new ImageReadException(
602                         "Jpeg contains more than one Photoshop App13 segment.");
603             }
604 
605             photoshopApp13Data = data;
606         }
607 
608         if (null == photoshopApp13Data) {
609             return null;
610         }
611         return new JpegPhotoshopMetadata(photoshopApp13Data);
612     }
613 
614     @Override
615     public Dimension getImageSize(final ByteSource byteSource, final Map<String, Object> params)
616             throws ImageReadException, IOException {
617         final List<Segment> segments = readSegments(byteSource, new int[] {
618                 // kJFIFMarker,
619                 JpegConstants.SOF0_MARKER,
620                 JpegConstants.SOF1_MARKER,
621                 JpegConstants.SOF2_MARKER,
622                 JpegConstants.SOF3_MARKER,
623                 JpegConstants.SOF5_MARKER,
624                 JpegConstants.SOF6_MARKER,
625                 JpegConstants.SOF7_MARKER,
626                 JpegConstants.SOF9_MARKER,
627                 JpegConstants.SOF10_MARKER,
628                 JpegConstants.SOF11_MARKER,
629                 JpegConstants.SOF13_MARKER,
630                 JpegConstants.SOF14_MARKER,
631                 JpegConstants.SOF15_MARKER,
632 
633         }, true);
634 
635         if ((segments == null) || (segments.isEmpty())) {
636             throw new ImageReadException("No JFIF Data Found.");
637         }
638 
639         if (segments.size() > 1) {
640             throw new ImageReadException("Redundant JFIF Data Found.");
641         }
642 
643         final SofnSegment fSOFNSegment = (SofnSegment) segments.get(0);
644 
645         return new Dimension(fSOFNSegment.width, fSOFNSegment.height);
646     }
647 
648     @Override
649     public ImageInfo getImageInfo(final ByteSource byteSource, final Map<String, Object> params)
650             throws ImageReadException, IOException {
651         // List allSegments = readSegments(byteSource, null, false);
652 
653         final List<Segment> SOF_segments = readSegments(byteSource, new int[] {
654                 // kJFIFMarker,
655 
656                 JpegConstants.SOF0_MARKER,
657                 JpegConstants.SOF1_MARKER,
658                 JpegConstants.SOF2_MARKER,
659                 JpegConstants.SOF3_MARKER,
660                 JpegConstants.SOF5_MARKER,
661                 JpegConstants.SOF6_MARKER,
662                 JpegConstants.SOF7_MARKER,
663                 JpegConstants.SOF9_MARKER,
664                 JpegConstants.SOF10_MARKER,
665                 JpegConstants.SOF11_MARKER,
666                 JpegConstants.SOF13_MARKER,
667                 JpegConstants.SOF14_MARKER,
668                 JpegConstants.SOF15_MARKER,
669 
670         }, false);
671 
672         if (SOF_segments == null) {
673             throw new ImageReadException("No SOFN Data Found.");
674         }
675 
676         // if (SOF_segments.size() != 1)
677         // System.out.println("Incoherent SOFN Data Found: "
678         // + SOF_segments.size());
679 
680         final List<Segment> jfifSegments = readSegments(byteSource,
681                 new int[] { JpegConstants.JFIF_MARKER, }, true);
682 
683         final SofnSegment fSOFNSegment = (SofnSegment) SOF_segments.get(0);
684         // SofnSegment fSOFNSegment = (SofnSegment) findSegment(segments,
685         // SOFNmarkers);
686 
687         if (fSOFNSegment == null) {
688             throw new ImageReadException("No SOFN Data Found.");
689         }
690 
691         final int width = fSOFNSegment.width;
692         final int height = fSOFNSegment.height;
693 
694         JfifSegment jfifSegment = null;
695 
696         if ((jfifSegments != null) && (!jfifSegments.isEmpty())) {
697             jfifSegment = (JfifSegment) jfifSegments.get(0);
698         }
699 
700         final List<Segment> app14Segments = readSegments(byteSource, new int[] { JpegConstants.JPEG_APP14_MARKER}, true);
701         App14Segment app14Segment = null;
702         if (app14Segments != null && !app14Segments.isEmpty()) {
703             app14Segment = (App14Segment) app14Segments.get(0);
704         }
705         
706         // JfifSegment fTheJFIFSegment = (JfifSegment) findSegment(segments,
707         // kJFIFMarker);
708 
709         double xDensity = -1.0;
710         double yDensity = -1.0;
711         double unitsPerInch = -1.0;
712         // int JFIF_major_version;
713         // int JFIF_minor_version;
714         String formatDetails;
715 
716         if (jfifSegment != null) {
717             xDensity = jfifSegment.xDensity;
718             yDensity = jfifSegment.yDensity;
719             final int densityUnits = jfifSegment.densityUnits;
720             // JFIF_major_version = fTheJFIFSegment.JFIF_major_version;
721             // JFIF_minor_version = fTheJFIFSegment.JFIF_minor_version;
722 
723             formatDetails = "Jpeg/JFIF v." + jfifSegment.jfifMajorVersion + "."
724                     + jfifSegment.jfifMinorVersion;
725 
726             switch (densityUnits) {
727             case 0:
728                 break;
729             case 1: // inches
730                 unitsPerInch = 1.0;
731                 break;
732             case 2: // cms
733                 unitsPerInch = 2.54;
734                 break;
735             default:
736                 break;
737             }
738         } else {
739             final JpegImageMetadata metadata = (JpegImageMetadata) getMetadata(
740                     byteSource, params);
741 
742             if (metadata != null) {
743                 {
744                     final TiffField field = metadata.findEXIFValue(TiffTagConstants.TIFF_TAG_XRESOLUTION);
745                     if (field != null) {
746                         xDensity = ((Number) field.getValue()).doubleValue();
747                     }
748                 }
749                 {
750                     final TiffField field = metadata.findEXIFValue(TiffTagConstants.TIFF_TAG_YRESOLUTION);
751                     if (field != null) {
752                         yDensity = ((Number) field.getValue()).doubleValue();
753                     }
754                 }
755                 {
756                     final TiffField field = metadata.findEXIFValue(TiffTagConstants.TIFF_TAG_RESOLUTION_UNIT);
757                     if (field != null) {
758                         final int densityUnits = ((Number) field.getValue()).intValue();
759 
760                         switch (densityUnits) {
761                         case 1:
762                             break;
763                         case 2: // inches
764                             unitsPerInch = 1.0;
765                             break;
766                         case 3: // cms
767                             unitsPerInch = 2.54;
768                             break;
769                         default:
770                             break;
771                         }
772                     }
773 
774                 }
775             }
776 
777             formatDetails = "Jpeg/DCM";
778 
779         }
780 
781         int physicalHeightDpi = -1;
782         float physicalHeightInch = -1;
783         int physicalWidthDpi = -1;
784         float physicalWidthInch = -1;
785 
786         if (unitsPerInch > 0) {
787             physicalWidthDpi = (int) Math.round(xDensity * unitsPerInch);
788             physicalWidthInch = (float) (width / (xDensity * unitsPerInch));
789             physicalHeightDpi = (int) Math.round(yDensity * unitsPerInch);
790             physicalHeightInch = (float) (height / (yDensity * unitsPerInch));
791         }
792 
793         final List<String> comments = new ArrayList<>();
794         final List<Segment> commentSegments = readSegments(byteSource,
795                 new int[] { JpegConstants.COM_MARKER}, false);
796         for (final Segment commentSegment : commentSegments) {
797             final ComSegment comSegment = (ComSegment) commentSegment;
798             String comment = "";
799             try {
800                 comment = new String(comSegment.getComment(), "UTF-8");
801             } catch (final UnsupportedEncodingException cannotHappen) { // NOPMD - can't happen
802             }
803             comments.add(comment);
804         }
805 
806         final int numberOfComponents = fSOFNSegment.numberOfComponents;
807         final int precision = fSOFNSegment.precision;
808 
809         final int bitsPerPixel = numberOfComponents * precision;
810         final ImageFormat format = ImageFormats.JPEG;
811         final String formatName = "JPEG (Joint Photographic Experts Group) Format";
812         final String mimeType = "image/jpeg";
813         // we ought to count images, but don't yet.
814         final int numberOfImages = 1;
815         // not accurate ... only reflects first
816         final boolean progressive = fSOFNSegment.marker == JpegConstants.SOF2_MARKER;
817 
818         boolean transparent = false;
819         final boolean usesPalette = false; // TODO: inaccurate.
820         
821         // See http://docs.oracle.com/javase/6/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html#color
822         ImageInfo.ColorType colorType = ImageInfo.ColorType.UNKNOWN;
823         // Some images have both JFIF/APP0 and APP14.
824         // JFIF is meant to win but in them APP14 is clearly right, so make it win.
825         if (app14Segment != null && app14Segment.isAdobeJpegSegment()) {
826             final int colorTransform = app14Segment.getAdobeColorTransform();
827             if (colorTransform == App14Segment.ADOBE_COLOR_TRANSFORM_UNKNOWN) { 
828                 if (numberOfComponents == 3) {
829                     colorType = ImageInfo.ColorType.RGB;
830                 } else if (numberOfComponents == 4) {
831                     colorType = ImageInfo.ColorType.CMYK;
832                 }
833             } else if (colorTransform == App14Segment.ADOBE_COLOR_TRANSFORM_YCbCr) {
834                 colorType = ImageInfo.ColorType.YCbCr;
835             } else if (colorTransform == App14Segment.ADOBE_COLOR_TRANSFORM_YCCK) {
836                 colorType = ImageInfo.ColorType.YCCK;
837             }
838         } else if (jfifSegment != null) {
839             if (numberOfComponents == 1) {
840                 colorType = ImageInfo.ColorType.GRAYSCALE;
841             } else if (numberOfComponents == 3) {
842                 colorType = ImageInfo.ColorType.YCbCr;
843             }
844         } else {
845             if (numberOfComponents == 1) {
846                 colorType = ImageInfo.ColorType.GRAYSCALE;
847             } else if (numberOfComponents == 2) {
848                 colorType = ImageInfo.ColorType.GRAYSCALE;
849                 transparent = true;
850             } else if (numberOfComponents == 3 || numberOfComponents == 4) {
851                 boolean have1 = false;
852                 boolean have2 = false;
853                 boolean have3 = false;
854                 boolean have4 = false;
855                 boolean haveOther = false;
856                 for (final SofnSegment.Component component : fSOFNSegment.getComponents()) {
857                     final int id = component.componentIdentifier;
858                     if (id == 1) {
859                         have1 = true;
860                     } else if (id == 2) {
861                         have2 = true;
862                     } else if (id == 3) {
863                         have3 = true;
864                     } else if (id == 4) {
865                         have4 = true;
866                     } else {
867                         haveOther = true;
868                     }
869                 }
870                 if (numberOfComponents == 3 && have1 && have2 && have3 && !have4 && !haveOther) {
871                     colorType = ImageInfo.ColorType.YCbCr;
872                 } else if (numberOfComponents == 4 && have1 && have2 && have3 && have4 && !haveOther) {
873                     colorType = ImageInfo.ColorType.YCbCr;
874                     transparent = true;
875                 } else {
876                     boolean haveR = false;
877                     boolean haveG = false;
878                     boolean haveB = false;
879                     boolean haveA = false;
880                     boolean haveC = false;
881                     boolean havec = false;
882                     boolean haveY = false;
883                     for (final SofnSegment.Component component : fSOFNSegment.getComponents()) {
884                         final int id = component.componentIdentifier;
885                         if (id == 'R') {
886                             haveR = true;
887                         } else if (id == 'G') {
888                             haveG = true;
889                         } else if (id == 'B') {
890                             haveB = true;
891                         } else if (id == 'A') {
892                             haveA = true;
893                         } else if (id == 'C') {
894                             haveC = true;
895                         } else if (id == 'c') {
896                             havec = true;
897                         } else if (id == 'Y') {
898                             haveY = true;
899                         }
900                     }
901                     if (haveR && haveG && haveB && !haveA && !haveC && !havec && !haveY) {
902                         colorType = ImageInfo.ColorType.RGB;
903                     } else if (haveR && haveG && haveB && haveA && !haveC && !havec && !haveY) {
904                         colorType = ImageInfo.ColorType.RGB;
905                         transparent = true;
906                     } else if (haveY && haveC && havec && !haveR && !haveG && !haveB && !haveA) {
907                         colorType = ImageInfo.ColorType.YCC;
908                     } else if (haveY && haveC && havec && haveA && !haveR && !haveG && !haveB) {
909                         colorType = ImageInfo.ColorType.YCC;
910                         transparent = true;
911                     } else {
912                         int minHorizontalSamplingFactor = Integer.MAX_VALUE;
913                         int maxHorizontalSmaplingFactor = Integer.MIN_VALUE;
914                         int minVerticalSamplingFactor = Integer.MAX_VALUE;
915                         int maxVerticalSamplingFactor = Integer.MIN_VALUE;
916                         for (final SofnSegment.Component component : fSOFNSegment.getComponents()) {
917                             if (minHorizontalSamplingFactor > component.horizontalSamplingFactor) {
918                                 minHorizontalSamplingFactor = component.horizontalSamplingFactor;
919                             }
920                             if (maxHorizontalSmaplingFactor < component.horizontalSamplingFactor) {
921                                 maxHorizontalSmaplingFactor = component.horizontalSamplingFactor;
922                             }
923                             if (minVerticalSamplingFactor > component.verticalSamplingFactor) {
924                                 minVerticalSamplingFactor = component.verticalSamplingFactor;
925                             }
926                             if (maxVerticalSamplingFactor < component.verticalSamplingFactor) {
927                                 maxVerticalSamplingFactor = component.verticalSamplingFactor;
928                             }
929                         }
930                         final boolean isSubsampled = (minHorizontalSamplingFactor != maxHorizontalSmaplingFactor) 
931                                 || (minVerticalSamplingFactor != maxVerticalSamplingFactor);
932                         if (numberOfComponents == 3) {
933                             if (isSubsampled) {
934                                 colorType = ImageInfo.ColorType.YCbCr;
935                             } else {
936                                 colorType = ImageInfo.ColorType.RGB;
937                             }
938                         } else if (numberOfComponents == 4) {
939                             if (isSubsampled) {
940                                 colorType = ImageInfo.ColorType.YCCK;
941                             } else {
942                                 colorType = ImageInfo.ColorType.CMYK;
943                             }
944                         }
945                     }
946                 }
947             }
948         }
949 
950         final ImageInfo.CompressionAlgorithm compressionAlgorithm = ImageInfo.CompressionAlgorithm.JPEG;
951 
952         return new ImageInfo(formatDetails, bitsPerPixel, comments,
953                 format, formatName, height, mimeType, numberOfImages,
954                 physicalHeightDpi, physicalHeightInch, physicalWidthDpi,
955                 physicalWidthInch, width, progressive, transparent,
956                 usesPalette, colorType, compressionAlgorithm);
957     }
958 
959     // public ImageInfo getImageInfo(ByteSource byteSource, Map params)
960     // throws ImageReadException, IOException
961     // {
962     //
963     // List allSegments = readSegments(byteSource, null, false);
964     //
965     // final int SOF_MARKERS[] = new int[]{
966     // SOF0_MARKER, SOF1_MARKER, SOF2_MARKER, SOF3_MARKER, SOF5_MARKER,
967     // SOF6_MARKER, SOF7_MARKER, SOF9_MARKER, SOF10_MARKER, SOF11_MARKER,
968     // SOF13_MARKER, SOF14_MARKER, SOF15_MARKER,
969     // };
970     //
971     // List sofMarkers = new ArrayList();
972     // for(int i=0;i<SOF_MARKERS.length;i++)
973     // sofMarkers.add(new Integer(SOF_MARKERS[i]));
974     // List SOFSegments = filterSegments(allSegments, sofMarkers);
975     // if (SOFSegments == null || SOFSegments.size()<1)
976     // throw new ImageReadException("No SOFN Data Found.");
977     //
978     // List jfifMarkers = new ArrayList();
979     // jfifMarkers.add(new Integer(JFIF_MARKER));
980     // List jfifSegments = filterSegments(allSegments, jfifMarkers);
981     //
982     // SofnSegment firstSOFNSegment = (SofnSegment) SOFSegments.get(0);
983     //
984     // int Width = firstSOFNSegment.width;
985     // int Height = firstSOFNSegment.height;
986     //
987     // JfifSegment jfifSegment = null;
988     //
989     // if (jfifSegments != null && jfifSegments.size() > 0)
990     // jfifSegment = (JfifSegment) jfifSegments.get(0);
991     //
992     // double x_density = -1.0;
993     // double y_density = -1.0;
994     // double units_per_inch = -1.0;
995     // // int JFIF_major_version;
996     // // int JFIF_minor_version;
997     // String FormatDetails;
998     //
999     // if (jfifSegment != null)
1000     // {
1001     // x_density = jfifSegment.xDensity;
1002     // y_density = jfifSegment.yDensity;
1003     // int density_units = jfifSegment.densityUnits;
1004     // // JFIF_major_version = fTheJFIFSegment.JFIF_major_version;
1005     // // JFIF_minor_version = fTheJFIFSegment.JFIF_minor_version;
1006     //
1007     // FormatDetails = "Jpeg/JFIF v." + jfifSegment.jfifMajorVersion
1008     // + "." + jfifSegment.jfifMinorVersion;
1009     //
1010     // switch (density_units)
1011     // {
1012     // case 0 :
1013     // break;
1014     // case 1 : // inches
1015     // units_per_inch = 1.0;
1016     // break;
1017     // case 2 : // cms
1018     // units_per_inch = 2.54;
1019     // break;
1020     // default :
1021     // break;
1022     // }
1023     // }
1024     // else
1025     // {
1026     // JpegImageMetadata metadata = (JpegImageMetadata) getMetadata(byteSource,
1027     // params);
1028     //
1029     // {
1030     // TiffField field = metadata
1031     // .findEXIFValue(TiffField.TIFF_TAG_XRESOLUTION);
1032     // if (field == null)
1033     // throw new ImageReadException("No XResolution");
1034     //
1035     // x_density = ((Number) field.getValue()).doubleValue();
1036     // }
1037     // {
1038     // TiffField field = metadata
1039     // .findEXIFValue(TiffField.TIFF_TAG_YRESOLUTION);
1040     // if (field == null)
1041     // throw new ImageReadException("No YResolution");
1042     //
1043     // y_density = ((Number) field.getValue()).doubleValue();
1044     // }
1045     // {
1046     // TiffField field = metadata
1047     // .findEXIFValue(TiffField.TIFF_TAG_RESOLUTION_UNIT);
1048     // if (field == null)
1049     // throw new ImageReadException("No ResolutionUnits");
1050     //
1051     // int density_units = ((Number) field.getValue()).intValue();
1052     //
1053     // switch (density_units)
1054     // {
1055     // case 1 :
1056     // break;
1057     // case 2 : // inches
1058     // units_per_inch = 1.0;
1059     // break;
1060     // case 3 : // cms
1061     // units_per_inch = 2.54;
1062     // break;
1063     // default :
1064     // break;
1065     // }
1066     //
1067     // }
1068     //
1069     // FormatDetails = "Jpeg/DCM";
1070     //
1071     // }
1072     //
1073     // int PhysicalHeightDpi = -1;
1074     // float PhysicalHeightInch = -1;
1075     // int PhysicalWidthDpi = -1;
1076     // float PhysicalWidthInch = -1;
1077     //
1078     // if (units_per_inch > 0)
1079     // {
1080     // PhysicalWidthDpi = (int) Math.round((double) x_density
1081     // / units_per_inch);
1082     // PhysicalWidthInch = (float) ((double) Width / (x_density *
1083     // units_per_inch));
1084     // PhysicalHeightDpi = (int) Math.round((double) y_density
1085     // * units_per_inch);
1086     // PhysicalHeightInch = (float) ((double) Height / (y_density *
1087     // units_per_inch));
1088     // }
1089     //
1090     // List Comments = new ArrayList();
1091     // // TODO: comments...
1092     //
1093     // int Number_of_components = firstSOFNSegment.numberOfComponents;
1094     // int Precision = firstSOFNSegment.precision;
1095     //
1096     // int BitsPerPixel = Number_of_components * Precision;
1097     // ImageFormat Format = ImageFormat.IMAGE_FORMAT_JPEG;
1098     // String FormatName = "JPEG (Joint Photographic Experts Group) Format";
1099     // String MimeType = "image/jpeg";
1100     // // we ought to count images, but don't yet.
1101     // int NumberOfImages = -1;
1102     // // not accurate ... only reflects first
1103     // boolean progressive = firstSOFNSegment.marker == SOF2_MARKER;
1104     //
1105     // boolean transparent = false; // TODO: inaccurate.
1106     // boolean usesPalette = false; // TODO: inaccurate.
1107     // int ColorType;
1108     // if (Number_of_components == 1)
1109     // ColorType = ImageInfo.ColorType.BW;
1110     // else if (Number_of_components == 3)
1111     // ColorType = ImageInfo.ColorType.RGB;
1112     // else if (Number_of_components == 4)
1113     // ColorType = ImageInfo.ColorType.CMYK;
1114     // else
1115     // ColorType = ImageInfo.ColorType.UNKNOWN;
1116     //
1117     // String compressionAlgorithm = ImageInfo.COMPRESSION_ALGORITHM_JPEG;
1118     //
1119     // ImageInfo result = new ImageInfo(FormatDetails, BitsPerPixel, Comments,
1120     // Format, FormatName, Height, MimeType, NumberOfImages,
1121     // PhysicalHeightDpi, PhysicalHeightInch, PhysicalWidthDpi,
1122     // PhysicalWidthInch, Width, progressive, transparent,
1123     // usesPalette, ColorType, compressionAlgorithm);
1124     //
1125     // return result;
1126     // }
1127 
1128     @Override
1129     public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource)
1130             throws ImageReadException, IOException {
1131         pw.println("jpeg.dumpImageFile");
1132 
1133         {
1134             final ImageInfo imageInfo = getImageInfo(byteSource);
1135             if (imageInfo == null) {
1136                 return false;
1137             }
1138 
1139             imageInfo.toString(pw, "");
1140         }
1141 
1142         pw.println("");
1143 
1144         {
1145             final List<Segment> segments = readSegments(byteSource, null, false);
1146 
1147             if (segments == null) {
1148                 throw new ImageReadException("No Segments Found.");
1149             }
1150 
1151             for (int d = 0; d < segments.size(); d++) {
1152 
1153                 final Segment segment = segments.get(d);
1154 
1155                 final NumberFormat nf = NumberFormat.getIntegerInstance();
1156                 // this.debugNumber("found, marker: ", marker, 4);
1157                 pw.println(d + ": marker: "
1158                         + Integer.toHexString(segment.marker) + ", "
1159                         + segment.getDescription() + " (length: "
1160                         + nf.format(segment.length) + ")");
1161                 segment.dump(pw);
1162             }
1163 
1164             pw.println("");
1165         }
1166 
1167         return true;
1168     }
1169 
1170 }