Changeset 6127 in josm for trunk/src/com/drew/imaging/jpeg/JpegSegmentReader.java
- Timestamp:
- 2013-08-09T18:05:11+02:00 (13 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/com/drew/imaging/jpeg/JpegSegmentReader.java
r4231 r6127 1 1 /* 2 * JpegSegmentReader.java3 * 4 * This class written by Drew Noakes, in accordance with the Jpeg specification.5 * 6 * This is public domain software - that is, you can do whatever you want7 * with it, and include it software that is licensed under the GNU or the8 * BSDlicense, or whatever other licence you choose, including proprietary9 * closed source licenses. I do ask that you leave this header in tact.10 * 11 * If you make modifications to this code that you think would benefit the12 * wider community, please send me a copy and I'll post it on my site.13 * 14 * If you make use of this code, I'd appreciate hearing about it.15 * drew@drewnoakes.com16 * Latest version of this software kept at17 * http://drewnoakes.com/18 * 19 * Created by dnoakes on 04-Nov-2002 00:54:00 using IntelliJ IDEA2 * Copyright 2002-2012 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 * http://drewnoakes.com/code/exif/ 19 * http://code.google.com/p/metadata-extractor/ 20 20 */ 21 21 package com.drew.imaging.jpeg; 22 22 23 import com.drew.lang.annotations.NotNull; 24 import com.drew.lang.annotations.Nullable; 25 23 26 import java.io.*; 24 27 25 28 /** 26 29 * Performs read functions of Jpeg files, returning specific file segments. 27 * TODO add a findAvailableSegments() method28 * TODO add more segment identifiers29 * TODO add a getSegmentDescription() method, returning for example 'App1 application data segment, commonly containing Exif data'30 30 * @author Drew Noakes http://drewnoakes.com 31 31 */ 32 32 public class JpegSegmentReader 33 33 { 34 // Jpeg data can be sourced from either a file, byte[] or InputStream 35 36 /** Jpeg file */ 37 private final File _file; 38 /** Jpeg data as byte array */ 39 private final byte[] _data; 40 /** Jpeg data as an InputStream */ 41 private final InputStream _stream; 42 43 private JpegSegmentData _segmentData; 44 45 /** 46 * Private, because this segment crashes my algorithm, and searching for 47 * it doesn't work (yet). 34 // TODO add a findAvailableSegments() method 35 // TODO add more segment identifiers 36 // TODO add a getSegmentDescription() method, returning for example 'App1 application data segment, commonly containing Exif data' 37 38 @NotNull 39 private final JpegSegmentData _segmentData; 40 41 /** 42 * Private, because this segment crashes my algorithm, and searching for it doesn't work (yet). 48 43 */ 49 44 private static final byte SEGMENT_SOS = (byte)0xDA; … … 54 49 private static final byte MARKER_EOI = (byte)0xD9; 55 50 56 /** APP0 Jpeg segment identifier -- J fif data. */51 /** APP0 Jpeg segment identifier -- JFIF data (also JFXX apparently). */ 57 52 public static final byte SEGMENT_APP0 = (byte)0xE0; 58 /** APP1 Jpeg segment identifier -- where Exif data is kept. */ 53 /** APP1 Jpeg segment identifier -- where Exif data is kept. XMP data is also kept in here, though usually in a second instance. */ 59 54 public static final byte SEGMENT_APP1 = (byte)0xE1; 60 55 /** APP2 Jpeg segment identifier. */ … … 74 69 /** APP9 Jpeg segment identifier. */ 75 70 public static final byte SEGMENT_APP9 = (byte)0xE9; 76 /** APPA Jpeg segment identifier -- can hold Unicode comments. */ 71 /** APPA (App10) Jpeg segment identifier -- can hold Unicode comments. */ 77 72 public static final byte SEGMENT_APPA = (byte)0xEA; 78 /** APPB Jpeg segment identifier. */ 73 /** APPB (App11) Jpeg segment identifier. */ 79 74 public static final byte SEGMENT_APPB = (byte)0xEB; 80 /** APPC Jpeg segment identifier. */ 75 /** APPC (App12) Jpeg segment identifier. */ 81 76 public static final byte SEGMENT_APPC = (byte)0xEC; 82 /** APPD Jpeg segment identifier -- IPTC data in here. */ 77 /** APPD (App13) Jpeg segment identifier -- IPTC data in here. */ 83 78 public static final byte SEGMENT_APPD = (byte)0xED; 84 /** APPE Jpeg segment identifier. */ 79 /** APPE (App14) Jpeg segment identifier. */ 85 80 public static final byte SEGMENT_APPE = (byte)0xEE; 86 /** APPF Jpeg segment identifier. */ 81 /** APPF (App15) Jpeg segment identifier. */ 87 82 public static final byte SEGMENT_APPF = (byte)0xEF; 88 83 /** Start Of Image segment identifier. */ … … 101 96 * @param file the Jpeg file to read segments from 102 97 */ 103 public JpegSegmentReader(File file) throws JpegProcessingException 104 { 105 _file = file; 106 _data = null; 107 _stream = null; 108 109 readSegments(); 98 @SuppressWarnings({ "ConstantConditions" }) 99 public JpegSegmentReader(@NotNull File file) throws JpegProcessingException, IOException 100 { 101 if (file==null) 102 throw new NullPointerException(); 103 104 InputStream inputStream = null; 105 try { 106 inputStream = new FileInputStream(file); 107 _segmentData = readSegments(new BufferedInputStream(inputStream), false); 108 } finally { 109 if (inputStream != null) 110 inputStream.close(); 111 } 110 112 } 111 113 … … 114 116 * @param fileContents the byte array containing Jpeg data 115 117 */ 116 public JpegSegmentReader(byte[] fileContents) throws JpegProcessingException117 {118 _file = null;119 _data =fileContents;120 _stream = null;121 122 readSegments();123 }124 125 public JpegSegmentReader(InputStream in) throws JpegProcessingException 126 {127 _stream = in;128 _file = null;129 _data = null;130 131 readSegments();132 }133 134 public JpegSegmentReader(JpegSegmentData segmentData)135 { 136 _file = null;137 _data = null;138 _stream = null;139 140 _segmentData = segmentData;118 @SuppressWarnings({ "ConstantConditions" }) 119 public JpegSegmentReader(@NotNull byte[] fileContents) throws JpegProcessingException 120 { 121 if (fileContents==null) 122 throw new NullPointerException(); 123 124 BufferedInputStream stream = new BufferedInputStream(new ByteArrayInputStream(fileContents)); 125 _segmentData = readSegments(stream, false); 126 } 127 128 /** 129 * Creates a JpegSegmentReader for an InputStream. 130 * @param inputStream the InputStream containing Jpeg data 131 */ 132 @SuppressWarnings({ "ConstantConditions" }) 133 public JpegSegmentReader(@NotNull InputStream inputStream, boolean waitForBytes) throws JpegProcessingException 134 { 135 if (inputStream==null) 136 throw new NullPointerException(); 137 138 BufferedInputStream bufferedInputStream = inputStream instanceof BufferedInputStream 139 ? (BufferedInputStream)inputStream 140 : new BufferedInputStream(inputStream); 141 142 _segmentData = readSegments(bufferedInputStream, waitForBytes); 141 143 } 142 144 … … 146 148 * @param segmentMarker the byte identifier for the desired segment 147 149 * @return the byte array if found, else null 148 * @throws JpegProcessingException for any problems processing the Jpeg data, 149 * including inner IOExceptions 150 */ 151 public byte[] readSegment(byte segmentMarker) throws JpegProcessingException 150 */ 151 @Nullable 152 public byte[] readSegment(byte segmentMarker) 152 153 { 153 154 return readSegment(segmentMarker, 0); … … 155 156 156 157 /** 157 * Reads the firstinstance of a given Jpeg segment, returning the contents as158 * a byte array.158 * Reads the Nth instance of a given Jpeg segment, returning the contents as a byte array. 159 * 159 160 * @param segmentMarker the byte identifier for the desired segment 160 161 * @param occurrence the occurrence of the specified segment within the jpeg file 161 162 * @return the byte array if found, else null 162 163 */ 164 @Nullable 163 165 public byte[] readSegment(byte segmentMarker, int occurrence) 164 166 { … … 166 168 } 167 169 170 /** 171 * Returns all instances of a given Jpeg segment. If no instances exist, an empty sequence is returned. 172 * 173 * @param segmentMarker a number which identifies the type of Jpeg segment being queried 174 * @return zero or more byte arrays, each holding the data of a Jpeg segment 175 */ 176 @NotNull 177 public Iterable<byte[]> readSegments(byte segmentMarker) 178 { 179 return _segmentData.getSegments(segmentMarker); 180 } 181 182 /** 183 * Returns the number of segments having the specified JPEG segment marker. 184 * @param segmentMarker the JPEG segment identifying marker. 185 * @return the count of matching segments. 186 */ 168 187 public final int getSegmentCount(byte segmentMarker) 169 188 { … … 171 190 } 172 191 192 /** 193 * Returns the JpegSegmentData object used by this reader. 194 * @return the JpegSegmentData object. 195 */ 196 @NotNull 173 197 public final JpegSegmentData getSegmentData() 174 198 { … … 176 200 } 177 201 178 private void readSegments() throws JpegProcessingException179 {180 _segmentData = new JpegSegmentData();181 182 BufferedInputStream inStream = getJpegInputStream(); 202 @NotNull 203 private JpegSegmentData readSegments(@NotNull final BufferedInputStream jpegInputStream, boolean waitForBytes) throws JpegProcessingException 204 { 205 JpegSegmentData segmentData = new JpegSegmentData(); 206 183 207 try { 184 208 int offset = 0; 185 209 // first two bytes should be jpeg magic number 186 if (!isValidJpegHeaderBytes(inStream)) { 210 byte[] headerBytes = new byte[2]; 211 if (jpegInputStream.read(headerBytes, 0, 2)!=2) 187 212 throw new JpegProcessingException("not a jpeg file"); 188 } 213 final boolean hasValidHeader = (headerBytes[0] & 0xFF) == 0xFF && (headerBytes[1] & 0xFF) == 0xD8; 214 if (!hasValidHeader) 215 throw new JpegProcessingException("not a jpeg file"); 216 189 217 offset += 2; 190 218 do { 219 // need four bytes from stream for segment header before continuing 220 if (!checkForBytesOnStream(jpegInputStream, 4, waitForBytes)) 221 throw new JpegProcessingException("stream ended before segment header could be read"); 222 191 223 // next byte is 0xFF 192 byte segmentIdentifier = (byte)( inStream.read() & 0xFF);224 byte segmentIdentifier = (byte)(jpegInputStream.read() & 0xFF); 193 225 if ((segmentIdentifier & 0xFF) != 0xFF) { 194 226 throw new JpegProcessingException("expected jpeg segment start identifier 0xFF at offset " + offset + ", not 0x" + Integer.toHexString(segmentIdentifier & 0xFF)); … … 196 228 offset++; 197 229 // next byte is <segment-marker> 198 byte thisSegmentMarker = (byte)( inStream.read() & 0xFF);230 byte thisSegmentMarker = (byte)(jpegInputStream.read() & 0xFF); 199 231 offset++; 200 232 // next 2-bytes are <segment-size>: [high-byte] [low-byte] 201 233 byte[] segmentLengthBytes = new byte[2]; 202 inStream.read(segmentLengthBytes, 0, 2); 234 if (jpegInputStream.read(segmentLengthBytes, 0, 2) != 2) 235 throw new JpegProcessingException("Jpeg data ended unexpectedly."); 203 236 offset += 2; 204 237 int segmentLength = ((segmentLengthBytes[0] << 8) & 0xFF00) | (segmentLengthBytes[1] & 0xFF); 205 238 // segment length includes size bytes, so subtract two 206 239 segmentLength -= 2; 207 if (segmentLength > inStream.available())240 if (!checkForBytesOnStream(jpegInputStream, segmentLength, waitForBytes)) 208 241 throw new JpegProcessingException("segment size would extend beyond file stream length"); 209 elseif (segmentLength < 0)242 if (segmentLength < 0) 210 243 throw new JpegProcessingException("segment size would be less than zero"); 211 244 byte[] segmentBytes = new byte[segmentLength]; 212 inStream.read(segmentBytes, 0, segmentLength); 245 if (jpegInputStream.read(segmentBytes, 0, segmentLength) != segmentLength) 246 throw new JpegProcessingException("Jpeg data ended unexpectedly."); 213 247 offset += segmentLength; 214 248 if ((thisSegmentMarker & 0xFF) == (SEGMENT_SOS & 0xFF)) { … … 216 250 // have to search for the two bytes: 0xFF 0xD9 (EOI). 217 251 // It comes last so simply return at this point 218 return; 252 return segmentData; 219 253 } else if ((thisSegmentMarker & 0xFF) == (MARKER_EOI & 0xFF)) { 220 254 // the 'End-Of-Image' segment -- this should never be found in this fashion 221 return; 255 return segmentData; 222 256 } else { 223 _segmentData.addSegment(thisSegmentMarker, segmentBytes);257 segmentData.addSegment(thisSegmentMarker, segmentBytes); 224 258 } 225 // didn't find the one we're looking for, loop through to the next segment226 259 } while (true); 227 260 } catch (IOException ioe) { 228 //throw new JpegProcessingException("IOException processing Jpeg file", ioe);229 261 throw new JpegProcessingException("IOException processing Jpeg file: " + ioe.getMessage(), ioe); 230 262 } finally { 231 263 try { 232 if ( inStream != null) {233 inStream.close();264 if (jpegInputStream != null) { 265 jpegInputStream.close(); 234 266 } 235 267 } catch (IOException ioe) { 236 //throw new JpegProcessingException("IOException processing Jpeg file", ioe);237 268 throw new JpegProcessingException("IOException processing Jpeg file: " + ioe.getMessage(), ioe); 238 269 } … … 240 271 } 241 272 242 /** 243 * Private helper method to create a BufferedInputStream of Jpeg data from whichever 244 * data source was specified upon construction of this instance. 245 * @return a a BufferedInputStream of Jpeg data 246 * @throws JpegProcessingException for any problems obtaining the stream 247 */ 248 private BufferedInputStream getJpegInputStream() throws JpegProcessingException 249 { 250 if (_stream!=null) { 251 if (_stream instanceof BufferedInputStream) { 252 return (BufferedInputStream) _stream; 253 } else { 254 return new BufferedInputStream(_stream); 273 private boolean checkForBytesOnStream(@NotNull BufferedInputStream stream, int bytesNeeded, boolean waitForBytes) throws IOException 274 { 275 // NOTE waiting is essential for network streams where data can be delayed, but it is not necessary for byte[] or filesystems 276 277 if (!waitForBytes) 278 return bytesNeeded <= stream.available(); 279 280 int count = 40; // * 100ms = approx 4 seconds 281 while (count > 0) { 282 if (bytesNeeded <= stream.available()) 283 return true; 284 try { 285 Thread.sleep(100); 286 } catch (InterruptedException e) { 287 // continue 255 288 } 289 count--; 256 290 } 257 InputStream inputStream; 258 if (_data == null) { 259 try { 260 inputStream = new FileInputStream(_file); 261 } catch (FileNotFoundException e) { 262 throw new JpegProcessingException("Jpeg file does not exist", e); 263 } 264 } else { 265 inputStream = new ByteArrayInputStream(_data); 266 } 267 return new BufferedInputStream(inputStream); 268 } 269 270 /** 271 * Helper method that validates the Jpeg file's magic number. 272 * @param fileStream the InputStream to read bytes from, which must be positioned 273 * at its start (i.e. no bytes read yet) 274 * @return true if the magic number is Jpeg (0xFFD8) 275 * @throws IOException for any problem in reading the file 276 */ 277 private boolean isValidJpegHeaderBytes(InputStream fileStream) throws IOException 278 { 279 byte[] header = new byte[2]; 280 fileStream.read(header, 0, 2); 281 return (header[0] & 0xFF) == 0xFF && (header[1] & 0xFF) == 0xD8; 291 return false; 282 292 } 283 293 }
Note:
See TracChangeset
for help on using the changeset viewer.
