Changeset 6127 in josm for trunk/src/com/drew/imaging
- Timestamp:
- 2013-08-09T18:05:11+02:00 (12 years ago)
- Location:
- trunk/src/com/drew/imaging
- Files:
-
- 1 added
- 5 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/com/drew/imaging/PhotographicConversions.java
r4231 r6127 1 1 /* 2 * This is public domain software - that is, you can do whatever you want 3 * with it, and include it software that is licensed under the GNU or the 4 * BSD license, or whatever other licence you choose, including proprietary 5 * closed source licenses. I do ask that you leave this header in tact. 2 * Copyright 2002-2012 Drew Noakes 6 3 * 7 * If you make modifications to this code that you think would benefit the 8 * wider community, please send me a copy and I'll post it on my site. 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 9 7 * 10 * If you make use of this code, I'd appreciate hearing about it. 11 * drew@drewnoakes.com 12 * Latest version of this software kept at 13 * http://drewnoakes.com/ 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/ 14 20 */ 15 21 package com.drew.imaging; … … 17 23 /** 18 24 * Contains helper methods that perform photographic conversions. 25 * 26 * @author Drew Noakes http://drewnoakes.com 19 27 */ 20 public class PhotographicConversions 28 public final class PhotographicConversions 21 29 { 22 30 public final static double ROOT_TWO = Math.sqrt(2); 23 31 24 private PhotographicConversions() 25 {} 32 private PhotographicConversions() throws Exception 33 { 34 throw new Exception("Not intended for instantiation."); 35 } 26 36 27 37 /** 28 38 * Converts an aperture value to its corresponding F-stop number. 39 * 29 40 * @param aperture the aperture value to convert 30 41 * @return the F-stop number of the specified aperture … … 32 43 public static double apertureToFStop(double aperture) 33 44 { 34 double fStop = Math.pow(ROOT_TWO, aperture); 35 return fStop; 45 return Math.pow(ROOT_TWO, aperture); 36 46 37 // Puzzle?! 38 // jhead uses a different calculation as far as i can tell... this confuses me... 47 // NOTE jhead uses a different calculation as far as i can tell... this confuses me... 39 48 // fStop = (float)Math.exp(aperture * Math.log(2) * 0.5)); 40 49 } … … 42 51 /** 43 52 * Converts a shutter speed to an exposure time. 53 * 44 54 * @param shutterSpeed the shutter speed to convert 45 55 * @return the exposure time of the specified shutter speed … … 47 57 public static double shutterSpeedToExposureTime(double shutterSpeed) 48 58 { 49 return (float)(1 / Math.exp(shutterSpeed * Math.log(2))); 59 return (float) (1 / Math.exp(shutterSpeed * Math.log(2))); 50 60 } 51 61 } -
trunk/src/com/drew/imaging/jpeg/JpegMetadataReader.java
r4231 r6127 1 1 /* 2 * This is public domain software - that is, you can do whatever you want 3 * with it, and include it software that is licensed under the GNU or the 4 * BSD license, or whatever other licence you choose, including proprietary 5 * closed source licenses. I do ask that you leave this header in tact. 2 * Copyright 2002-2012 Drew Noakes 6 3 * 7 * If you make modifications to this code that you think would benefit the 8 * wider community, please send me a copy and I'll post it on my site. 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 9 7 * 10 * If you make use of this code, I'd appreciate hearing about it. 11 * drew@drewnoakes.com 12 * Latest version of this software kept at 13 * http://drewnoakes.com/ 8 * http://www.apache.org/licenses/LICENSE-2.0 14 9 * 15 * Created by dnoakes on 12-Nov-2002 18:51:36 using IntelliJ IDEA. 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/ 16 20 */ 17 21 package com.drew.imaging.jpeg; 18 22 19 import com.drew.metadata.Directory; 23 import com.drew.lang.ByteArrayReader; 24 import com.drew.lang.annotations.NotNull; 20 25 import com.drew.metadata.Metadata; 21 import com.drew.metadata.MetadataException;22 import com.drew.metadata.Tag;23 import com.drew.metadata.exif.ExifDirectory;24 26 import com.drew.metadata.exif.ExifReader; 25 27 import com.drew.metadata.iptc.IptcReader; 26 28 import com.drew.metadata.jpeg.JpegCommentReader; 29 import com.drew.metadata.jpeg.JpegDirectory; 27 30 import com.drew.metadata.jpeg.JpegReader; 28 31 … … 30 33 import java.io.IOException; 31 34 import java.io.InputStream; 32 import java.util.Iterator;33 35 34 36 /** 37 * Obtains all available metadata from Jpeg formatted files. 35 38 * 39 * @author Drew Noakes http://drewnoakes.com 36 40 */ 37 41 public class JpegMetadataReader 38 42 { 43 // TODO investigate supporting javax.imageio 39 44 // public static Metadata readMetadata(IIOMetadata metadata) throws JpegProcessingException {} 40 45 // public static Metadata readMetadata(ImageInputStream in) throws JpegProcessingException{} … … 42 47 // public static Metadata readMetadata(ImageReader reader) throws JpegProcessingException{} 43 48 44 public static Metadata readMetadata(InputStream in) throws JpegProcessingException 49 @NotNull 50 public static Metadata readMetadata(@NotNull InputStream inputStream) throws JpegProcessingException 45 51 { 46 JpegSegmentReader segmentReader = new JpegSegmentReader(in); 47 return extractMetadataFromJpegSegmentReader(segmentReader); 52 return readMetadata(inputStream, true); 48 53 } 49 54 50 public static Metadata readMetadata(File file) throws JpegProcessingException 55 @NotNull 56 public static Metadata readMetadata(@NotNull InputStream inputStream, final boolean waitForBytes) throws JpegProcessingException 57 { 58 JpegSegmentReader segmentReader = new JpegSegmentReader(inputStream, waitForBytes); 59 return extractMetadataFromJpegSegmentReader(segmentReader.getSegmentData()); 60 } 61 62 @NotNull 63 public static Metadata readMetadata(@NotNull File file) throws JpegProcessingException, IOException 51 64 { 52 65 JpegSegmentReader segmentReader = new JpegSegmentReader(file); 53 return extractMetadataFromJpegSegmentReader(segmentReader); 66 return extractMetadataFromJpegSegmentReader(segmentReader.getSegmentData()); 54 67 } 55 68 56 public static Metadata extractMetadataFromJpegSegmentReader(JpegSegmentReader segmentReader) 69 @NotNull 70 public static Metadata extractMetadataFromJpegSegmentReader(@NotNull JpegSegmentData segmentReader) 57 71 { 58 72 final Metadata metadata = new Metadata(); 59 try { 60 byte[] exifSegment = segmentReader.readSegment(JpegSegmentReader.SEGMENT_APP1); 61 new ExifReader(exifSegment).extract(metadata); 62 } catch (JpegProcessingException e) { 63 // in the interests of catching as much data as possible, continue 64 // TODO lodge error message within exif directory? 73 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; 65 88 } 66 89 67 try { 68 byte[] iptcSegment = segmentReader.readSegment(JpegSegmentReader.SEGMENT_APPD); 69 new IptcReader(iptcSegment).extract(metadata); 70 } catch (JpegProcessingException e) { 71 // TODO lodge error message within iptc directory? 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); 72 102 } 73 103 74 try { 75 byte[] jpegSegment = segmentReader.readSegment(JpegSegmentReader.SEGMENT_SOF0); 76 new JpegReader(jpegSegment).extract(metadata); 77 } catch (JpegProcessingException e) { 78 // TODO lodge error message within jpeg directory? 79 } 80 81 try { 82 byte[] jpegCommentSegment = segmentReader.readSegment(JpegSegmentReader.SEGMENT_COM); 83 new JpegCommentReader(jpegCommentSegment).extract(metadata); 84 } catch (JpegProcessingException e) { 85 // TODO lodge error message within jpegcomment directory? 86 } 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); 111 } 112 } 87 113 88 114 return metadata; 89 115 } 90 116 91 private JpegMetadataReader() 117 private JpegMetadataReader() throws Exception 92 118 { 93 } 94 95 public static void main(String[] args) throws MetadataException, IOException 96 { 97 Metadata metadata = null; 98 try { 99 metadata = JpegMetadataReader.readMetadata(new File(args[0])); 100 } catch (Exception e) { 101 e.printStackTrace(System.err); 102 System.exit(1); 103 } 104 105 // iterate over the exif data and print to System.out 106 Iterator directories = metadata.getDirectoryIterator(); 107 while (directories.hasNext()) { 108 Directory directory = (Directory)directories.next(); 109 Iterator tags = directory.getTagIterator(); 110 while (tags.hasNext()) { 111 Tag tag = (Tag)tags.next(); 112 try { 113 System.out.println("[" + directory.getName() + "] " + tag.getTagName() + " = " + tag.getDescription()); 114 } catch (MetadataException e) { 115 System.err.println(e.getMessage()); 116 System.err.println(tag.getDirectoryName() + " " + tag.getTagName() + " (error)"); 117 } 118 } 119 if (directory.hasErrors()) { 120 Iterator errors = directory.getErrors(); 121 while (errors.hasNext()) { 122 System.out.println("ERROR: " + errors.next()); 123 } 124 } 125 } 126 127 if (args.length>1 && args[1].trim().equals("/thumb")) 128 { 129 ExifDirectory directory = (ExifDirectory)metadata.getDirectory(ExifDirectory.class); 130 if (directory.containsThumbnail()) 131 { 132 System.out.println("Writing thumbnail..."); 133 directory.writeThumbnail(args[0].trim() + ".thumb.jpg"); 134 } 135 else 136 { 137 System.out.println("No thumbnail data exists in this image"); 138 } 139 } 119 throw new Exception("Not intended for instantiation"); 140 120 } 141 121 } 122 -
trunk/src/com/drew/imaging/jpeg/JpegProcessingException.java
r4231 r6127 1 1 /* 2 * JpegProcessingException.java2 * Copyright 2002-2012 Drew Noakes 3 3 * 4 * This class is public domain software - that is, you can do whatever you want 5 * with it, and include it software that is licensed under the GNU or the 6 * BSD license, or whatever other licence you choose, including proprietary 7 * closed source licenses. I do ask that you leave this header in tact. 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 8 7 * 9 * If you make modifications to this code that you think would benefit the 10 * wider community, please send me a copy and I'll post it on my site. 8 * http://www.apache.org/licenses/LICENSE-2.0 11 9 * 12 * If you make use of this code, I'd appreciate hearing about it. 13 * drew@drewnoakes.com 14 * Latest version of this software kept at 15 * http://drewnoakes.com/ 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. 16 15 * 17 * Created by dnoakes on 04-Nov-2002 19:31:29 using IntelliJ IDEA. 16 * More information about this project is available at: 17 * 18 * http://drewnoakes.com/code/exif/ 19 * http://code.google.com/p/metadata-extractor/ 18 20 */ 19 21 package com.drew.imaging.jpeg; 20 22 21 import com.drew.lang.CompoundException; 23 import com.drew.imaging.ImageProcessingException; 24 import com.drew.lang.annotations.Nullable; 22 25 23 26 /** 24 * An exception class thrown upon unexpected and fatal conditions while processing 25 * a Jpeg file.26 * @author 27 * An exception class thrown upon unexpected and fatal conditions while processing a Jpeg file. 28 * 29 * @author Drew Noakes http://drewnoakes.com 27 30 */ 28 public class JpegProcessingException extends CompoundException31 public class JpegProcessingException extends ImageProcessingException 29 32 { 30 public JpegProcessingException(String message) 33 private static final long serialVersionUID = -7870179776125450158L; 34 35 public JpegProcessingException(@Nullable String message) 31 36 { 32 37 super(message); 33 38 } 34 39 35 public JpegProcessingException(String message, Throwable cause) 40 public JpegProcessingException(@Nullable String message, @Nullable Throwable cause) 36 41 { 37 42 super(message, cause); 38 43 } 39 44 40 public JpegProcessingException(Throwable cause) 45 public JpegProcessingException(@Nullable Throwable cause) 41 46 { 42 47 super(cause); -
trunk/src/com/drew/imaging/jpeg/JpegSegmentData.java
r4231 r6127 1 1 /* 2 * This is public domain software - that is, you can do whatever you want 3 * with it, and include it software that is licensed under the GNU or the 4 * BSD license, or whatever other licence you choose, including proprietary 5 * closed source licenses. I do ask that you leave this header in tact. 6 * 7 * If you make modifications to this code that you think would benefit the 8 * wider community, please send me a copy and I'll post it on my site. 9 * 10 * If you make use of this code, I'd appreciate hearing about it. 11 * drew@drewnoakes.com 12 * Latest version of this software kept at 13 * http://drewnoakes.com/ 2 * 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/ 14 20 */ 15 21 package com.drew.imaging.jpeg; 22 23 import com.drew.lang.annotations.NotNull; 24 import com.drew.lang.annotations.Nullable; 16 25 17 26 import java.io.*; … … 22 31 /** 23 32 * Holds a collection of Jpeg data segments. This need not necessarily be all segments 24 * within the Jpeg. For example, it may be convenient to port aboutonly the non-image33 * within the Jpeg. For example, it may be convenient to store only the non-image 25 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 26 40 */ 27 41 public class JpegSegmentData implements Serializable 28 42 { 29 static final long serialVersionUID = 7110175216435025451L; 43 private static final long serialVersionUID = 7110175216435025451L; 30 44 31 45 /** A map of byte[], keyed by the segment marker */ 32 private final HashMap _segmentDataMap; 33 34 public JpegSegmentData() 35 { 36 _segmentDataMap = new HashMap(10); 37 } 38 39 public void addSegment(byte segmentMarker, byte[] segmentBytes) 40 { 41 List segmentList = getOrCreateSegmentList(segmentMarker); 46 @NotNull 47 private final HashMap<Byte, List<byte[]>> _segmentDataMap = new HashMap<Byte, List<byte[]>>(10); 48 49 /** 50 * 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); 42 58 segmentList.add(segmentBytes); 43 59 } 44 60 61 /** 62 * Gets the first Jpeg segment data for the specified marker. 63 * @param segmentMarker the byte identifier for the desired segment 64 * @return a byte[] containing segment data or null if no data exists for that segment 65 */ 66 @Nullable 45 67 public byte[] getSegment(byte segmentMarker) 46 68 { … … 48 70 } 49 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 50 80 public byte[] getSegment(byte segmentMarker, int occurrence) 51 81 { 52 final List segmentList = getSegmentList(segmentMarker); 82 final List<byte[]> segmentList = getSegmentList(segmentMarker); 53 83 54 84 if (segmentList==null || segmentList.size()<=occurrence) 55 85 return null; 56 86 else 57 return (byte[]) segmentList.get(occurrence); 58 } 59 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) 111 { 112 List<byte[]> segmentList; 113 if (_segmentDataMap.containsKey(segmentMarker)) { 114 segmentList = _segmentDataMap.get(segmentMarker); 115 } else { 116 segmentList = new ArrayList<byte[]>(); 117 _segmentDataMap.put(segmentMarker, segmentList); 118 } 119 return segmentList; 120 } 121 122 /** 123 * Returns the count of segment data byte arrays stored for a given segment marker. 124 * @param segmentMarker identifies the required segment 125 * @return the segment count (zero if no segments exist). 126 */ 60 127 public int getSegmentCount(byte segmentMarker) 61 128 { 62 final List segmentList = getSegmentList(segmentMarker); 63 if (segmentList==null) 64 return 0; 65 else 66 return segmentList.size(); 67 } 68 129 final List<byte[]> segmentList = getSegmentList(segmentMarker); 130 return segmentList == null ? 0 : segmentList.size(); 131 } 132 133 /** 134 * 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" }) 69 140 public void removeSegmentOccurrence(byte segmentMarker, int occurrence) 70 141 { 71 final List segmentList = (List)_segmentDataMap.get(new Byte(segmentMarker));142 final List<byte[]> segmentList = _segmentDataMap.get(Byte.valueOf(segmentMarker)); 72 143 segmentList.remove(occurrence); 73 144 } 74 145 146 /** 147 * Removes all segments from the collection having the specified marker. 148 * @param segmentMarker identifies the required segment 149 */ 75 150 public void removeSegment(byte segmentMarker) 76 151 { 77 _segmentDataMap.remove(new Byte(segmentMarker)); 78 } 79 80 private List getSegmentList(byte segmentMarker) 81 { 82 return (List)_segmentDataMap.get(new Byte(segmentMarker)); 83 } 84 85 private List getOrCreateSegmentList(byte segmentMarker) 86 { 87 List segmentList; 88 Byte key = new Byte(segmentMarker); 89 if (_segmentDataMap.containsKey(key)) { 90 segmentList = (List)_segmentDataMap.get(key); 91 } else { 92 segmentList = new ArrayList(); 93 _segmentDataMap.put(key, segmentList); 94 } 95 return segmentList; 96 } 97 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 158 * @return true if data exists, otherwise false 159 */ 98 160 public boolean containsSegment(byte segmentMarker) 99 161 { 100 return _segmentDataMap.containsKey(new Byte(segmentMarker)); 101 } 102 103 public static void ToFile(File file, JpegSegmentData segmentData) throws IOException 104 { 105 ObjectOutputStream outputStream = null; 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; 106 174 try 107 175 { 108 outputStream =new ObjectOutputStream(new FileOutputStream(file));109 outputStream.writeObject(segmentData);176 fileOutputStream = new FileOutputStream(file); 177 new ObjectOutputStream(fileOutputStream).writeObject(segmentData); 110 178 } 111 179 finally 112 180 { 113 if (outputStream!=null) 114 outputStream.close(); 115 } 116 } 117 118 public static JpegSegmentData FromFile(File file) throws IOException, ClassNotFoundException 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 119 195 { 120 196 ObjectInputStream inputStream = null; -
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.