1 package tim.prune.jpeg.drew;
\r
6 * Class to perform read functions of Jpeg files, returning specific file segments
\r
7 * Based on Drew Noakes' Metadata extractor at http://drewnoakes.com
\r
9 public class JpegSegmentReader
\r
11 /** Start of scan marker */
\r
12 private static final byte SEGMENT_SOS = (byte)0xDA;
\r
14 /** End of image marker */
\r
15 private static final byte MARKER_EOI = (byte)0xD9;
\r
17 /** APP0 Jpeg segment identifier -- Jfif data. */
\r
18 public static final byte SEGMENT_APP0 = (byte)0xE0;
\r
19 /** APP1 Jpeg segment identifier -- where Exif data is kept. */
\r
20 public static final byte SEGMENT_APP1 = (byte)0xE1;
\r
21 /** APP2 Jpeg segment identifier. */
\r
22 public static final byte SEGMENT_APP2 = (byte)0xE2;
\r
23 /** APP3 Jpeg segment identifier. */
\r
24 public static final byte SEGMENT_APP3 = (byte)0xE3;
\r
25 /** APP4 Jpeg segment identifier. */
\r
26 public static final byte SEGMENT_APP4 = (byte)0xE4;
\r
27 /** APP5 Jpeg segment identifier. */
\r
28 public static final byte SEGMENT_APP5 = (byte)0xE5;
\r
29 /** APP6 Jpeg segment identifier. */
\r
30 public static final byte SEGMENT_APP6 = (byte)0xE6;
\r
31 /** APP7 Jpeg segment identifier. */
\r
32 public static final byte SEGMENT_APP7 = (byte)0xE7;
\r
33 /** APP8 Jpeg segment identifier. */
\r
34 public static final byte SEGMENT_APP8 = (byte)0xE8;
\r
35 /** APP9 Jpeg segment identifier. */
\r
36 public static final byte SEGMENT_APP9 = (byte)0xE9;
\r
37 /** APPA Jpeg segment identifier -- can hold Unicode comments. */
\r
38 public static final byte SEGMENT_APPA = (byte)0xEA;
\r
39 /** APPB Jpeg segment identifier. */
\r
40 public static final byte SEGMENT_APPB = (byte)0xEB;
\r
41 /** APPC Jpeg segment identifier. */
\r
42 public static final byte SEGMENT_APPC = (byte)0xEC;
\r
43 /** APPD Jpeg segment identifier -- IPTC data in here. */
\r
44 public static final byte SEGMENT_APPD = (byte)0xED;
\r
45 /** APPE Jpeg segment identifier. */
\r
46 public static final byte SEGMENT_APPE = (byte)0xEE;
\r
47 /** APPF Jpeg segment identifier. */
\r
48 public static final byte SEGMENT_APPF = (byte)0xEF;
\r
49 /** Start Of Image segment identifier. */
\r
50 public static final byte SEGMENT_SOI = (byte)0xD8;
\r
51 /** Define Quantization Table segment identifier. */
\r
52 public static final byte SEGMENT_DQT = (byte)0xDB;
\r
53 /** Define Huffman Table segment identifier. */
\r
54 public static final byte SEGMENT_DHT = (byte)0xC4;
\r
55 /** Start-of-Frame Zero segment identifier. */
\r
56 public static final byte SEGMENT_SOF0 = (byte)0xC0;
\r
57 /** Jpeg comment segment identifier. */
\r
58 public static final byte SEGMENT_COM = (byte)0xFE;
\r
60 /** Magic numbers to mark the beginning of all Jpegs */
\r
61 private static final int MAGIC_JPEG_BYTE_1 = 0xFF;
\r
62 private static final int MAGIC_JPEG_BYTE_2 = 0xD8;
\r
66 * Obtain the Jpeg segment data from the specified file
\r
67 * @param inFile File to read
\r
68 * @return Jpeg segment data from file
\r
69 * @throws JpegException on file read errors or exif data errors
\r
71 public static JpegSegmentData readSegments(File inFile) throws JpegException
\r
73 JpegSegmentData segmentData = new JpegSegmentData();
\r
75 BufferedInputStream bStream = null;
\r
79 bStream = new BufferedInputStream(new FileInputStream(inFile));
\r
81 // first two bytes should be jpeg magic number
\r
82 int magic1 = bStream.read() & 0xFF;
\r
83 int magic2 = bStream.read() & 0xFF;
\r
84 checkMagicNumbers(magic1, magic2);
\r
87 // Loop around segments found
\r
90 // next byte is 0xFF
\r
91 byte segmentIdentifier = (byte) (bStream.read() & 0xFF);
\r
92 if ((segmentIdentifier & 0xFF) != 0xFF)
\r
94 throw new JpegException("expected jpeg segment start identifier 0xFF at offset "
\r
95 + offset + ", not 0x" + Integer.toHexString(segmentIdentifier & 0xFF));
\r
98 // next byte is <segment-marker>
\r
99 byte thisSegmentMarker = (byte) (bStream.read() & 0xFF);
\r
101 // next 2-bytes are <segment-size>: [high-byte] [low-byte]
\r
102 byte[] segmentLengthBytes = new byte[2];
\r
103 bStream.read(segmentLengthBytes, 0, 2);
\r
105 int segmentLength = ((segmentLengthBytes[0] << 8) & 0xFF00) | (segmentLengthBytes[1] & 0xFF);
\r
106 // segment length includes size bytes, so subtract two
\r
107 segmentLength -= 2;
\r
108 if (segmentLength > bStream.available())
\r
109 throw new JpegException("segment size would extend beyond file stream length");
\r
110 else if (segmentLength < 0)
\r
111 throw new JpegException("segment size would be less than zero");
\r
112 byte[] segmentBytes = new byte[segmentLength];
\r
113 bStream.read(segmentBytes, 0, segmentLength);
\r
114 offset += segmentLength;
\r
115 if ((thisSegmentMarker & 0xFF) == (SEGMENT_SOS & 0xFF))
\r
117 // The 'Start-Of-Scan' segment comes last so break out of loop
\r
120 else if ((thisSegmentMarker & 0xFF) == (MARKER_EOI & 0xFF))
\r
122 // the 'End-Of-Image' segment - should already have exited by now
\r
127 segmentData.addSegment(thisSegmentMarker, segmentBytes);
\r
129 // loop through to the next segment
\r
133 catch (FileNotFoundException fnfe)
\r
135 throw new JpegException("Jpeg file not found");
\r
137 catch (IOException ioe)
\r
139 throw new JpegException("IOException processing Jpeg file: " + ioe.getMessage(), ioe);
\r
145 if (bStream != null) {
\r
149 catch (IOException ioe) {
\r
150 throw new JpegException("IOException processing Jpeg file: " + ioe.getMessage(), ioe);
\r
153 // Return the result
\r
154 return segmentData;
\r
159 * Helper method that validates the Jpeg file's magic number.
\r
160 * @param inMagic1 first half of magic number
\r
161 * @param inMagic2 second half of magic number
\r
162 * @throws JpegException if numbers do not match magic numbers expected
\r
164 private static void checkMagicNumbers(int inMagic1, int inMagic2) throws JpegException
\r
166 if (inMagic1 != MAGIC_JPEG_BYTE_1 || inMagic2 != MAGIC_JPEG_BYTE_2)
\r
168 throw new JpegException("not a jpeg file");
\r