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