Changeset 8132 in josm for trunk/src/com/drew/imaging
- Timestamp:
- 2015-03-10T01:17:39+01:00 (10 years ago)
- Location:
- trunk/src/com/drew/imaging
- Files:
-
- 10 added
- 6 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/com/drew/imaging/ImageProcessingException.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http://drewnoakes.com/code/exif/ 19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.imaging; … … 26 26 /** 27 27 * An exception class thrown upon an unexpected condition that was fatal for the processing of an image. 28 * 29 * @author Drew Noakes http://drewnoakes.com 28 * 29 * @author Drew Noakes https://drewnoakes.com 30 30 */ 31 31 public class ImageProcessingException extends CompoundException -
trunk/src/com/drew/imaging/PhotographicConversions.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http://drewnoakes.com/code/exif/ 19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.imaging; … … 24 24 * Contains helper methods that perform photographic conversions. 25 25 * 26 * @author Drew Noakes http://drewnoakes.com 26 * @author Drew Noakes https://drewnoakes.com 27 27 */ 28 28 public final class PhotographicConversions -
trunk/src/com/drew/imaging/jpeg/JpegMetadataReader.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http://drewnoakes.com/code/exif/ 19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.imaging.jpeg; 22 22 23 import com.drew.lang.ByteArrayReader;24 import com.drew.lang.annotations.NotNull;25 import com.drew.metadata.Metadata;26 import com.drew.metadata.exif.ExifReader;27 import com.drew.metadata.iptc.IptcReader;28 import com.drew.metadata.jpeg.JpegCommentReader;29 import com.drew.metadata.jpeg.JpegDirectory;30 import com.drew.metadata.jpeg.JpegReader;31 32 23 import java.io.File; 24 import java.io.FileInputStream; 33 25 import java.io.IOException; 34 26 import java.io.InputStream; 27 import java.util.Arrays; 28 import java.util.HashSet; 29 import java.util.Set; 30 31 import com.drew.lang.StreamReader; 32 import com.drew.lang.annotations.NotNull; 33 import com.drew.lang.annotations.Nullable; 34 import com.drew.metadata.Metadata; 35 //import com.drew.metadata.adobe.AdobeJpegReader; 36 import com.drew.metadata.exif.ExifReader; 37 //import com.drew.metadata.icc.IccReader; 38 import com.drew.metadata.iptc.IptcReader; 39 //import com.drew.metadata.jfif.JfifReader; 40 import com.drew.metadata.jpeg.JpegCommentReader; 41 import com.drew.metadata.jpeg.JpegReader; 42 //import com.drew.metadata.photoshop.PhotoshopReader; 43 //import com.drew.metadata.xmp.XmpReader; 35 44 36 45 /** 37 * Obtains all available metadata from J pegformatted files.46 * Obtains all available metadata from JPEG formatted files. 38 47 * 39 * @author Drew Noakes http://drewnoakes.com 48 * @author Drew Noakes https://drewnoakes.com 40 49 */ 41 50 public class JpegMetadataReader 42 51 { 43 // TODO investigate supporting javax.imageio 44 // public static Metadata readMetadata(IIOMetadata metadata) throws JpegProcessingException {} 45 // public static Metadata readMetadata(ImageInputStream in) throws JpegProcessingException{} 46 // public static Metadata readMetadata(IIOImage image) throws JpegProcessingException{} 47 // public static Metadata readMetadata(ImageReader reader) throws JpegProcessingException{} 52 public static final Iterable<JpegSegmentMetadataReader> ALL_READERS = Arrays.asList( 53 new JpegReader(), 54 new JpegCommentReader(), 55 //new JfifReader(), 56 new ExifReader(), 57 //new XmpReader(), 58 //new IccReader(), 59 //new PhotoshopReader(), 60 new IptcReader()//, 61 //new AdobeJpegReader() 62 ); 48 63 49 64 @NotNull 50 public static Metadata readMetadata(@NotNull InputStream inputStream) throws JpegProcessingException 65 public static Metadata readMetadata(@NotNull InputStream inputStream, @Nullable Iterable<JpegSegmentMetadataReader> readers) throws JpegProcessingException, IOException 51 66 { 52 return readMetadata(inputStream, true); 67 Metadata metadata = new Metadata(); 68 process(metadata, inputStream, readers); 69 return metadata; 53 70 } 54 71 55 72 @NotNull 56 public static Metadata readMetadata(@NotNull InputStream inputStream , final boolean waitForBytes) throws JpegProcessingException73 public static Metadata readMetadata(@NotNull InputStream inputStream) throws JpegProcessingException, IOException 57 74 { 58 JpegSegmentReader segmentReader = new JpegSegmentReader(inputStream, waitForBytes); 59 return extractMetadataFromJpegSegmentReader(segmentReader.getSegmentData()); 75 return readMetadata(inputStream, null); 76 } 77 78 @NotNull 79 public static Metadata readMetadata(@NotNull File file, @Nullable Iterable<JpegSegmentMetadataReader> readers) throws JpegProcessingException, IOException 80 { 81 InputStream inputStream = null; 82 try 83 { 84 inputStream = new FileInputStream(file); 85 return readMetadata(inputStream, readers); 86 } finally { 87 if (inputStream != null) 88 inputStream.close(); 89 } 60 90 } 61 91 … … 63 93 public static Metadata readMetadata(@NotNull File file) throws JpegProcessingException, IOException 64 94 { 65 JpegSegmentReader segmentReader = new JpegSegmentReader(file); 66 return extractMetadataFromJpegSegmentReader(segmentReader.getSegmentData()); 95 return readMetadata(file, null); 67 96 } 68 97 69 @NotNull 70 public static Metadata extractMetadataFromJpegSegmentReader(@NotNull JpegSegmentData segmentReader) 98 public static void process(@NotNull Metadata metadata, @NotNull InputStream inputStream) throws JpegProcessingException, IOException 71 99 { 72 final Metadata metadata = new Metadata(); 100 process(metadata, inputStream, null); 101 } 73 102 74 // Loop through looking for all SOFn segments. When we find one, we know what type of compression 75 // was used for the JPEG, and we can process the JPEG metadata in the segment too. 76 for (byte i = 0; i < 16; i++) { 77 // There are no SOF4 or SOF12 segments, so don't bother 78 if (i == 4 || i == 12) 79 continue; 80 // Should never have more than one SOFn for a given 'n'. 81 byte[] jpegSegment = segmentReader.getSegment((byte)(JpegSegmentReader.SEGMENT_SOF0 + i)); 82 if (jpegSegment == null) 83 continue; 84 JpegDirectory directory = metadata.getOrCreateDirectory(JpegDirectory.class); 85 directory.setInt(JpegDirectory.TAG_JPEG_COMPRESSION_TYPE, i); 86 new JpegReader().extract(new ByteArrayReader(jpegSegment), metadata); 87 break; 88 } 103 public static void process(@NotNull Metadata metadata, @NotNull InputStream inputStream, @Nullable Iterable<JpegSegmentMetadataReader> readers) throws JpegProcessingException, IOException 104 { 105 if (readers == null) 106 readers = ALL_READERS; 89 107 90 // There should never be more than one COM segment. 91 byte[] comSegment = segmentReader.getSegment(JpegSegmentReader.SEGMENT_COM); 92 if (comSegment != null) 93 new JpegCommentReader().extract(new ByteArrayReader(comSegment), metadata); 94 95 // Loop through all APP1 segments, checking the leading bytes to identify the format of each. 96 for (byte[] app1Segment : segmentReader.getSegments(JpegSegmentReader.SEGMENT_APP1)) { 97 if (app1Segment.length > 3 && "EXIF".equalsIgnoreCase(new String(app1Segment, 0, 4))) 98 new ExifReader().extract(new ByteArrayReader(app1Segment), metadata); 99 100 //if (app1Segment.length > 27 && "http://ns.adobe.com/xap/1.0/".equalsIgnoreCase(new String(app1Segment, 0, 28))) 101 // new XmpReader().extract(new ByteArrayReader(app1Segment), metadata); 102 } 103 104 // Loop through all APPD segments, checking the leading bytes to identify the format of each. 105 for (byte[] appdSegment : segmentReader.getSegments(JpegSegmentReader.SEGMENT_APPD)) { 106 if (appdSegment.length > 12 && "Photoshop 3.0".compareTo(new String(appdSegment, 0, 13))==0) { 107 //new PhotoshopReader().extract(new ByteArrayReader(appdSegment), metadata); 108 } else { 109 // TODO might be able to check for a leading 0x1c02 for IPTC data... 110 new IptcReader().extract(new ByteArrayReader(appdSegment), metadata); 108 Set<JpegSegmentType> segmentTypes = new HashSet<JpegSegmentType>(); 109 for (JpegSegmentMetadataReader reader : readers) { 110 for (JpegSegmentType type : reader.getSegmentTypes()) { 111 segmentTypes.add(type); 111 112 } 112 113 } 113 114 114 return metadata; 115 JpegSegmentData segmentData = JpegSegmentReader.readSegments(new StreamReader(inputStream), segmentTypes); 116 117 processJpegSegmentData(metadata, readers, segmentData); 118 } 119 120 public static void processJpegSegmentData(Metadata metadata, Iterable<JpegSegmentMetadataReader> readers, JpegSegmentData segmentData) 121 { 122 // Pass the appropriate byte arrays to each reader. 123 for (JpegSegmentMetadataReader reader : readers) { 124 for (JpegSegmentType segmentType : reader.getSegmentTypes()) { 125 for (byte[] segmentBytes : segmentData.getSegments(segmentType)) { 126 if (reader.canProcess(segmentBytes, segmentType)) { 127 reader.extract(segmentBytes, metadata, segmentType); 128 } 129 } 130 } 131 } 115 132 } 116 133 … … 120 137 } 121 138 } 122 -
trunk/src/com/drew/imaging/jpeg/JpegProcessingException.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http://drewnoakes.com/code/exif/ 19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.imaging.jpeg; … … 25 25 26 26 /** 27 * An exception class thrown upon unexpected and fatal conditions while processing a J pegfile.27 * An exception class thrown upon unexpected and fatal conditions while processing a JPEG file. 28 28 * 29 * @author Drew Noakes http://drewnoakes.com 29 * @author Drew Noakes https://drewnoakes.com 30 30 */ 31 31 public class JpegProcessingException extends ImageProcessingException -
trunk/src/com/drew/imaging/jpeg/JpegSegmentData.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http://drewnoakes.com/code/exif/ 19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.imaging.jpeg; … … 24 24 import com.drew.lang.annotations.Nullable; 25 25 26 import java.io.*; 27 import java.util.ArrayList; 28 import java.util.HashMap; 29 import java.util.List; 26 import java.util.*; 30 27 31 28 /** 32 * Holds a collection of Jpeg data segments. This need not necessarily be all segments 33 * within the Jpeg. For example, it may be convenient to store only the non-image 34 * segments when analysing (or serializing) metadata. 35 * <p/> 36 * Segments are keyed via their segment marker (a byte). Where multiple segments use the 37 * same segment marker, they will all be stored and available. 38 * 39 * @author Drew Noakes http://drewnoakes.com 29 * Holds a collection of JPEG data segments. This need not necessarily be all segments 30 * within the JPEG. For example, it may be convenient to store only the non-image 31 * segments when analysing metadata. 32 * <p> 33 * Segments are keyed via their {@link JpegSegmentType}. Where multiple segments use the 34 * same segment type, they will all be stored and available. 35 * <p> 36 * Each segment type may contain multiple entries. Conceptually the model is: 37 * <code>Map<JpegSegmentType, Collection<byte[]>></code>. This class provides 38 * convenience methods around that structure. 39 * 40 * @author Drew Noakes https://drewnoakes.com 40 41 */ 41 public class JpegSegmentData implements Serializable42 public class JpegSegmentData 42 43 { 43 private static final long serialVersionUID = 7110175216435025451L; 44 45 /** A map of byte[], keyed by the segment marker */ 44 // TODO key this on JpegSegmentType rather than Byte, and hopefully lose much of the use of 'byte' with this class 46 45 @NotNull 47 46 private final HashMap<Byte, List<byte[]>> _segmentDataMap = new HashMap<Byte, List<byte[]>>(10); … … 49 48 /** 50 49 * Adds segment bytes to the collection. 51 * @param segmentMarker 52 * @param segmentBytes 53 */ 54 @SuppressWarnings({ "MismatchedQueryAndUpdateOfCollection" }) 55 public void addSegment(byte segmentMarker, @NotNull byte[] segmentBytes) 56 { 57 final List<byte[]> segmentList = getOrCreateSegmentList(segmentMarker); 58 segmentList.add(segmentBytes); 59 } 60 61 /** 62 * Gets the first Jpeg segment data for the specified marker. 63 * @param segmentMarker the byte identifier for the desired segment 50 * 51 * @param segmentType the type of the segment being added 52 * @param segmentBytes the byte array holding data for the segment being added 53 */ 54 @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection"}) 55 public void addSegment(byte segmentType, @NotNull byte[] segmentBytes) 56 { 57 getOrCreateSegmentList(segmentType).add(segmentBytes); 58 } 59 60 /** 61 * Gets the set of JPEG segment type identifiers. 62 */ 63 public Iterable<JpegSegmentType> getSegmentTypes() 64 { 65 Set<JpegSegmentType> segmentTypes = new HashSet<JpegSegmentType>(); 66 67 for (Byte segmentTypeByte : _segmentDataMap.keySet()) 68 { 69 JpegSegmentType segmentType = JpegSegmentType.fromByte(segmentTypeByte); 70 if (segmentType == null) { 71 throw new IllegalStateException("Should not have a segmentTypeByte that is not in the enum: " + Integer.toHexString(segmentTypeByte)); 72 } 73 segmentTypes.add(segmentType); 74 } 75 76 return segmentTypes; 77 } 78 79 /** 80 * Gets the first JPEG segment data for the specified type. 81 * 82 * @param segmentType the JpegSegmentType for the desired segment 64 83 * @return a byte[] containing segment data or null if no data exists for that segment 65 84 */ 66 85 @Nullable 67 public byte[] getSegment(byte segmentMarker) 68 { 69 return getSegment(segmentMarker, 0); 70 } 71 72 /** 73 * Gets segment data for a specific occurrence and marker. Use this method when more than one occurrence 74 * of segment data for a given marker exists. 75 * @param segmentMarker identifies the required segment 76 * @param occurrence the zero-based index of the occurrence 77 * @return the segment data as a byte[], or null if no segment exists for the marker & occurrence 78 */ 79 @Nullable 80 public byte[] getSegment(byte segmentMarker, int occurrence) 81 { 82 final List<byte[]> segmentList = getSegmentList(segmentMarker); 83 84 if (segmentList==null || segmentList.size()<=occurrence) 85 return null; 86 else 87 return segmentList.get(occurrence); 88 } 89 90 /** 91 * Returns all instances of a given Jpeg segment. If no instances exist, an empty sequence is returned. 92 * 93 * @param segmentMarker a number which identifies the type of Jpeg segment being queried 94 * @return zero or more byte arrays, each holding the data of a Jpeg segment 95 */ 96 @NotNull 97 public Iterable<byte[]> getSegments(byte segmentMarker) 98 { 99 final List<byte[]> segmentList = getSegmentList(segmentMarker); 100 return segmentList==null ? new ArrayList<byte[]>() : segmentList; 101 } 102 103 @Nullable 104 public List<byte[]> getSegmentList(byte segmentMarker) 105 { 106 return _segmentDataMap.get(Byte.valueOf(segmentMarker)); 107 } 108 109 @NotNull 110 private List<byte[]> getOrCreateSegmentList(byte segmentMarker) 86 public byte[] getSegment(byte segmentType) 87 { 88 return getSegment(segmentType, 0); 89 } 90 91 /** 92 * Gets the first JPEG segment data for the specified type. 93 * 94 * @param segmentType the JpegSegmentType for the desired segment 95 * @return a byte[] containing segment data or null if no data exists for that segment 96 */ 97 @Nullable 98 public byte[] getSegment(@NotNull JpegSegmentType segmentType) 99 { 100 return getSegment(segmentType.byteValue, 0); 101 } 102 103 /** 104 * Gets segment data for a specific occurrence and type. Use this method when more than one occurrence 105 * of segment data for a given type exists. 106 * 107 * @param segmentType identifies the required segment 108 * @param occurrence the zero-based index of the occurrence 109 * @return the segment data as a byte[], or null if no segment exists for the type & occurrence 110 */ 111 @Nullable 112 public byte[] getSegment(@NotNull JpegSegmentType segmentType, int occurrence) 113 { 114 return getSegment(segmentType.byteValue, occurrence); 115 } 116 117 /** 118 * Gets segment data for a specific occurrence and type. Use this method when more than one occurrence 119 * of segment data for a given type exists. 120 * 121 * @param segmentType identifies the required segment 122 * @param occurrence the zero-based index of the occurrence 123 * @return the segment data as a byte[], or null if no segment exists for the type & occurrence 124 */ 125 @Nullable 126 public byte[] getSegment(byte segmentType, int occurrence) 127 { 128 final List<byte[]> segmentList = getSegmentList(segmentType); 129 130 return segmentList != null && segmentList.size() > occurrence 131 ? segmentList.get(occurrence) 132 : null; 133 } 134 135 /** 136 * Returns all instances of a given JPEG segment. If no instances exist, an empty sequence is returned. 137 * 138 * @param segmentType a number which identifies the type of JPEG segment being queried 139 * @return zero or more byte arrays, each holding the data of a JPEG segment 140 */ 141 @NotNull 142 public Iterable<byte[]> getSegments(@NotNull JpegSegmentType segmentType) 143 { 144 return getSegments(segmentType.byteValue); 145 } 146 147 /** 148 * Returns all instances of a given JPEG segment. If no instances exist, an empty sequence is returned. 149 * 150 * @param segmentType a number which identifies the type of JPEG segment being queried 151 * @return zero or more byte arrays, each holding the data of a JPEG segment 152 */ 153 @NotNull 154 public Iterable<byte[]> getSegments(byte segmentType) 155 { 156 final List<byte[]> segmentList = getSegmentList(segmentType); 157 return segmentList == null ? new ArrayList<byte[]>() : segmentList; 158 } 159 160 @Nullable 161 private List<byte[]> getSegmentList(byte segmentType) 162 { 163 return _segmentDataMap.get(segmentType); 164 } 165 166 @NotNull 167 private List<byte[]> getOrCreateSegmentList(byte segmentType) 111 168 { 112 169 List<byte[]> segmentList; 113 if (_segmentDataMap.containsKey(segment Marker)) {114 segmentList = _segmentDataMap.get(segment Marker);170 if (_segmentDataMap.containsKey(segmentType)) { 171 segmentList = _segmentDataMap.get(segmentType); 115 172 } else { 116 173 segmentList = new ArrayList<byte[]>(); 117 _segmentDataMap.put(segment Marker, segmentList);174 _segmentDataMap.put(segmentType, segmentList); 118 175 } 119 176 return segmentList; … … 121 178 122 179 /** 123 * Returns the count of segment data byte arrays stored for a given segment marker. 124 * @param segmentMarker identifies the required segment 180 * Returns the count of segment data byte arrays stored for a given segment type. 181 * 182 * @param segmentType identifies the required segment 125 183 * @return the segment count (zero if no segments exist). 126 184 */ 127 public int getSegmentCount(byte segmentMarker) 128 { 129 final List<byte[]> segmentList = getSegmentList(segmentMarker); 185 public int getSegmentCount(@NotNull JpegSegmentType segmentType) 186 { 187 return getSegmentCount(segmentType.byteValue); 188 } 189 190 /** 191 * Returns the count of segment data byte arrays stored for a given segment type. 192 * 193 * @param segmentType identifies the required segment 194 * @return the segment count (zero if no segments exist). 195 */ 196 public int getSegmentCount(byte segmentType) 197 { 198 final List<byte[]> segmentList = getSegmentList(segmentType); 130 199 return segmentList == null ? 0 : segmentList.size(); 131 200 } … … 133 202 /** 134 203 * Removes a specified instance of a segment's data from the collection. Use this method when more than one 135 * occurrence of segment data for a given marker exists. 136 * @param segmentMarker identifies the required segment 137 * @param occurrence the zero-based index of the segment occurrence to remove. 138 */ 139 @SuppressWarnings({ "MismatchedQueryAndUpdateOfCollection" }) 140 public void removeSegmentOccurrence(byte segmentMarker, int occurrence) 141 { 142 final List<byte[]> segmentList = _segmentDataMap.get(Byte.valueOf(segmentMarker)); 204 * occurrence of segment data exists for a given type exists. 205 * 206 * @param segmentType identifies the required segment 207 * @param occurrence the zero-based index of the segment occurrence to remove. 208 */ 209 @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection"}) 210 public void removeSegmentOccurrence(@NotNull JpegSegmentType segmentType, int occurrence) 211 { 212 removeSegmentOccurrence(segmentType.byteValue, occurrence); 213 } 214 215 /** 216 * Removes a specified instance of a segment's data from the collection. Use this method when more than one 217 * occurrence of segment data exists for a given type exists. 218 * 219 * @param segmentType identifies the required segment 220 * @param occurrence the zero-based index of the segment occurrence to remove. 221 */ 222 @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection"}) 223 public void removeSegmentOccurrence(byte segmentType, int occurrence) 224 { 225 final List<byte[]> segmentList = _segmentDataMap.get(segmentType); 143 226 segmentList.remove(occurrence); 144 227 } 145 228 146 229 /** 147 * Removes all segments from the collection having the specified marker. 148 * @param segmentMarker identifies the required segment 149 */ 150 public void removeSegment(byte segmentMarker) 151 { 152 _segmentDataMap.remove(Byte.valueOf(segmentMarker)); 153 } 154 155 /** 156 * Determines whether data is present for a given segment marker. 157 * @param segmentMarker identifies the required segment 230 * Removes all segments from the collection having the specified type. 231 * 232 * @param segmentType identifies the required segment 233 */ 234 public void removeSegment(@NotNull JpegSegmentType segmentType) 235 { 236 removeSegment(segmentType.byteValue); 237 } 238 239 /** 240 * Removes all segments from the collection having the specified type. 241 * 242 * @param segmentType identifies the required segment 243 */ 244 public void removeSegment(byte segmentType) 245 { 246 _segmentDataMap.remove(segmentType); 247 } 248 249 /** 250 * Determines whether data is present for a given segment type. 251 * 252 * @param segmentType identifies the required segment 158 253 * @return true if data exists, otherwise false 159 254 */ 160 public boolean containsSegment(byte segmentMarker) 161 { 162 return _segmentDataMap.containsKey(Byte.valueOf(segmentMarker)); 163 } 164 165 /** 166 * Serialises the contents of a JpegSegmentData to a file. 167 * @param file to file to write from 168 * @param segmentData the data to write 169 * @throws IOException if problems occur while writing 170 */ 171 public static void toFile(@NotNull File file, @NotNull JpegSegmentData segmentData) throws IOException 172 { 173 FileOutputStream fileOutputStream = null; 174 try 175 { 176 fileOutputStream = new FileOutputStream(file); 177 new ObjectOutputStream(fileOutputStream).writeObject(segmentData); 178 } 179 finally 180 { 181 if (fileOutputStream!=null) 182 fileOutputStream.close(); 183 } 184 } 185 186 /** 187 * Deserialises the contents of a JpegSegmentData from a file. 188 * @param file the file to read from 189 * @return the JpegSegmentData as read 190 * @throws IOException if problems occur while reading 191 * @throws ClassNotFoundException if problems occur while deserialising 192 */ 193 @NotNull 194 public static JpegSegmentData fromFile(@NotNull File file) throws IOException, ClassNotFoundException 195 { 196 ObjectInputStream inputStream = null; 197 try 198 { 199 inputStream = new ObjectInputStream(new FileInputStream(file)); 200 return (JpegSegmentData)inputStream.readObject(); 201 } 202 finally 203 { 204 if (inputStream!=null) 205 inputStream.close(); 206 } 255 public boolean containsSegment(@NotNull JpegSegmentType segmentType) 256 { 257 return containsSegment(segmentType.byteValue); 258 } 259 260 /** 261 * Determines whether data is present for a given segment type. 262 * 263 * @param segmentType identifies the required segment 264 * @return true if data exists, otherwise false 265 */ 266 public boolean containsSegment(byte segmentType) 267 { 268 return _segmentDataMap.containsKey(segmentType); 207 269 } 208 270 } -
trunk/src/com/drew/imaging/jpeg/JpegSegmentReader.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http://drewnoakes.com/code/exif/ 19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.imaging.jpeg; 22 22 23 import com.drew.lang.SequentialReader; 24 import com.drew.lang.StreamReader; 23 25 import com.drew.lang.annotations.NotNull; 24 26 import com.drew.lang.annotations.Nullable; 25 27 26 import java.io.*; 28 import java.io.File; 29 import java.io.FileInputStream; 30 import java.io.IOException; 31 import java.util.HashSet; 32 import java.util.Set; 27 33 28 34 /** 29 * Performs read functions of Jpeg files, returning specific file segments. 30 * @author Drew Noakes http://drewnoakes.com 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 31 42 */ 32 43 public class JpegSegmentReader 33 44 { 34 // TODO add a findAvailableSegments() method35 // TODO add more segment identifiers36 // TODO add a getSegmentDescription() method, returning for example 'App1 application data segment, commonly containing Exif data'37 38 @NotNull39 private final JpegSegmentData _segmentData;40 41 45 /** 42 46 * Private, because this segment crashes my algorithm, and searching for it doesn't work (yet). 43 47 */ 44 private static final byte SEGMENT_SOS = (byte)0xDA; 48 private static final byte SEGMENT_SOS = (byte) 0xDA; 45 49 46 50 /** 47 51 * Private, because one wouldn't search for it. 48 52 */ 49 private static final byte MARKER_EOI = (byte)0xD9; 50 51 /** APP0 Jpeg segment identifier -- JFIF data (also JFXX apparently). */ 52 public static final byte SEGMENT_APP0 = (byte)0xE0; 53 /** APP1 Jpeg segment identifier -- where Exif data is kept. XMP data is also kept in here, though usually in a second instance. */ 54 public static final byte SEGMENT_APP1 = (byte)0xE1; 55 /** APP2 Jpeg segment identifier. */ 56 public static final byte SEGMENT_APP2 = (byte)0xE2; 57 /** APP3 Jpeg segment identifier. */ 58 public static final byte SEGMENT_APP3 = (byte)0xE3; 59 /** APP4 Jpeg segment identifier. */ 60 public static final byte SEGMENT_APP4 = (byte)0xE4; 61 /** APP5 Jpeg segment identifier. */ 62 public static final byte SEGMENT_APP5 = (byte)0xE5; 63 /** APP6 Jpeg segment identifier. */ 64 public static final byte SEGMENT_APP6 = (byte)0xE6; 65 /** APP7 Jpeg segment identifier. */ 66 public static final byte SEGMENT_APP7 = (byte)0xE7; 67 /** APP8 Jpeg segment identifier. */ 68 public static final byte SEGMENT_APP8 = (byte)0xE8; 69 /** APP9 Jpeg segment identifier. */ 70 public static final byte SEGMENT_APP9 = (byte)0xE9; 71 /** APPA (App10) Jpeg segment identifier -- can hold Unicode comments. */ 72 public static final byte SEGMENT_APPA = (byte)0xEA; 73 /** APPB (App11) Jpeg segment identifier. */ 74 public static final byte SEGMENT_APPB = (byte)0xEB; 75 /** APPC (App12) Jpeg segment identifier. */ 76 public static final byte SEGMENT_APPC = (byte)0xEC; 77 /** APPD (App13) Jpeg segment identifier -- IPTC data in here. */ 78 public static final byte SEGMENT_APPD = (byte)0xED; 79 /** APPE (App14) Jpeg segment identifier. */ 80 public static final byte SEGMENT_APPE = (byte)0xEE; 81 /** APPF (App15) Jpeg segment identifier. */ 82 public static final byte SEGMENT_APPF = (byte)0xEF; 83 /** Start Of Image segment identifier. */ 84 public static final byte SEGMENT_SOI = (byte)0xD8; 85 /** Define Quantization Table segment identifier. */ 86 public static final byte SEGMENT_DQT = (byte)0xDB; 87 /** Define Huffman Table segment identifier. */ 88 public static final byte SEGMENT_DHT = (byte)0xC4; 89 /** Start-of-Frame Zero segment identifier. */ 90 public static final byte SEGMENT_SOF0 = (byte)0xC0; 91 /** Jpeg comment segment identifier. */ 92 public static final byte SEGMENT_COM = (byte)0xFE; 53 private static final byte MARKER_EOI = (byte) 0xD9; 93 54 94 55 /** 95 * Creates a JpegSegmentReader for a specific file. 96 * @param file the Jpeg file to read segments from 56 * Processes the provided JPEG data, and extracts the specified JPEG segments into a {@link JpegSegmentData} object. 57 * <p> 58 * Will not return SOS (start of scan) or EOI (end of image) segments. 59 * 60 * @param file a {@link File} from which the JPEG data will be read. 61 * @param segmentTypes the set of JPEG segments types that are to be returned. If this argument is <code>null</code> 62 * then all found segment types are returned. 97 63 */ 98 @ SuppressWarnings({ "ConstantConditions" })99 public JpegSegment Reader(@NotNull File file) throws JpegProcessingException, IOException64 @NotNull 65 public static JpegSegmentData readSegments(@NotNull File file, @Nullable Iterable<JpegSegmentType> segmentTypes) throws JpegProcessingException, IOException 100 66 { 101 if (file==null) 102 throw new NullPointerException(); 103 104 InputStream inputStream = null; 67 FileInputStream stream = null; 105 68 try { 106 inputStream = new FileInputStream(file);107 _segmentData =readSegments(newBufferedInputStream(inputStream),false);69 stream = new FileInputStream(file); 70 return readSegments(new StreamReader(stream), segmentTypes); 108 71 } finally { 109 if (inputStream != null) 110 inputStream.close(); 72 if (stream != null) { 73 stream.close(); 74 } 111 75 } 112 76 } 113 77 114 78 /** 115 * Creates a JpegSegmentReader for a byte array. 116 * @param fileContents the byte array containing Jpeg data 79 * Processes the provided JPEG data, and extracts the specified JPEG segments into a {@link JpegSegmentData} object. 80 * <p> 81 * Will not return SOS (start of scan) or EOI (end of image) segments. 82 * 83 * @param reader a {@link SequentialReader} from which the JPEG data will be read. It must be positioned at the 84 * beginning of the JPEG data stream. 85 * @param segmentTypes the set of JPEG segments types that are to be returned. If this argument is <code>null</code> 86 * then all found segment types are returned. 117 87 */ 118 @ SuppressWarnings({ "ConstantConditions" })119 public JpegSegment Reader(@NotNullbyte[] fileContents) throws JpegProcessingException88 @NotNull 89 public static JpegSegmentData readSegments(@NotNull final SequentialReader reader, @Nullable Iterable<JpegSegmentType> segmentTypes) throws JpegProcessingException, IOException 120 90 { 121 if (fileContents==null)122 throw new NullPointerException();91 // Must be big-endian 92 assert (reader.isMotorolaByteOrder()); 123 93 124 BufferedInputStream stream = new BufferedInputStream(new ByteArrayInputStream(fileContents)); 125 _segmentData = readSegments(stream, false); 94 // first two bytes should be JPEG magic number 95 final int magicNumber = reader.getUInt16(); 96 if (magicNumber != 0xFFD8) { 97 throw new JpegProcessingException("JPEG data is expected to begin with 0xFFD8 (ÿØ) not 0x" + Integer.toHexString(magicNumber)); 98 } 99 100 Set<Byte> segmentTypeBytes = null; 101 if (segmentTypes != null) { 102 segmentTypeBytes = new HashSet<Byte>(); 103 for (JpegSegmentType segmentType : segmentTypes) { 104 segmentTypeBytes.add(segmentType.byteValue); 105 } 106 } 107 108 JpegSegmentData segmentData = new JpegSegmentData(); 109 110 do { 111 // Find the segment marker. Markers are zero or more 0xFF bytes, followed 112 // by a 0xFF and then a byte not equal to 0x00 or 0xFF. 113 114 final short segmentIdentifier = reader.getUInt8(); 115 116 // We must have at least one 0xFF byte 117 if (segmentIdentifier != 0xFF) 118 throw new JpegProcessingException("Expected JPEG segment start identifier 0xFF, not 0x" + Integer.toHexString(segmentIdentifier).toUpperCase()); 119 120 // Read until we have a non-0xFF byte. This identifies the segment type. 121 byte segmentType = reader.getInt8(); 122 while (segmentType == (byte)0xFF) 123 segmentType = reader.getInt8(); 124 125 if (segmentType == 0) 126 throw new JpegProcessingException("Expected non-zero byte as part of JPEG marker identifier"); 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); 126 162 } 127 163 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 164 private JpegSegmentReader() throws Exception 134 165 { 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); 143 } 144 145 /** 146 * Reads the first instance of a given Jpeg segment, returning the contents as 147 * a byte array. 148 * @param segmentMarker the byte identifier for the desired segment 149 * @return the byte array if found, else null 150 */ 151 @Nullable 152 public byte[] readSegment(byte segmentMarker) 153 { 154 return readSegment(segmentMarker, 0); 155 } 156 157 /** 158 * Reads the Nth instance of a given Jpeg segment, returning the contents as a byte array. 159 * 160 * @param segmentMarker the byte identifier for the desired segment 161 * @param occurrence the occurrence of the specified segment within the jpeg file 162 * @return the byte array if found, else null 163 */ 164 @Nullable 165 public byte[] readSegment(byte segmentMarker, int occurrence) 166 { 167 return _segmentData.getSegment(segmentMarker, occurrence); 168 } 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 */ 187 public final int getSegmentCount(byte segmentMarker) 188 { 189 return _segmentData.getSegmentCount(segmentMarker); 190 } 191 192 /** 193 * Returns the JpegSegmentData object used by this reader. 194 * @return the JpegSegmentData object. 195 */ 196 @NotNull 197 public final JpegSegmentData getSegmentData() 198 { 199 return _segmentData; 200 } 201 202 @NotNull 203 private JpegSegmentData readSegments(@NotNull final BufferedInputStream jpegInputStream, boolean waitForBytes) throws JpegProcessingException 204 { 205 JpegSegmentData segmentData = new JpegSegmentData(); 206 207 try { 208 int offset = 0; 209 // first two bytes should be jpeg magic number 210 byte[] headerBytes = new byte[2]; 211 if (jpegInputStream.read(headerBytes, 0, 2)!=2) 212 throw new JpegProcessingException("not a jpeg file"); 213 final boolean hasValidHeader = (headerBytes[0] & 0xFF) == 0xFF && (headerBytes[1] & 0xFF) == 0xD8; 214 if (!hasValidHeader) 215 throw new JpegProcessingException("not a jpeg file"); 216 217 offset += 2; 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 223 // next byte is 0xFF 224 byte segmentIdentifier = (byte)(jpegInputStream.read() & 0xFF); 225 if ((segmentIdentifier & 0xFF) != 0xFF) { 226 throw new JpegProcessingException("expected jpeg segment start identifier 0xFF at offset " + offset + ", not 0x" + Integer.toHexString(segmentIdentifier & 0xFF)); 227 } 228 offset++; 229 // next byte is <segment-marker> 230 byte thisSegmentMarker = (byte)(jpegInputStream.read() & 0xFF); 231 offset++; 232 // next 2-bytes are <segment-size>: [high-byte] [low-byte] 233 byte[] segmentLengthBytes = new byte[2]; 234 if (jpegInputStream.read(segmentLengthBytes, 0, 2) != 2) 235 throw new JpegProcessingException("Jpeg data ended unexpectedly."); 236 offset += 2; 237 int segmentLength = ((segmentLengthBytes[0] << 8) & 0xFF00) | (segmentLengthBytes[1] & 0xFF); 238 // segment length includes size bytes, so subtract two 239 segmentLength -= 2; 240 if (!checkForBytesOnStream(jpegInputStream, segmentLength, waitForBytes)) 241 throw new JpegProcessingException("segment size would extend beyond file stream length"); 242 if (segmentLength < 0) 243 throw new JpegProcessingException("segment size would be less than zero"); 244 byte[] segmentBytes = new byte[segmentLength]; 245 if (jpegInputStream.read(segmentBytes, 0, segmentLength) != segmentLength) 246 throw new JpegProcessingException("Jpeg data ended unexpectedly."); 247 offset += segmentLength; 248 if ((thisSegmentMarker & 0xFF) == (SEGMENT_SOS & 0xFF)) { 249 // The 'Start-Of-Scan' segment's length doesn't include the image data, instead would 250 // have to search for the two bytes: 0xFF 0xD9 (EOI). 251 // It comes last so simply return at this point 252 return segmentData; 253 } else if ((thisSegmentMarker & 0xFF) == (MARKER_EOI & 0xFF)) { 254 // the 'End-Of-Image' segment -- this should never be found in this fashion 255 return segmentData; 256 } else { 257 segmentData.addSegment(thisSegmentMarker, segmentBytes); 258 } 259 } while (true); 260 } catch (IOException ioe) { 261 throw new JpegProcessingException("IOException processing Jpeg file: " + ioe.getMessage(), ioe); 262 } finally { 263 try { 264 if (jpegInputStream != null) { 265 jpegInputStream.close(); 266 } 267 } catch (IOException ioe) { 268 throw new JpegProcessingException("IOException processing Jpeg file: " + ioe.getMessage(), ioe); 269 } 270 } 271 } 272 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 288 } 289 count--; 290 } 291 return false; 166 throw new Exception("Not intended for instantiation."); 292 167 } 293 168 }
Note:
See TracChangeset
for help on using the changeset viewer.