Subversion Repositories general

Compare Revisions

Ignore whitespace Rev 1267 → Rev 1268

/contrib/metadata-extractor/trunk/src/com/drew/imaging/jpeg/JpegSegmentReader.java
0,0 → 1,283
/*
* JpegSegmentReader.java
*
* This class written by Drew Noakes, in accordance with the Jpeg specification.
*
* This is public domain software - that is, you can do whatever you want
* with it, and include it software that is licensed under the GNU or the
* BSD license, or whatever other licence you choose, including proprietary
* closed source licenses. I do ask that you leave this header in tact.
*
* If you make modifications to this code that you think would benefit the
* wider community, please send me a copy and I'll post it on my site.
*
* If you make use of this code, I'd appreciate hearing about it.
* drew@drewnoakes.com
* Latest version of this software kept at
* http://drewnoakes.com/
*
* Created by dnoakes on 04-Nov-2002 00:54:00 using IntelliJ IDEA
*/
package com.drew.imaging.jpeg;
 
import java.io.*;
 
/**
* Performs read functions of Jpeg files, returning specific file segments.
* TODO add a findAvailableSegments() method
* TODO add more segment identifiers
* TODO add a getSegmentDescription() method, returning for example 'App1 application data segment, commonly containing Exif data'
* @author Drew Noakes http://drewnoakes.com
*/
public class JpegSegmentReader
{
// Jpeg data can be sourced from either a file, byte[] or InputStream
 
/** Jpeg file */
private final File _file;
/** Jpeg data as byte array */
private final byte[] _data;
/** Jpeg data as an InputStream */
private final InputStream _stream;
 
private JpegSegmentData _segmentData;
 
/**
* Private, because this segment crashes my algorithm, and searching for
* it doesn't work (yet).
*/
private static final byte SEGMENT_SOS = (byte)0xDA;
 
/**
* Private, because one wouldn't search for it.
*/
private static final byte MARKER_EOI = (byte)0xD9;
 
/** APP0 Jpeg segment identifier -- Jfif data. */
public static final byte SEGMENT_APP0 = (byte)0xE0;
/** APP1 Jpeg segment identifier -- where Exif data is kept. */
public static final byte SEGMENT_APP1 = (byte)0xE1;
/** APP2 Jpeg segment identifier. */
public static final byte SEGMENT_APP2 = (byte)0xE2;
/** APP3 Jpeg segment identifier. */
public static final byte SEGMENT_APP3 = (byte)0xE3;
/** APP4 Jpeg segment identifier. */
public static final byte SEGMENT_APP4 = (byte)0xE4;
/** APP5 Jpeg segment identifier. */
public static final byte SEGMENT_APP5 = (byte)0xE5;
/** APP6 Jpeg segment identifier. */
public static final byte SEGMENT_APP6 = (byte)0xE6;
/** APP7 Jpeg segment identifier. */
public static final byte SEGMENT_APP7 = (byte)0xE7;
/** APP8 Jpeg segment identifier. */
public static final byte SEGMENT_APP8 = (byte)0xE8;
/** APP9 Jpeg segment identifier. */
public static final byte SEGMENT_APP9 = (byte)0xE9;
/** APPA Jpeg segment identifier -- can hold Unicode comments. */
public static final byte SEGMENT_APPA = (byte)0xEA;
/** APPB Jpeg segment identifier. */
public static final byte SEGMENT_APPB = (byte)0xEB;
/** APPC Jpeg segment identifier. */
public static final byte SEGMENT_APPC = (byte)0xEC;
/** APPD Jpeg segment identifier -- IPTC data in here. */
public static final byte SEGMENT_APPD = (byte)0xED;
/** APPE Jpeg segment identifier. */
public static final byte SEGMENT_APPE = (byte)0xEE;
/** APPF Jpeg segment identifier. */
public static final byte SEGMENT_APPF = (byte)0xEF;
/** Start Of Image segment identifier. */
public static final byte SEGMENT_SOI = (byte)0xD8;
/** Define Quantization Table segment identifier. */
public static final byte SEGMENT_DQT = (byte)0xDB;
/** Define Huffman Table segment identifier. */
public static final byte SEGMENT_DHT = (byte)0xC4;
/** Start-of-Frame Zero segment identifier. */
public static final byte SEGMENT_SOF0 = (byte)0xC0;
/** Jpeg comment segment identifier. */
public static final byte SEGMENT_COM = (byte)0xFE;
 
/**
* Creates a JpegSegmentReader for a specific file.
* @param file the Jpeg file to read segments from
*/
public JpegSegmentReader(File file) throws JpegProcessingException
{
_file = file;
_data = null;
_stream = null;
 
readSegments();
}
 
/**
* Creates a JpegSegmentReader for a byte array.
* @param fileContents the byte array containing Jpeg data
*/
public JpegSegmentReader(byte[] fileContents) throws JpegProcessingException
{
_file = null;
_data = fileContents;
_stream = null;
 
readSegments();
}
 
public JpegSegmentReader(InputStream in) throws JpegProcessingException
{
_stream = in;
_file = null;
_data = null;
readSegments();
}
 
public JpegSegmentReader(JpegSegmentData segmentData)
{
_file = null;
_data = null;
_stream = null;
 
_segmentData = segmentData;
}
 
/**
* Reads the first instance of a given Jpeg segment, returning the contents as
* a byte array.
* @param segmentMarker the byte identifier for the desired segment
* @return the byte array if found, else null
* @throws JpegProcessingException for any problems processing the Jpeg data,
* including inner IOExceptions
*/
public byte[] readSegment(byte segmentMarker) throws JpegProcessingException
{
return readSegment(segmentMarker, 0);
}
 
/**
* Reads the first instance of a given Jpeg segment, returning the contents as
* a byte array.
* @param segmentMarker the byte identifier for the desired segment
* @param occurrence the occurrence of the specified segment within the jpeg file
* @return the byte array if found, else null
*/
public byte[] readSegment(byte segmentMarker, int occurrence)
{
return _segmentData.getSegment(segmentMarker, occurrence);
}
 
public final int getSegmentCount(byte segmentMarker)
{
return _segmentData.getSegmentCount(segmentMarker);
}
 
public final JpegSegmentData getSegmentData()
{
return _segmentData;
}
 
private void readSegments() throws JpegProcessingException
{
_segmentData = new JpegSegmentData();
 
BufferedInputStream inStream = getJpegInputStream();
try {
int offset = 0;
// first two bytes should be jpeg magic number
if (!isValidJpegHeaderBytes(inStream)) {
throw new JpegProcessingException("not a jpeg file");
}
offset += 2;
do {
// next byte is 0xFF
byte segmentIdentifier = (byte)(inStream.read() & 0xFF);
if ((segmentIdentifier & 0xFF) != 0xFF) {
throw new JpegProcessingException("expected jpeg segment start identifier 0xFF at offset " + offset + ", not 0x" + Integer.toHexString(segmentIdentifier & 0xFF));
}
offset++;
// next byte is <segment-marker>
byte thisSegmentMarker = (byte)(inStream.read() & 0xFF);
offset++;
// next 2-bytes are <segment-size>: [high-byte] [low-byte]
byte[] segmentLengthBytes = new byte[2];
inStream.read(segmentLengthBytes, 0, 2);
offset += 2;
int segmentLength = ((segmentLengthBytes[0] << 8) & 0xFF00) | (segmentLengthBytes[1] & 0xFF);
// segment length includes size bytes, so subtract two
segmentLength -= 2;
if (segmentLength > inStream.available())
throw new JpegProcessingException("segment size would extend beyond file stream length");
else if (segmentLength < 0)
throw new JpegProcessingException("segment size would be less than zero");
byte[] segmentBytes = new byte[segmentLength];
inStream.read(segmentBytes, 0, segmentLength);
offset += segmentLength;
if ((thisSegmentMarker & 0xFF) == (SEGMENT_SOS & 0xFF)) {
// The 'Start-Of-Scan' segment's length doesn't include the image data, instead would
// have to search for the two bytes: 0xFF 0xD9 (EOI).
// It comes last so simply return at this point
return;
} else if ((thisSegmentMarker & 0xFF) == (MARKER_EOI & 0xFF)) {
// the 'End-Of-Image' segment -- this should never be found in this fashion
return;
} else {
_segmentData.addSegment(thisSegmentMarker, segmentBytes);
}
// didn't find the one we're looking for, loop through to the next segment
} while (true);
} catch (IOException ioe) {
//throw new JpegProcessingException("IOException processing Jpeg file", ioe);
throw new JpegProcessingException("IOException processing Jpeg file: " + ioe.getMessage(), ioe);
} finally {
try {
if (inStream != null) {
inStream.close();
}
} catch (IOException ioe) {
//throw new JpegProcessingException("IOException processing Jpeg file", ioe);
throw new JpegProcessingException("IOException processing Jpeg file: " + ioe.getMessage(), ioe);
}
}
}
 
/**
* Private helper method to create a BufferedInputStream of Jpeg data from whichever
* data source was specified upon construction of this instance.
* @return a a BufferedInputStream of Jpeg data
* @throws JpegProcessingException for any problems obtaining the stream
*/
private BufferedInputStream getJpegInputStream() throws JpegProcessingException
{
if (_stream!=null) {
if (_stream instanceof BufferedInputStream) {
return (BufferedInputStream) _stream;
} else {
return new BufferedInputStream(_stream);
}
}
InputStream inputStream;
if (_data == null) {
try {
inputStream = new FileInputStream(_file);
} catch (FileNotFoundException e) {
throw new JpegProcessingException("Jpeg file does not exist", e);
}
} else {
inputStream = new ByteArrayInputStream(_data);
}
return new BufferedInputStream(inputStream);
}
 
/**
* Helper method that validates the Jpeg file's magic number.
* @param fileStream the InputStream to read bytes from, which must be positioned
* at its start (i.e. no bytes read yet)
* @return true if the magic number is Jpeg (0xFFD8)
* @throws IOException for any problem in reading the file
*/
private boolean isValidJpegHeaderBytes(InputStream fileStream) throws IOException
{
byte[] header = new byte[2];
fileStream.read(header, 0, 2);
return (header[0] & 0xFF) == 0xFF && (header[1] & 0xFF) == 0xD8;
}
}