/** * @author Copyright (c) 2008 shichiseki.jp * @version 1.0 * @link http://code.shichiseki.jp/as3/ExifInfo/ */ package jp.shichiseki.exif { import flash.utils.ByteArray; /** * The ExifInfo class contains information about a JPEG file. It includes some IFD * which contain some tags. The ExifInfo class also contains the thumbnail image of * the JPEG file. The ExifInfo class is loosely based on Exif Version 2.2. * See http://www.exif.org/Exif2-2.PDF * for more detail of Exif specification. */ public class ExifInfo { private const SOI_MAKER:Array = [0xff, 0xd8]; private const APP1_MAKER:Array = [0xff, 0xe1]; private const EXIF_HEADER:Array = [0x45, 0x78, 0x69, 0x66, 0x00, 0x00]; private const JFIF_MAKER:Array = [0xff, 0xe0]; private var _tiffHeader:TIFFHeader; /** * An TIFF header of the JPEG APP1. * * @see jp.shichiseki.exif.TIFFHeader; */ public function get tiffHeader():TIFFHeader { return _tiffHeader; } private var _ifds:IFDSet; /** * A set of IFD that read from a JPEG file. * * @see jp.shichiseki.exif.IFDSet */ public function get ifds():IFDSet { return _ifds; } private var _thumbnailData:ByteArray; /** * A thumbnail data extracted from a JPEG file. * * @example The following example shows the thumbnail image. * * private var loadImage(jpeg:ByteArray):void { * var exif:ExifInfo = new ExifInfo(jpeg); * var loader:Loader = new Loader(); * loader.loadBytes(exif.thumbnailData); * appendChild(loader); * } * */ public function get thumbnailData():ByteArray { return _thumbnailData; } /** * Create the Exif information instance from a JPEG file. * @param stream JPEG file stored in a ByteArray object. */ public function ExifInfo(stream:ByteArray) { if (!validate(stream)) { return; } _tiffHeader = new TIFFHeader(stream); readIFDs(stream); readThumbnail(stream); } private function validate(stream:ByteArray):Boolean { var app1DataSize:uint; // JPG format check if (!hasSoiMaker(stream) ) { return false; } if(hasJFIFMaker(stream)) { // Skip the JFIF marker, if present. CWW stream.position += 16; } else { stream.position -=2; // Set position back to start of APP1 marker } if ( !hasAPP1Maker(stream)) { return false; } // handle app1 data size app1DataSize = stream.readUnsignedShort(); if (!hasExifHeader(stream)) { return false; } return true; } private function readIFDs(stream:ByteArray):void { _ifds = new IFDSet(); // primary ifd ifds._primary = new IFD(stream, Tags.getSet("primary"), tiffHeader.position); // 1st ifd (for thumbnail) var nextIFDPointer:uint = stream.readUnsignedInt(); if (nextIFDPointer != 0) { stream.position = nextIFDPointer + tiffHeader.position; ifds._thumbnail = new IFD(stream, Tags.getSet("thumbnail"), tiffHeader.position); } // Exif ifd if (ifds.primary.ExifIFDPointer) { stream.position = ifds.primary.ExifIFDPointer + tiffHeader.position; ifds._exif = new IFD(stream, Tags.getSet("exif"), tiffHeader.position); delete ifds._primary.ExifIFDPointer; } // GPS ifd if (ifds.primary.GPSInfoIFDPointer) { stream.position = ifds.primary.GPSInfoIFDPointer + tiffHeader.position; ifds._gps = new IFD(stream, Tags.getSet("gps"), tiffHeader.position); delete ifds._primary.GPSInfoIFDPointer; } // Interoperability ifd if (ifds.exif && ifds.exif.InteroperabilityIFDPointer) { stream.position = ifds.exif.InteroperabilityIFDPointer + tiffHeader.position; ifds._interoperability = new IFD(stream, Tags.getSet("interoperability"), tiffHeader.position); delete ifds._exif.InteroperabilityIFDPointer; } } private function readThumbnail(stream:ByteArray):void { // read thumbnail if (ifds.thumbnail && ifds.thumbnail.JPEGInterchangeFormat && ifds.thumbnail.JPEGInterchangeFormatLength) { _thumbnailData = new ByteArray(); stream.position = ifds.thumbnail.JPEGInterchangeFormat + tiffHeader.position; stream.readBytes(_thumbnailData, 0, ifds.thumbnail.JPEGInterchangeFormatLength); delete ifds._thumbnail.JPEGInterchangeFormat; delete ifds._thumbnail.JPEGInterchangeFormatLength; } } private function hasSoiMaker(stream:ByteArray):Boolean { return compareStreamBytes(stream, SOI_MAKER); } private function hasJFIFMaker(stream:ByteArray):Boolean { return compareStreamBytes(stream, JFIF_MAKER); } private function hasAPP1Maker(stream:ByteArray):Boolean { return compareStreamBytes(stream, APP1_MAKER); } private function hasExifHeader(stream:ByteArray):Boolean { return compareStreamBytes(stream, EXIF_HEADER); } private function compareStreamBytes(stream:ByteArray, data:Array, offset:uint=0):Boolean { var b:uint; if (offset > 0) stream.position = offset; for (var i:int = 0; i < data.length; i++) { b = stream.readUnsignedByte(); if (b != data[i]) return false; } return true; } } } .