]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/jpeg/drew/JpegSegmentReader.java
Version 10, May 2010
[GpsPrune.git] / tim / prune / jpeg / drew / JpegSegmentReader.java
1 package tim.prune.jpeg.drew;\r
2 \r
3 import java.io.*;\r
4 \r
5 /**\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
8  */\r
9 public class JpegSegmentReader\r
10 {\r
11         /** Start of scan marker */\r
12         private static final byte SEGMENT_SOS = (byte)0xDA;\r
13 \r
14         /** End of image marker */\r
15         private static final byte MARKER_EOI = (byte)0xD9;\r
16 \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
59 \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
63 \r
64 \r
65         /**\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
70          */\r
71         public static JpegSegmentData readSegments(File inFile) throws JpegException\r
72         {\r
73                 JpegSegmentData segmentData = new JpegSegmentData();\r
74 \r
75                 BufferedInputStream bStream = null;\r
76 \r
77                 try\r
78                 {\r
79                         bStream = new BufferedInputStream(new FileInputStream(inFile));\r
80                         int offset = 0;\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
85 \r
86                         offset += 2;\r
87                         // Loop around segments found\r
88                         do\r
89                         {\r
90                                 // next byte is 0xFF\r
91                                 byte segmentIdentifier = (byte) (bStream.read() & 0xFF);\r
92                                 if ((segmentIdentifier & 0xFF) != 0xFF)\r
93                                 {\r
94                                         throw new JpegException("expected jpeg segment start identifier 0xFF at offset "\r
95                                                 + offset + ", not 0x" + Integer.toHexString(segmentIdentifier & 0xFF));\r
96                                 }\r
97                                 offset++;\r
98                                 // next byte is <segment-marker>\r
99                                 byte thisSegmentMarker = (byte) (bStream.read() & 0xFF);\r
100                                 offset++;\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
104                                 offset += 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
116                                 {\r
117                                         // The 'Start-Of-Scan' segment comes last so break out of loop\r
118                                         break;\r
119                                 }\r
120                                 else if ((thisSegmentMarker & 0xFF) == (MARKER_EOI & 0xFF))\r
121                                 {\r
122                                         // the 'End-Of-Image' segment - should already have exited by now\r
123                                         break;\r
124                                 }\r
125                                 else\r
126                                 {\r
127                                         segmentData.addSegment(thisSegmentMarker, segmentBytes);\r
128                                 }\r
129                                 // loop through to the next segment\r
130                         }\r
131                         while (true);\r
132                 }\r
133                 catch (FileNotFoundException fnfe)\r
134                 {\r
135                         throw new JpegException("Jpeg file not found");\r
136                 }\r
137                 catch (IOException ioe)\r
138                 {\r
139                         throw new JpegException("IOException processing Jpeg file: " + ioe.getMessage(), ioe);\r
140                 }\r
141                 finally\r
142                 {\r
143                         try\r
144                         {\r
145                                 if (bStream != null) {\r
146                                         bStream.close();\r
147                                 }\r
148                         }\r
149                         catch (IOException ioe) {\r
150                                 throw new JpegException("IOException processing Jpeg file: " + ioe.getMessage(), ioe);\r
151                         }\r
152                 }\r
153                 // Return the result\r
154                 return segmentData;\r
155         }\r
156 \r
157 \r
158         /**\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
163          */\r
164         private static void checkMagicNumbers(int inMagic1, int inMagic2) throws JpegException\r
165         {\r
166                 if (inMagic1 != MAGIC_JPEG_BYTE_1 || inMagic2 != MAGIC_JPEG_BYTE_2)\r
167                 {\r
168                         throw new JpegException("not a jpeg file");\r
169                 }\r
170         }\r
171 }