using System;
using System.IO;
using System.Collections;
using System.Globalization;
///
/// This class was first written by Drew Noakes in Java.
///
/// 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, Drew Noakes will appreciate hearing
/// about it: drew@drewnoakes.com
///
/// Latest Java version of this software kept at
/// http://drewnoakes.com/
///
/// The C# class was made by Ferret Renaud:
/// renaud91@free.fr
/// If you find a bug in the C# code, feel free to mail me.
///
namespace com.drew.imaging.jpg
{
///
/// Will analyze a stream form an image
///
public class JpegSegmentReader
{
private FileInfo file;
private byte[] data;
private Stream stream;
private IDictionary segmentDataMap;
private readonly static byte SEGMENT_SOS = (byte) 0xDA;
private readonly static byte MARKER_EOI = (byte) 0xD9;
public readonly static byte SEGMENT_APP0 = (byte) 0xE0;
public readonly static byte SEGMENT_APP1 = (byte) 0xE1;
public readonly static byte SEGMENT_APP2 = (byte) 0xE2;
public readonly static byte SEGMENT_APP3 = (byte) 0xE3;
public readonly static byte SEGMENT_APP4 = (byte) 0xE4;
public readonly static byte SEGMENT_APP5 = (byte) 0xE5;
public readonly static byte SEGMENT_APP6 = (byte) 0xE6;
public readonly static byte SEGMENT_APP7 = (byte) 0xE7;
public readonly static byte SEGMENT_APP8 = (byte) 0xE8;
public readonly static byte SEGMENT_APP9 = (byte) 0xE9;
public readonly static byte SEGMENT_APPA = (byte) 0xEA;
public readonly static byte SEGMENT_APPB = (byte) 0xEB;
public readonly static byte SEGMENT_APPC = (byte) 0xEC;
public readonly static byte SEGMENT_APPD = (byte) 0xED;
public readonly static byte SEGMENT_APPE = (byte) 0xEE;
public readonly static byte SEGMENT_APPF = (byte) 0xEF;
public readonly static byte SEGMENT_SOI = (byte) 0xD8;
public readonly static byte SEGMENT_DQT = (byte) 0xDB;
public readonly static byte SEGMENT_DHT = (byte) 0xC4;
public readonly static byte SEGMENT_SOF0 = (byte) 0xC0;
public readonly static byte SEGMENT_COM = (byte) 0xFE;
///
/// Constructor of the object
///
/// where to read
public JpegSegmentReader(FileInfo aFile) : base()
{
this.file = aFile;
this.data = null;
this.stream = null;
this.ReadSegments();
}
///
/// Constructor of the object
///
/// where to read
public JpegSegmentReader(byte[] aFileContents)
{
this.file = null;
this.stream = null;
this.data = aFileContents;
this.ReadSegments();
}
///
/// Constructor of the object
///
/// where to read
public JpegSegmentReader(Stream aStream)
{
this.stream = aStream;
this.file = null;
this.data = null;
this.ReadSegments();
}
///
/// Reads the first instance of a given Jpeg segment, returning the contents as a byte array.
///
/// the byte identifier for the desired segment
/// the byte array if found, else null
/// for any problems processing the Jpeg data
public byte[] ReadSegment(byte segmentMarker)
{
return ReadSegment(segmentMarker, 0);
}
///
/// Reads the first instance of a given Jpeg segment, returning the contents as a byte array.
///
/// the byte identifier for the desired segment
/// the occurrence of the specified segment within the jpeg file
/// the byte array if found, else null
/// for any problems processing the Jpeg data
public byte[] ReadSegment(byte segmentMarker, int occurrence)
{
if (segmentDataMap.Contains(segmentMarker))
{
IList segmentList = (IList) segmentDataMap[segmentMarker];
if (segmentList.Count <= occurrence)
{
return null;
}
return (byte[]) segmentList[occurrence];
}
else
{
return null;
}
}
///
/// Gets the number of segment
///
/// the byte identifier for the desired segment
/// the number of segment or zero if segment does not exist
public int GetSegmentCount(byte segmentMarker)
{
IList segmentList =
(IList) segmentDataMap[segmentMarker];
if (segmentList == null)
{
return 0;
}
return segmentList.Count;
}
///
/// Reads segments
///
/// for any problems processing the Jpeg data
private void ReadSegments()
{
// Create a Hashtable using a case-insensitive code provider and a case-insensitive comparer,
// based on the Spanish culture (es-AR), where "I" is not the uppercase version of "i".
CultureInfo myCul = new CultureInfo( "es-AR" );
segmentDataMap = new Hashtable( new CaseInsensitiveHashCodeProvider( myCul ), new CaseInsensitiveComparer( myCul ));
//segmentDataMap = new Hashtable();
BufferedStream 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.ReadByte() & 0xFF);
if ((segmentIdentifier & 0xFF) != 0xFF)
{
throw new JpegProcessingException(
"expected jpeg segment start identifier 0xFF at offset "
+ offset
+ ", not 0x"
+ (segmentIdentifier & 0xFF).ToString("X"));
}
offset++;
// next byte is
byte thisSegmentMarker = (byte) (inStream.ReadByte() & 0xFF);
offset++;
// next 2-bytes are : [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.Length-inStream.Position))
{
throw new JpegProcessingException("segment size would extend beyond file stream length");
}
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
{
IList segmentList;
if (segmentDataMap.Contains(thisSegmentMarker))
{
segmentList = (IList) segmentDataMap[thisSegmentMarker];
}
else
{
segmentList = new ArrayList();
segmentDataMap.Add(thisSegmentMarker, segmentList);
}
segmentList.Add(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.Message,
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.Message,
ioe);
}
}
}
///
/// Private helper method to create a BufferedInputStream of Jpeg data
/// from whichever data source was specified upon construction of this instance.
///
/// a a BufferedStream of Jpeg data
/// for any problems processing the Jpeg data
private BufferedStream GetJpegInputStream()
{
if (stream != null)
{
if (stream is BufferedStream)
{
return (BufferedStream) stream;
}
else
{
return new BufferedStream(stream);
}
}
Stream inputStream = null;
if (data == null)
{
try
{
// Added read only access for ASPX use, thanks for Ryan Patridge
inputStream = file.Open(FileMode.Open, FileAccess.Read);
}
catch (FileNotFoundException e)
{
throw new JpegProcessingException(
"Jpeg file \""+file.FullName+"\" does not exist",
e);
}
}
else
{
inputStream = new MemoryStream(data);
}
return new BufferedStream(inputStream);
}
///
/// Helper method that validates the Jpeg file's magic number.
///
/// the InputStream to read bytes from, which must be positioned at its start (i.e. no bytes read yet)
/// true if the magic number is Jpeg (0xFFD8)
/// for any problems processing the Jpeg data
private bool IsValidJpegHeaderBytes(BufferedStream fileStream)
{
byte[] header = new byte[2];
fileStream.Read(header, 0, 2);
return ((header[0] & 0xFF) == 0xFF && (header[1] & 0xFF) == 0xD8);
}
}
}