source: josm/trunk/src/com/drew/imaging/jpeg/JpegSegmentReader.java@ 13061

Last change on this file since 13061 was 13061, checked in by Don-vip, 7 years ago

fix #15505 - update to metadata-extractor 2.10.1

File size: 6.6 KB
Line 
1/*
2 * Copyright 2002-2017 Drew Noakes
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 * More information about this project is available at:
17 *
18 * https://drewnoakes.com/code/exif/
19 * https://github.com/drewnoakes/metadata-extractor
20 */
21package com.drew.imaging.jpeg;
22
23import com.drew.lang.SequentialReader;
24import com.drew.lang.StreamReader;
25import com.drew.lang.annotations.NotNull;
26import com.drew.lang.annotations.Nullable;
27
28import java.io.File;
29import java.io.FileInputStream;
30import java.io.IOException;
31import java.util.HashSet;
32import java.util.Set;
33
34/**
35 * Performs read functions of JPEG files, returning specific file segments.
36 * <p>
37 * JPEG files are composed of a sequence of consecutive JPEG 'segments'. Each is identified by one of a set of byte
38 * values, modelled in the {@link JpegSegmentType} enumeration. Use <code>readSegments</code> to read out the some
39 * or all segments into a {@link JpegSegmentData} object, from which the raw JPEG segment byte arrays may be accessed.
40 *
41 * @author Drew Noakes https://drewnoakes.com
42 */
43public class JpegSegmentReader
44{
45 /**
46 * The 0xFF byte that signals the start of a segment.
47 */
48 private static final byte SEGMENT_IDENTIFIER = (byte) 0xFF;
49
50 /**
51 * Private, because this segment crashes my algorithm, and searching for it doesn't work (yet).
52 */
53 private static final byte SEGMENT_SOS = (byte) 0xDA;
54
55 /**
56 * Private, because one wouldn't search for it.
57 */
58 private static final byte MARKER_EOI = (byte) 0xD9;
59
60 /**
61 * Processes the provided JPEG data, and extracts the specified JPEG segments into a {@link JpegSegmentData} object.
62 * <p>
63 * Will not return SOS (start of scan) or EOI (end of image) segments.
64 *
65 * @param file a {@link File} from which the JPEG data will be read.
66 * @param segmentTypes the set of JPEG segments types that are to be returned. If this argument is <code>null</code>
67 * then all found segment types are returned.
68 */
69 @NotNull
70 public static JpegSegmentData readSegments(@NotNull File file, @Nullable Iterable<JpegSegmentType> segmentTypes) throws JpegProcessingException, IOException
71 {
72 FileInputStream stream = null;
73 try {
74 stream = new FileInputStream(file);
75 return readSegments(new StreamReader(stream), segmentTypes);
76 } finally {
77 if (stream != null) {
78 stream.close();
79 }
80 }
81 }
82
83 /**
84 * Processes the provided JPEG data, and extracts the specified JPEG segments into a {@link JpegSegmentData} object.
85 * <p>
86 * Will not return SOS (start of scan) or EOI (end of image) segments.
87 *
88 * @param reader a {@link SequentialReader} from which the JPEG data will be read. It must be positioned at the
89 * beginning of the JPEG data stream.
90 * @param segmentTypes the set of JPEG segments types that are to be returned. If this argument is <code>null</code>
91 * then all found segment types are returned.
92 */
93 @NotNull
94 public static JpegSegmentData readSegments(@NotNull final SequentialReader reader, @Nullable Iterable<JpegSegmentType> segmentTypes) throws JpegProcessingException, IOException
95 {
96 // Must be big-endian
97 assert (reader.isMotorolaByteOrder());
98
99 // first two bytes should be JPEG magic number
100 final int magicNumber = reader.getUInt16();
101 if (magicNumber != 0xFFD8) {
102 throw new JpegProcessingException("JPEG data is expected to begin with 0xFFD8 (ÿØ) not 0x" + Integer.toHexString(magicNumber));
103 }
104
105 Set<Byte> segmentTypeBytes = null;
106 if (segmentTypes != null) {
107 segmentTypeBytes = new HashSet<Byte>();
108 for (JpegSegmentType segmentType : segmentTypes) {
109 segmentTypeBytes.add(segmentType.byteValue);
110 }
111 }
112
113 JpegSegmentData segmentData = new JpegSegmentData();
114
115 do {
116 // Find the segment marker. Markers are zero or more 0xFF bytes, followed
117 // by a 0xFF and then a byte not equal to 0x00 or 0xFF.
118
119 byte segmentIdentifier = reader.getInt8();
120 byte segmentType = reader.getInt8();
121
122 // Read until we have a 0xFF byte followed by a byte that is not 0xFF or 0x00
123 while (segmentIdentifier != SEGMENT_IDENTIFIER || segmentType == SEGMENT_IDENTIFIER || segmentType == 0) {
124 segmentIdentifier = segmentType;
125 segmentType = reader.getInt8();
126 }
127
128 if (segmentType == SEGMENT_SOS) {
129 // The 'Start-Of-Scan' segment's length doesn't include the image data, instead would
130 // have to search for the two bytes: 0xFF 0xD9 (EOI).
131 // It comes last so simply return at this point
132 return segmentData;
133 }
134
135 if (segmentType == MARKER_EOI) {
136 // the 'End-Of-Image' segment -- this should never be found in this fashion
137 return segmentData;
138 }
139
140 // next 2-bytes are <segment-size>: [high-byte] [low-byte]
141 int segmentLength = reader.getUInt16();
142
143 // segment length includes size bytes, so subtract two
144 segmentLength -= 2;
145
146 if (segmentLength < 0)
147 throw new JpegProcessingException("JPEG segment size would be less than zero");
148
149 // Check whether we are interested in this segment
150 if (segmentTypeBytes == null || segmentTypeBytes.contains(segmentType)) {
151 byte[] segmentBytes = reader.getBytes(segmentLength);
152 assert (segmentLength == segmentBytes.length);
153 segmentData.addSegment(segmentType, segmentBytes);
154 } else {
155 // Some if the JPEG is truncated, just return what data we've already gathered
156 if (!reader.trySkip(segmentLength)) {
157 return segmentData;
158 }
159 }
160
161 } while (true);
162 }
163
164 private JpegSegmentReader() throws Exception
165 {
166 throw new Exception("Not intended for instantiation.");
167 }
168}
Note: See TracBrowser for help on using the repository browser.