1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.imaging.formats.jpeg;
18
19 import static org.apache.commons.imaging.common.BinaryFunctions.remainingBytes;
20 import static org.apache.commons.imaging.common.BinaryFunctions.startsWith;
21
22 import java.awt.Dimension;
23 import java.awt.image.BufferedImage;
24 import java.io.IOException;
25 import java.io.PrintWriter;
26 import java.nio.charset.StandardCharsets;
27 import java.text.NumberFormat;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.List;
31 import java.util.logging.Level;
32 import java.util.logging.Logger;
33
34 import org.apache.commons.imaging.AbstractImageParser;
35 import org.apache.commons.imaging.ImageFormat;
36 import org.apache.commons.imaging.ImageFormats;
37 import org.apache.commons.imaging.ImageInfo;
38 import org.apache.commons.imaging.ImagingException;
39 import org.apache.commons.imaging.bytesource.ByteSource;
40 import org.apache.commons.imaging.common.Allocator;
41 import org.apache.commons.imaging.common.ImageMetadata;
42 import org.apache.commons.imaging.common.XmpEmbeddable;
43 import org.apache.commons.imaging.common.XmpImagingParameters;
44 import org.apache.commons.imaging.formats.jpeg.decoder.JpegDecoder;
45 import org.apache.commons.imaging.formats.jpeg.iptc.IptcParser;
46 import org.apache.commons.imaging.formats.jpeg.iptc.PhotoshopApp13Data;
47 import org.apache.commons.imaging.formats.jpeg.segments.AbstractSegment;
48 import org.apache.commons.imaging.formats.jpeg.segments.App13Segment;
49 import org.apache.commons.imaging.formats.jpeg.segments.App14Segment;
50 import org.apache.commons.imaging.formats.jpeg.segments.App2Segment;
51 import org.apache.commons.imaging.formats.jpeg.segments.ComSegment;
52 import org.apache.commons.imaging.formats.jpeg.segments.DqtSegment;
53 import org.apache.commons.imaging.formats.jpeg.segments.GenericSegment;
54 import org.apache.commons.imaging.formats.jpeg.segments.JfifSegment;
55 import org.apache.commons.imaging.formats.jpeg.segments.SofnSegment;
56 import org.apache.commons.imaging.formats.jpeg.segments.UnknownSegment;
57 import org.apache.commons.imaging.formats.jpeg.xmp.JpegXmpParser;
58 import org.apache.commons.imaging.formats.tiff.TiffField;
59 import org.apache.commons.imaging.formats.tiff.TiffImageMetadata;
60 import org.apache.commons.imaging.formats.tiff.TiffImageParser;
61 import org.apache.commons.imaging.formats.tiff.TiffImagingParameters;
62 import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
63 import org.apache.commons.imaging.internal.Debug;
64
65 public class JpegImageParser extends AbstractImageParser<JpegImagingParameters> implements XmpEmbeddable<JpegImagingParameters> {
66
67 private static final Logger LOGGER = Logger.getLogger(JpegImageParser.class.getName());
68
69 private static final String DEFAULT_EXTENSION = ImageFormats.JPEG.getDefaultExtension();
70 private static final String[] ACCEPTED_EXTENSIONS = ImageFormats.JPEG.getExtensions();
71
72 public static boolean isExifApp1Segment(final GenericSegment segment) {
73 return startsWith(segment.getSegmentData(), JpegConstants.EXIF_IDENTIFIER_CODE);
74 }
75
76 private byte[] assembleSegments(final List<App2Segment> segments) throws ImagingException {
77 try {
78 return assembleSegments(segments, false);
79 } catch (final ImagingException e) {
80 return assembleSegments(segments, true);
81 }
82 }
83
84 private byte[] assembleSegments(final List<App2Segment> segments, final boolean startWithZero) throws ImagingException {
85 if (segments.isEmpty()) {
86 throw new ImagingException("No App2 Segments Found.");
87 }
88
89 final int markerCount = segments.get(0).numMarkers;
90
91 if (segments.size() != markerCount) {
92 throw new ImagingException("App2 Segments Missing. Found: " + segments.size() + ", Expected: " + markerCount + ".");
93 }
94
95 segments.sort(null);
96
97 final int offset = startWithZero ? 0 : 1;
98
99 int total = 0;
100 for (int i = 0; i < segments.size(); i++) {
101 final App2Segment segment = segments.get(i);
102
103 if (i + offset != segment.curMarker) {
104 dumpSegments(segments);
105 throw new ImagingException("Incoherent App2 Segment Ordering. i: " + i + ", segment[" + i + "].curMarker: " + segment.curMarker + ".");
106 }
107
108 if (markerCount != segment.numMarkers) {
109 dumpSegments(segments);
110 throw new ImagingException(
111 "Inconsistent App2 Segment Count info. markerCount: " + markerCount + ", segment[" + i + "].numMarkers: " + segment.numMarkers + ".");
112 }
113
114 if (segment.getIccBytes() != null) {
115 total += segment.getIccBytes().length;
116 }
117 }
118
119 final byte[] result = Allocator.byteArray(total);
120 int progress = 0;
121
122 for (final App2Segment segment : segments) {
123 System.arraycopy(segment.getIccBytes(), 0, result, progress, segment.getIccBytes().length);
124 progress += segment.getIccBytes().length;
125 }
126
127 return result;
128 }
129
130 @Override
131 public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource) throws ImagingException, IOException {
132 pw.println("jpeg.dumpImageFile");
133
134 {
135 final ImageInfo imageInfo = getImageInfo(byteSource);
136 if (imageInfo == null) {
137 return false;
138 }
139
140 imageInfo.toString(pw, "");
141 }
142
143 pw.println("");
144
145 {
146 final List<AbstractSegment> abstractSegments = readSegments(byteSource, null, false);
147
148 if (abstractSegments == null) {
149 throw new ImagingException("No Segments Found.");
150 }
151
152 for (int d = 0; d < abstractSegments.size(); d++) {
153
154 final AbstractSegment abstractSegment = abstractSegments.get(d);
155
156 final NumberFormat nf = NumberFormat.getIntegerInstance();
157
158 pw.println(d + ": marker: " + Integer.toHexString(abstractSegment.marker) + ", " + abstractSegment.getDescription() + " (length: "
159 + nf.format(abstractSegment.length) + ")");
160 abstractSegment.dump(pw);
161 }
162
163 pw.println("");
164 }
165
166 return true;
167 }
168
169 private void dumpSegments(final List<? extends AbstractSegment> v) {
170 Debug.debug();
171 Debug.debug("dumpSegments: " + v.size());
172
173 for (int i = 0; i < v.size(); i++) {
174 final App2Segment segment = (App2Segment) v.get(i);
175
176 Debug.debug(i + ": " + segment.curMarker + " / " + segment.numMarkers);
177 }
178 Debug.debug();
179 }
180
181 private List<AbstractSegment> filterApp1Segments(final List<AbstractSegment> abstractSegments) {
182 final List<AbstractSegment> result = new ArrayList<>();
183
184 for (final AbstractSegment s : abstractSegments) {
185 final GenericSegment segment = (GenericSegment) s;
186 if (isExifApp1Segment(segment)) {
187 result.add(segment);
188 }
189 }
190
191 return result;
192 }
193
194 @Override
195 protected String[] getAcceptedExtensions() {
196 return ACCEPTED_EXTENSIONS;
197 }
198
199 @Override
200 protected ImageFormat[] getAcceptedTypes() {
201 return new ImageFormat[] { ImageFormats.JPEG,
202 };
203 }
204
205 @Override
206 public final BufferedImage getBufferedImage(final ByteSource byteSource, final JpegImagingParameters params) throws ImagingException, IOException {
207 final JpegDecoder jpegDecoder = new JpegDecoder();
208 return jpegDecoder.decode(byteSource);
209 }
210
211 @Override
212 public String getDefaultExtension() {
213 return DEFAULT_EXTENSION;
214 }
215
216 @Override
217 public JpegImagingParameters getDefaultParameters() {
218 return new JpegImagingParameters();
219 }
220
221 public TiffImageMetadata getExifMetadata(final ByteSource byteSource, TiffImagingParameters params) throws ImagingException, IOException {
222 final byte[] bytes = getExifRawData(byteSource);
223 if (null == bytes) {
224 return null;
225 }
226
227 if (params == null) {
228 params = new TiffImagingParameters();
229 }
230 params.setReadThumbnails(Boolean.TRUE);
231
232 return (TiffImageMetadata) new TiffImageParser().getMetadata(bytes, params);
233 }
234
235 public byte[] getExifRawData(final ByteSource byteSource) throws ImagingException, IOException {
236 final List<AbstractSegment> abstractSegments = readSegments(byteSource, new int[] { JpegConstants.JPEG_APP1_MARKER, }, false);
237
238 if (abstractSegments == null || abstractSegments.isEmpty()) {
239 return null;
240 }
241
242 final List<AbstractSegment> exifSegments = filterApp1Segments(abstractSegments);
243 if (LOGGER.isLoggable(Level.FINEST)) {
244 LOGGER.finest("exifSegments.size()" + ": " + exifSegments.size());
245 }
246
247
248
249
250
251 if (exifSegments.isEmpty()) {
252 return null;
253 }
254 if (exifSegments.size() > 1) {
255 throw new ImagingException(
256 "Imaging currently can't parse EXIF metadata split across multiple APP1 segments. " + "Please send this image to the Imaging project.");
257 }
258
259 final GenericSegment segment = (GenericSegment) exifSegments.get(0);
260 final byte[] bytes = segment.getSegmentData();
261
262
263
264
265
266 return remainingBytes("trimmed exif bytes", bytes, 6);
267 }
268
269 @Override
270 public byte[] getIccProfileBytes(final ByteSource byteSource, final JpegImagingParameters params) throws ImagingException, IOException {
271 final List<AbstractSegment> abstractSegments = readSegments(byteSource, new int[] { JpegConstants.JPEG_APP2_MARKER, }, false);
272
273 final List<App2Segment> filtered = new ArrayList<>();
274 if (abstractSegments != null) {
275
276 for (final AbstractSegment s : abstractSegments) {
277 final App2Segment segment = (App2Segment) s;
278 if (segment.getIccBytes() != null) {
279 filtered.add(segment);
280 }
281 }
282 }
283
284 if (filtered.isEmpty()) {
285 return null;
286 }
287
288 final byte[] bytes = assembleSegments(filtered);
289
290 if (LOGGER.isLoggable(Level.FINEST)) {
291 LOGGER.finest("bytes" + ": " + bytes.length);
292 }
293
294 return bytes;
295 }
296
297 @Override
298 public ImageInfo getImageInfo(final ByteSource byteSource, final JpegImagingParameters params) throws ImagingException, IOException {
299
300
301 final List<AbstractSegment> SOF_segments = readSegments(byteSource, new int[] {
302
303
304 JpegConstants.SOF0_MARKER, JpegConstants.SOF1_MARKER, JpegConstants.SOF2_MARKER, JpegConstants.SOF3_MARKER, JpegConstants.SOF5_MARKER,
305 JpegConstants.SOF6_MARKER, JpegConstants.SOF7_MARKER, JpegConstants.SOF9_MARKER, JpegConstants.SOF10_MARKER, JpegConstants.SOF11_MARKER,
306 JpegConstants.SOF13_MARKER, JpegConstants.SOF14_MARKER, JpegConstants.SOF15_MARKER,
307
308 }, false);
309
310 if (SOF_segments == null) {
311 throw new ImagingException("No SOFN Data Found.");
312 }
313
314
315
316
317
318 final List<AbstractSegment> jfifSegments = readSegments(byteSource, new int[] { JpegConstants.JFIF_MARKER, }, true);
319
320 final SofnSegment fSOFNSegment = (SofnSegment) SOF_segments.get(0);
321
322
323
324 if (fSOFNSegment == null) {
325 throw new ImagingException("No SOFN Data Found.");
326 }
327
328 final int width = fSOFNSegment.width;
329 final int height = fSOFNSegment.height;
330
331 JfifSegment jfifSegment = null;
332
333 if (jfifSegments != null && !jfifSegments.isEmpty()) {
334 jfifSegment = (JfifSegment) jfifSegments.get(0);
335 }
336
337 final List<AbstractSegment> app14Segments = readSegments(byteSource, new int[] { JpegConstants.JPEG_APP14_MARKER }, true);
338 App14Segment app14Segment = null;
339 if (app14Segments != null && !app14Segments.isEmpty()) {
340 app14Segment = (App14Segment) app14Segments.get(0);
341 }
342
343
344
345
346 double xDensity = -1.0;
347 double yDensity = -1.0;
348 double unitsPerInch = -1.0;
349
350
351 String formatDetails;
352
353 if (jfifSegment != null) {
354 xDensity = jfifSegment.xDensity;
355 yDensity = jfifSegment.yDensity;
356 final int densityUnits = jfifSegment.densityUnits;
357
358
359
360 formatDetails = "Jpeg/JFIF v." + jfifSegment.jfifMajorVersion + "." + jfifSegment.jfifMinorVersion;
361
362 switch (densityUnits) {
363 case 0:
364 break;
365 case 1:
366 unitsPerInch = 1.0;
367 break;
368 case 2:
369 unitsPerInch = 2.54;
370 break;
371 default:
372 break;
373 }
374 } else {
375 final JpegImageMetadata metadata = (JpegImageMetadata) getMetadata(byteSource, params);
376
377 if (metadata != null) {
378 {
379 final TiffField field = metadata.findExifValue(TiffTagConstants.TIFF_TAG_XRESOLUTION);
380 if (field != null) {
381 xDensity = ((Number) field.getValue()).doubleValue();
382 }
383 }
384 {
385 final TiffField field = metadata.findExifValue(TiffTagConstants.TIFF_TAG_YRESOLUTION);
386 if (field != null) {
387 yDensity = ((Number) field.getValue()).doubleValue();
388 }
389 }
390 {
391 final TiffField field = metadata.findExifValue(TiffTagConstants.TIFF_TAG_RESOLUTION_UNIT);
392 if (field != null) {
393 final int densityUnits = ((Number) field.getValue()).intValue();
394
395 switch (densityUnits) {
396 case 1:
397 break;
398 case 2:
399 unitsPerInch = 1.0;
400 break;
401 case 3:
402 unitsPerInch = 2.54;
403 break;
404 default:
405 break;
406 }
407 }
408
409 }
410 }
411
412 formatDetails = "Jpeg/DCM";
413
414 }
415
416 int physicalHeightDpi = -1;
417 float physicalHeightInch = -1;
418 int physicalWidthDpi = -1;
419 float physicalWidthInch = -1;
420
421 if (unitsPerInch > 0) {
422 physicalWidthDpi = (int) Math.round(xDensity * unitsPerInch);
423 physicalWidthInch = (float) (width / (xDensity * unitsPerInch));
424 physicalHeightDpi = (int) Math.round(yDensity * unitsPerInch);
425 physicalHeightInch = (float) (height / (yDensity * unitsPerInch));
426 }
427
428 final List<AbstractSegment> commentSegments = readSegments(byteSource, new int[] { JpegConstants.COM_MARKER }, false);
429 final List<String> comments = Allocator.arrayList(commentSegments.size());
430 for (final AbstractSegment commentSegment : commentSegments) {
431 final ComSegment comSegment = (ComSegment) commentSegment;
432 comments.add(new String(comSegment.getComment(), StandardCharsets.UTF_8));
433 }
434
435 final int numberOfComponents = fSOFNSegment.numberOfComponents;
436 final int precision = fSOFNSegment.precision;
437
438 final int bitsPerPixel = numberOfComponents * precision;
439 final ImageFormat format = ImageFormats.JPEG;
440 final String formatName = "JPEG (Joint Photographic Experts Group) Format";
441 final String mimeType = "image/jpeg";
442
443 final int numberOfImages = 1;
444
445 final boolean progressive = fSOFNSegment.marker == JpegConstants.SOF2_MARKER;
446
447 boolean transparent = false;
448 final boolean usesPalette = false;
449
450
451 ImageInfo.ColorType colorType = ImageInfo.ColorType.UNKNOWN;
452
453
454 if (app14Segment != null && app14Segment.isAdobeJpegSegment()) {
455 final int colorTransform = app14Segment.getAdobeColorTransform();
456 switch (colorTransform) {
457 case App14Segment.ADOBE_COLOR_TRANSFORM_UNKNOWN:
458 if (numberOfComponents == 3) {
459 colorType = ImageInfo.ColorType.RGB;
460 } else if (numberOfComponents == 4) {
461 colorType = ImageInfo.ColorType.CMYK;
462 }
463 break;
464 case App14Segment.ADOBE_COLOR_TRANSFORM_YCbCr:
465 colorType = ImageInfo.ColorType.YCbCr;
466 break;
467 case App14Segment.ADOBE_COLOR_TRANSFORM_YCCK:
468 colorType = ImageInfo.ColorType.YCCK;
469 break;
470 default:
471 break;
472 }
473 } else if (jfifSegment != null) {
474 if (numberOfComponents == 1) {
475 colorType = ImageInfo.ColorType.GRAYSCALE;
476 } else if (numberOfComponents == 3) {
477 colorType = ImageInfo.ColorType.YCbCr;
478 }
479 } else {
480 switch (numberOfComponents) {
481 case 1:
482 colorType = ImageInfo.ColorType.GRAYSCALE;
483 break;
484 case 2:
485 colorType = ImageInfo.ColorType.GRAYSCALE;
486 transparent = true;
487 break;
488 case 3:
489 case 4:
490 boolean have1 = false;
491 boolean have2 = false;
492 boolean have3 = false;
493 boolean have4 = false;
494 boolean haveOther = false;
495 for (final SofnSegment.Component component : fSOFNSegment.getComponents()) {
496 final int id = component.componentIdentifier;
497 switch (id) {
498 case 1:
499 have1 = true;
500 break;
501 case 2:
502 have2 = true;
503 break;
504 case 3:
505 have3 = true;
506 break;
507 case 4:
508 have4 = true;
509 break;
510 default:
511 haveOther = true;
512 break;
513 }
514 }
515 if (numberOfComponents == 3 && have1 && have2 && have3 && !have4 && !haveOther) {
516 colorType = ImageInfo.ColorType.YCbCr;
517 } else if (numberOfComponents == 4 && have1 && have2 && have3 && have4 && !haveOther) {
518 colorType = ImageInfo.ColorType.YCbCr;
519 transparent = true;
520 } else {
521 boolean haveR = false;
522 boolean haveG = false;
523 boolean haveB = false;
524 boolean haveA = false;
525 boolean haveC = false;
526 boolean havec = false;
527 boolean haveY = false;
528 for (final SofnSegment.Component component : fSOFNSegment.getComponents()) {
529 final int id = component.componentIdentifier;
530 switch (id) {
531 case 'R':
532 haveR = true;
533 break;
534 case 'G':
535 haveG = true;
536 break;
537 case 'B':
538 haveB = true;
539 break;
540 case 'A':
541 haveA = true;
542 break;
543 case 'C':
544 haveC = true;
545 break;
546 case 'c':
547 havec = true;
548 break;
549 case 'Y':
550 haveY = true;
551 break;
552 default:
553 break;
554 }
555 }
556 if (haveR && haveG && haveB && !haveA && !haveC && !havec && !haveY) {
557 colorType = ImageInfo.ColorType.RGB;
558 } else if (haveR && haveG && haveB && haveA && !haveC && !havec && !haveY) {
559 colorType = ImageInfo.ColorType.RGB;
560 transparent = true;
561 } else if (haveY && haveC && havec && !haveR && !haveG && !haveB && !haveA) {
562 colorType = ImageInfo.ColorType.YCC;
563 } else if (haveY && haveC && havec && haveA && !haveR && !haveG && !haveB) {
564 colorType = ImageInfo.ColorType.YCC;
565 transparent = true;
566 } else {
567 int minHorizontalSamplingFactor = Integer.MAX_VALUE;
568 int maxHorizontalSmaplingFactor = Integer.MIN_VALUE;
569 int minVerticalSamplingFactor = Integer.MAX_VALUE;
570 int maxVerticalSamplingFactor = Integer.MIN_VALUE;
571 for (final SofnSegment.Component component : fSOFNSegment.getComponents()) {
572 if (minHorizontalSamplingFactor > component.horizontalSamplingFactor) {
573 minHorizontalSamplingFactor = component.horizontalSamplingFactor;
574 }
575 if (maxHorizontalSmaplingFactor < component.horizontalSamplingFactor) {
576 maxHorizontalSmaplingFactor = component.horizontalSamplingFactor;
577 }
578 if (minVerticalSamplingFactor > component.verticalSamplingFactor) {
579 minVerticalSamplingFactor = component.verticalSamplingFactor;
580 }
581 if (maxVerticalSamplingFactor < component.verticalSamplingFactor) {
582 maxVerticalSamplingFactor = component.verticalSamplingFactor;
583 }
584 }
585 final boolean isSubsampled = minHorizontalSamplingFactor != maxHorizontalSmaplingFactor
586 || minVerticalSamplingFactor != maxVerticalSamplingFactor;
587 if (numberOfComponents == 3) {
588 if (isSubsampled) {
589 colorType = ImageInfo.ColorType.YCbCr;
590 } else {
591 colorType = ImageInfo.ColorType.RGB;
592 }
593 } else if (numberOfComponents == 4) {
594 if (isSubsampled) {
595 colorType = ImageInfo.ColorType.YCCK;
596 } else {
597 colorType = ImageInfo.ColorType.CMYK;
598 }
599 }
600 }
601 }
602 break;
603 default:
604 break;
605 }
606 }
607
608 final ImageInfo.CompressionAlgorithm compressionAlgorithm = ImageInfo.CompressionAlgorithm.JPEG;
609
610 return new ImageInfo(formatDetails, bitsPerPixel, comments, format, formatName, height, mimeType, numberOfImages, physicalHeightDpi, physicalHeightInch,
611 physicalWidthDpi, physicalWidthInch, width, progressive, transparent, usesPalette, colorType, compressionAlgorithm);
612 }
613
614 @Override
615 public Dimension getImageSize(final ByteSource byteSource, final JpegImagingParameters params) throws ImagingException, IOException {
616 final List<AbstractSegment> abstractSegments = readSegments(byteSource, new int[] {
617
618 JpegConstants.SOF0_MARKER, JpegConstants.SOF1_MARKER, JpegConstants.SOF2_MARKER, JpegConstants.SOF3_MARKER, JpegConstants.SOF5_MARKER,
619 JpegConstants.SOF6_MARKER, JpegConstants.SOF7_MARKER, JpegConstants.SOF9_MARKER, JpegConstants.SOF10_MARKER, JpegConstants.SOF11_MARKER,
620 JpegConstants.SOF13_MARKER, JpegConstants.SOF14_MARKER, JpegConstants.SOF15_MARKER,
621
622 }, true);
623
624 if (abstractSegments == null || abstractSegments.isEmpty()) {
625 throw new ImagingException("No JFIF Data Found.");
626 }
627
628 if (abstractSegments.size() > 1) {
629 throw new ImagingException("Redundant JFIF Data Found.");
630 }
631
632 final SofnSegment fSOFNSegment = (SofnSegment) abstractSegments.get(0);
633
634 return new Dimension(fSOFNSegment.width, fSOFNSegment.height);
635 }
636
637 @Override
638 public ImageMetadata getMetadata(final ByteSource byteSource, JpegImagingParameters params) throws ImagingException, IOException {
639 if (params == null) {
640 params = new JpegImagingParameters();
641 }
642 final TiffImageMetadata exif = getExifMetadata(byteSource, new TiffImagingParameters());
643
644 final JpegPhotoshopMetadata photoshop = getPhotoshopMetadata(byteSource, params);
645
646 if (null == exif && null == photoshop) {
647 return null;
648 }
649
650 return new JpegImageMetadata(photoshop, exif);
651 }
652
653 @Override
654 public String getName() {
655 return "Jpeg-Custom";
656 }
657
658 public JpegPhotoshopMetadata getPhotoshopMetadata(final ByteSource byteSource, final JpegImagingParameters params) throws ImagingException, IOException {
659 final List<AbstractSegment> abstractSegments = readSegments(byteSource, new int[] { JpegConstants.JPEG_APP13_MARKER, }, false);
660
661 if (abstractSegments == null || abstractSegments.isEmpty()) {
662 return null;
663 }
664
665 PhotoshopApp13Data photoshopApp13Data = null;
666
667 for (final AbstractSegment s : abstractSegments) {
668 final App13Segment segment = (App13Segment) s;
669
670 final PhotoshopApp13Data data = segment.parsePhotoshopSegment(params);
671 if (data != null) {
672 if (photoshopApp13Data != null) {
673 throw new ImagingException("JPEG contains more than one Photoshop App13 segment.");
674 }
675 photoshopApp13Data = data;
676 }
677 }
678
679 if (null == photoshopApp13Data) {
680 return null;
681 }
682 return new JpegPhotoshopMetadata(photoshopApp13Data);
683 }
684
685
686
687
688
689
690
691
692
693 @Override
694 public String getXmpXml(final ByteSource byteSource, final XmpImagingParameters<JpegImagingParameters> params) throws ImagingException, IOException {
695
696 final List<String> result = new ArrayList<>();
697
698 final JpegUtils.Visitor visitor = new JpegUtils.Visitor() {
699
700 @Override
701 public boolean beginSos() {
702 return false;
703 }
704
705
706 @Override
707 public boolean visitSegment(final int marker, final byte[] markerBytes, final int markerLength, final byte[] markerLengthBytes,
708 final byte[] segmentData) throws ImagingException {
709 if (marker == 0xffd9) {
710 return false;
711 }
712
713 if (marker == JpegConstants.JPEG_APP1_MARKER) {
714 if (new JpegXmpParser().isXmpJpegSegment(segmentData)) {
715 result.add(new JpegXmpParser().parseXmpJpegSegment(segmentData));
716 return false;
717 }
718 }
719
720 return true;
721 }
722
723 @Override
724 public void visitSos(final int marker, final byte[] markerBytes, final byte[] imageData) {
725
726 }
727 };
728 new JpegUtils().traverseJfif(byteSource, visitor);
729
730 if (result.isEmpty()) {
731 return null;
732 }
733 if (result.size() > 1) {
734 throw new ImagingException("JPEG file contains more than one XMP segment.");
735 }
736 return result.get(0);
737 }
738
739 public boolean hasExifSegment(final ByteSource byteSource) throws ImagingException, IOException {
740 final boolean[] result = { false, };
741
742 final JpegUtils.Visitor visitor = new JpegUtils.Visitor() {
743
744 @Override
745 public boolean beginSos() {
746 return false;
747 }
748
749
750 @Override
751 public boolean visitSegment(final int marker, final byte[] markerBytes, final int markerLength, final byte[] markerLengthBytes,
752 final byte[] segmentData) {
753 if (marker == 0xffd9) {
754 return false;
755 }
756
757 if (marker == JpegConstants.JPEG_APP1_MARKER) {
758 if (startsWith(segmentData, JpegConstants.EXIF_IDENTIFIER_CODE)) {
759 result[0] = true;
760 return false;
761 }
762 }
763
764 return true;
765 }
766
767 @Override
768 public void visitSos(final int marker, final byte[] markerBytes, final byte[] imageData) {
769
770 }
771 };
772
773 new JpegUtils().traverseJfif(byteSource, visitor);
774
775 return result[0];
776 }
777
778 public boolean hasIptcSegment(final ByteSource byteSource) throws ImagingException, IOException {
779 final boolean[] result = { false, };
780
781 final JpegUtils.Visitor visitor = new JpegUtils.Visitor() {
782
783 @Override
784 public boolean beginSos() {
785 return false;
786 }
787
788
789 @Override
790 public boolean visitSegment(final int marker, final byte[] markerBytes, final int markerLength, final byte[] markerLengthBytes,
791 final byte[] segmentData) {
792 if (marker == 0xffd9) {
793 return false;
794 }
795
796 if (marker == JpegConstants.JPEG_APP13_MARKER) {
797 if (new IptcParser().isPhotoshopJpegSegment(segmentData)) {
798 result[0] = true;
799 return false;
800 }
801 }
802
803 return true;
804 }
805
806 @Override
807 public void visitSos(final int marker, final byte[] markerBytes, final byte[] imageData) {
808
809 }
810 };
811
812 new JpegUtils().traverseJfif(byteSource, visitor);
813
814 return result[0];
815 }
816
817 public boolean hasXmpSegment(final ByteSource byteSource) throws ImagingException, IOException {
818 final boolean[] result = { false, };
819
820 final JpegUtils.Visitor visitor = new JpegUtils.Visitor() {
821
822 @Override
823 public boolean beginSos() {
824 return false;
825 }
826
827
828 @Override
829 public boolean visitSegment(final int marker, final byte[] markerBytes, final int markerLength, final byte[] markerLengthBytes,
830 final byte[] segmentData) {
831 if (marker == 0xffd9) {
832 return false;
833 }
834
835 if (marker == JpegConstants.JPEG_APP1_MARKER) {
836 if (new JpegXmpParser().isXmpJpegSegment(segmentData)) {
837 result[0] = true;
838 return false;
839 }
840 }
841
842 return true;
843 }
844
845 @Override
846 public void visitSos(final int marker, final byte[] markerBytes, final byte[] imageData) {
847
848 }
849 };
850 new JpegUtils().traverseJfif(byteSource, visitor);
851
852 return result[0];
853 }
854
855 private boolean keepMarker(final int marker, final int[] markers) {
856 if (markers == null) {
857 return true;
858 }
859
860 for (final int marker2 : markers) {
861 if (marker2 == marker) {
862 return true;
863 }
864 }
865
866 return false;
867 }
868
869 public List<AbstractSegment> readSegments(final ByteSource byteSource, final int[] markers, final boolean returnAfterFirst)
870 throws ImagingException, IOException {
871 final List<AbstractSegment> result = new ArrayList<>();
872 final int[] sofnSegments = {
873
874 JpegConstants.SOF0_MARKER, JpegConstants.SOF1_MARKER, JpegConstants.SOF2_MARKER, JpegConstants.SOF3_MARKER, JpegConstants.SOF5_MARKER,
875 JpegConstants.SOF6_MARKER, JpegConstants.SOF7_MARKER, JpegConstants.SOF9_MARKER, JpegConstants.SOF10_MARKER, JpegConstants.SOF11_MARKER,
876 JpegConstants.SOF13_MARKER, JpegConstants.SOF14_MARKER, JpegConstants.SOF15_MARKER, };
877
878 final JpegUtils.Visitor visitor = new JpegUtils.Visitor() {
879
880 @Override
881 public boolean beginSos() {
882 return false;
883 }
884
885
886 @Override
887 public boolean visitSegment(final int marker, final byte[] markerBytes, final int markerLength, final byte[] markerLengthBytes,
888 final byte[] segmentData) throws ImagingException, IOException {
889 if (marker == JpegConstants.EOI_MARKER) {
890 return false;
891 }
892
893
894
895
896
897
898
899 if (!keepMarker(marker, markers)) {
900 return true;
901 }
902
903 switch (marker) {
904 case JpegConstants.JPEG_APP13_MARKER:
905
906 result.add(new App13Segment(marker, segmentData));
907 break;
908 case JpegConstants.JPEG_APP14_MARKER:
909 result.add(new App14Segment(marker, segmentData));
910 break;
911 case JpegConstants.JPEG_APP2_MARKER:
912 result.add(new App2Segment(marker, segmentData));
913 break;
914 case JpegConstants.JFIF_MARKER:
915 result.add(new JfifSegment(marker, segmentData));
916 break;
917 default:
918 if (Arrays.binarySearch(sofnSegments, marker) >= 0) {
919 result.add(new SofnSegment(marker, segmentData));
920 } else if (marker == JpegConstants.DQT_MARKER) {
921 result.add(new DqtSegment(marker, segmentData));
922 } else if (marker >= JpegConstants.JPEG_APP1_MARKER && marker <= JpegConstants.JPEG_APP15_MARKER) {
923 result.add(new UnknownSegment(marker, segmentData));
924 } else if (marker == JpegConstants.COM_MARKER) {
925 result.add(new ComSegment(marker, segmentData));
926 }
927 break;
928 }
929
930 return !returnAfterFirst;
931 }
932
933 @Override
934 public void visitSos(final int marker, final byte[] markerBytes, final byte[] imageData) {
935
936 }
937 };
938
939 new JpegUtils().traverseJfif(byteSource, visitor);
940
941 return result;
942 }
943 }