Einzelnen Beitrag anzeigen

hathor
(Gast)

n/a Beiträge
 
#13

AW: JPG-Header lesen und speichern

  Alt 30. Jul 2015, 18:23
...........

Delphi-Quellcode:
// Program to pull the information out of various types of EXIF digital
// camera files and show it in a reasonably consistent way
//
// This module parses the very complicated exif structures.
//
// Matthias Wandel, Dec 1999 - August 2000 (most of the comments)
//
// Translated to Delphi:
// Gerry McGuire, March - April 2001 - Currently - read only
// May 2001 - add EXIF to jpeg output files
// September 2001 - read TIF files, IPTC data
// June 2003 - First (non-beta) Release
//--------------------------------------------------------------------------
// In addition to the basic information provided by Matthias, the
// following web page contains reference informtion regarding the
// exif standard: http://www.pima.net/standards/iso/tc42/wg18/WG18_POW.htm
// (the documents themselves are PDF).
//--------------------------------------------------------------------------
// 17.05.2002 MS Corrections/additions M. Schwaiger
//--------------------------------------------------------------------------
Unit dEXIF;
 
interface
 
uses sysutils,classes,math,diptc
{$IFNDEF dExifNoJpeg} 
,jpeg
{$ENDIF};
 
Const
   DexifVersion = '1.02a';
   ExifTag = 1; // default tag Types
   GpsTag = 2;
   ThumbTag = 4;
   GenericEXIF = 0;
   CustomEXIF = 1;
   AllEXIF = -1;
   crlf = #13#10;
   GenNone = 0;
   GenAll = 255;
   GenString = 2;
   GenList = 4;
   VLMin = 0;
   VLMax = 1;
 
type
   tEndInd = class
      MotorolaOrder: boolean;
      function Get16u(oset: integer): word;
      function Get32s(oset: integer): Longint;
      function Get32u(oset: integer): Longword;
      function Put32s(data: Integer): string;
      procedure WriteInt16(var buff:string;int,posn:integer);
      procedure WriteInt32(var buff:string;int,posn:longint);
      function GetDataBuff: string;
      procedure SetDataBuff(const Value: string);
      property DataBuff:string read GetDataBuff write SetDataBuff;
   private
      llData: string;
   end;
 
  TimgData = class;
  TImageInfo = class(tEndind)
  private
    function GetTagElement(TagID: integer): TTagEntry;
    procedure SetTagElement(TagID: integer; const Value: TTagEntry);
    function GetTagByName(TagName: string): TTagEntry;
    procedure SetTagByName(TagName: string; const Value: TTagEntry);
    procedure TagWriteThru16(te: ttagentry; NewVal16: word);
    procedure TagWriteThru32(te: ttagentry; NewVal32: longint);
  public
    FITagArray: array of tTagEntry;
    FITagCount: integer;
    MaxTag: integer;
    parent: timgdata;
    exifVersion : string[6];
    CameraMake: string[32];
    CameraModel: string[40];
    DateTime: string[20];
    Height,Width,HPosn,WPosn: integer;
    FlashUsed: integer;
    BuildList: integer;
    MakerNote: string;
    TiffFmt: boolean;
    Comments: string;
    CommentPosn: integer;
    CommentSize: integer;
// DateTime tag locations
    dt_oset:integer;
    dt_orig_oset:integer;
    dt_digi_oset:integer;
// Add support for thumbnail
    ThumbTrace:string;
    ThumbStart: integer;
    ThumbLength: integer;
    ThumbType: integer;
    FIThumbArray: array of tTagEntry;
    FIThumbCount: integer;
    MaxThumbTag: integer;
// Added the following elements to make the
// structure a little more code-friendly
    TraceLevel: integer;
    TraceStr: string;
    msTraceStr: string;
    msAvailable: boolean;
    msName:string;
    MakerOffset : integer;
    property ITagArray[TagID:integer]: TTagEntry
        read GetTagElement write SetTagElement; default;
    property Data[TagName:string]: TTagEntry
        read GetTagByName write SetTagByName;
 
    Constructor Create( p:timgdata; buildCode:integer =GenAll);
    procedure Assign(source:TImageInfo);
// The following functions format this structure into a string
    function toString:string; // Summerizes in a single line
    function toLongString:string;
    procedure SetExifComment(newComment: string);
// The following functions manage the date
    function GetImgDateTime: TDateTime;
    function ExtrDateTime(oset: integer): TDateTime;
    function ExifDateToDateTime(dstr: string): TDateTime;
    procedure SetDateTimeStr(oset: integer; TimeIn: TDateTime);
    procedure AdjDateTime(days, hours, mins, secs: integer);
    procedure OverwriteDateTime(InTime: tdatetime); // Contains embedded CR/LFs
    procedure ProcessHWSpecific(MakerBuff:string;
                  TagTbl:Array of TTagEntry;
                  DirStart:longint;
                  MakerOffset:Longint;
                  spOffset:integer = 0);
    Procedure ProcessThumbnail;
    Procedure AddMSTag(fname,fstr:string;fType:word);
    Procedure ProcessExifDir(DirStart, OffsetBase, ExifLength: longint;
             tagType:integer = ExifTag; prefix:string='');
    function CvtInt(buff: string): longint;
    Function FormatNumber(buffer: string; fmt: integer; fmtStr:string;
      decodeStr: string=''): string;
    Function GetNumber(buffer: string; fmt: integer): double;
    procedure removeThumbnail;
    procedure AdjExifSize(nh,nw:longint);
    Function LookupTag(SearchStr:string):integer; virtual;
    Function LookupTagVal(SearchStr:string):string; virtual;
    Function LookupTagDefn(item: string): integer;
    Function LookupTagByDesc(SearchStr: string): integer;
    function AddTagToArray(nextTag: iTag): integer;
    function AddTagToThumbArray(nextTag: iTag): integer;
    Procedure ResetIterator;
    Function IterateFoundTags(TagId:integer; var retVal:TTagEntry):boolean;
    Function GetTagByDesc(SearchStr: string): TTagEntry;
    Function HasThumbnail:boolean;
    function IterateFoundThumbTags(TagId: integer;
      var retVal: TTagEntry): boolean;
    procedure ResetThumbIterator;
    procedure Calc35Equiv;
    function EXIFArrayToXML: tstringlist;
    function LookupTagInt(SearchStr: string): integer;
    function GetRawFloat(tagName: string): double;
    function GetRawInt(tagName: string): integer;
    function LookupRatio: double;
    destructor Destroy; override;
    function WriteThruInt(tname: string; value: Integer): boolean;
    function WriteThruString(tname, value: String): boolean;
  private
    iterator:integer;
    iterThumb:integer;
  end; // TInfoData
 
  tSection = record
    data: string;
    dtype:integer;
    size:longint;
    base:longint;
  end;
  pSection = ^tSection;
 
 // TTagTableArray = array of TTagEntry;
  TGpsFormat = (gf_DD,gf_DM,gf_DMS);
 
    TImgData = class(tEndInd) // One per image object
        sections: array [1..21] of tSection;
        TiffFmt: boolean;
        BuildList: integer;
        SectionCnt : integer;
        ExifSegment: pSection;
        IPTCSegment: pSection;
        CommentSegment: pSection;
        HeaderSegment : pSection;
        Filename: string;
        FileDateTime: tDateTime;
        FileSize: longint;
        ErrStr: string;
        ExifObj: TImageInfo;
        IptcObj: TIPTCData;
        TraceLevel: integer;
        procedure reset;
        procedure SetFileInfo(fname:string);
        constructor Create(buildCode: integer = GenAll);
        function SaveExif(var jfs2:tstream):longint;
        function ReadExifInfo(fname:string):boolean;
        Procedure MakeIPTCSegment(buff:string);
        Procedure MakeCommentSegment(buff:string);
        function GetCommentStr:string;
        Function GetCommentSegment:string;
        function ProcessFile(const FileName:string):boolean;
        function ReadJpegSections (var f: tstream):boolean;
        function ReadJpegFile(const FileName:string):boolean;
        function ReadTiffSections (var f: tstream):boolean;
        function ReadTiffFile(const FileName:string):boolean;
        procedure ProcessEXIF;
        procedure CreateIPTCObj;
        function HasMetaData:boolean;
        function HasEXIF: boolean;
        function HasIPTC: boolean;
        function HasComment: boolean;
        function HasThumbnail: boolean;
        function ReadIPTCStrings(fname: string):tstringlist;
        function ExtractThumbnailBuffer: String;
{$IFNDEF dExifNoJpeg} 
        procedure WriteEXIFJpeg(j:tjpegimage;fname:string;origName:string;
                  adjSize:boolean = true); overload;
        procedure WriteEXIFJpeg(fname:string); overload;
        procedure WriteEXIFJpeg(j:tjpegimage;fname:string; adjSize:boolean = true); overload;
        function ExtractThumbnailJpeg: TJpegImage;
        function MetaDataToXML: tstringlist;
        function FillInIptc:boolean;
  public
    destructor Destroy; override;
{$ENDIF} 
 
    end; // TImgData
 
  // these function variables can be overridden to
  // alter the default formatting for various data types
  tfmtInt = function (inInt:integer):string;
  tfmtReal = function (inReal:double):string;
  tfmtFrac = function (inNum,inDen:integer):string;
 
  // These formatting functions can be used elsewhere
  function defIntFmt (inInt:integer):string;
  function defRealFmt(inReal:double):string;
  function defFracFmt(inNum,inDen:integer):string;
  function fmtRational( num,den:integer):string;
 
  function getbyte( var f : tstream) : byte;
  function DecodeField(DecodeStr, idx: string): string;
  function CvtTime(instr: string): string;
 
Var
   DexifDataSep : string = ', ';
   DexifDecodeSep : string = ',';
   DexifDelim : string = ' = ';
   DexifDecode : boolean = true;
   estimateValues : boolean = false;
   TiffReadLimit : longint = 256000;
   curTagArray : TImageInfo = nil;
   fmtInt: tfmtInt = defIntFmt;
   fmtReal: tfmtReal = defRealFmt;
   fmtFrac: tfmtFrac = defFracFmt;
 
Const
   GpsFormat = gf_DMS;
   validHeader: string = 'Exif'#0;
 
{ object declared in dIPTC unit
  TTagEntry = record
    TID: integer;        // TagTableID - EXIF use
    TType: word;        // tag type
    ICode: Word;        // iptc code
    Tag: word;          // primary key
    Name: string;        // searchable
    Desc: string;        // translatable
    Code: string;        // decode capability
    Data:String;        // display value
    Raw:string;          // unprocessed value
    Fmt:string;          // Format string
    Size: integer;      // used by ITPC module
    CallBack: StrFunct;  // formatting string
  end;
}
 
   EmptyEntry: ttagEntry = ( TID:0; TType:0; ICode:0; Tag:0; Name: '';
       Desc: ''; Code:''; Data:''; FormatS:''; Size:0);
 
//--------------------------------------------------------------------------
// JPEG markers consist of one or more= $FF bytes, followed by a marker
// code byte (which is not an FF). Here are the marker codes of interest
// in this program.
//--------------------------------------------------------------------------
 
     M_SOF0 = $C0; // Start Of Frame N
     M_SOF1 = $C1; // N indicates which compression process
     M_SOF2 = $C2; // Only SOF0-SOF2 are now in common use
     M_SOF3 = $C3;
     M_DHT = $C4; // Define Huffman Table
     M_SOF5 = $C5; // NB: codes C4 and CC are NOT SOF markers
     M_SOF6 = $C6;
     M_SOF7 = $C7;
     M_SOF9 = $C9;
     M_SOF10= $CA;
     M_SOF11= $CB;
     M_SOF13= $CD; M_DAC = $CC; // Define arithmetic coding conditioning
     M_SOF14= $CE;
     M_SOF15= $CF;
     M_SOI = $D8; // Start Of Image (beginning of datastream)
     M_EOI = $D9; // End Of Image (end of datastream)
     M_SOS = $DA; // Start Of Scan (begins compressed data)
     M_DQT = $DB; // Define Quantization table
     M_DNL = $DC; // Define number of lines
     M_DRI = $DD; // Restart interoperability definition
     M_DHP = $DE; // Define hierarchical progression
     M_EXP = $DF; // Expand reference component
     M_JFIF = $E0; // Jfif marker
     M_EXIF = $E1; // Exif marker
  M_EXIFEXT = $E2; // Exif extended marker
     // M_KODAK = $E3; // Kodak marker ???
     M_IPTC = $ED; // IPTC - Photoshop
    M_APP14 = $EE; // Photoshop data: App14
     M_COM = $FE; // Comment
 
    ProcessTable : array [0..29] of TTagEntry =
    (( Tag: M_SOF0; Desc: 'Baseline'),
     ( Tag: M_SOF1; Desc: 'Extended sequential'),
     ( Tag: M_SOF2; Desc: 'Progressive'),
     ( Tag: M_SOF3; Desc: 'Lossless'),
     ( Tag: M_DHT; Desc: 'Define Huffman table'),
     ( Tag: M_SOF5; Desc: 'Differential sequential'),
     ( Tag: M_SOF6; Desc: 'Differential progressive'),
     ( Tag: M_SOF7; Desc: 'Differential lossless'),
     ( Tag: M_SOF9; Desc: 'Extended sequential, arithmetic coding'),
     ( Tag: M_SOF10; Desc: 'Progressive, arithmetic coding'),
     ( Tag: M_SOF11; Desc: 'Lossless, arithmetic coding'),
     ( Tag: M_SOF13; Desc: 'Differential sequential, arithmetic coding'),
     ( Tag: M_DAC; Desc: 'Define arithmetic coding conditioning'),
     ( Tag: M_SOF14; Desc: 'Differential progressive, arithmetic coding'),
     ( Tag: M_SOF15; Desc: 'Differential lossless, arithmetic coding'),
     ( Tag: M_SOI; Desc: 'Start of Image'),
     ( Tag: M_EOI; Desc: 'End of Image'),
     ( Tag: M_SOS; Desc: 'Start of Scan'),
     ( Tag: M_DQT; Desc: 'Define quantization table'),
     ( Tag: M_DNL; Desc: 'Define number of lines'),
     ( Tag: M_DRI; Desc: 'Restart interoperability definition'),
     ( Tag: M_DHP; Desc: 'Define hierarchical progression'),
     ( Tag: M_EXP; Desc: 'Expand reference component'),
     ( Tag: M_JFIF; Desc: 'JPG marker'),
     ( Tag: M_EXIF; Desc: 'Exif Data'),
     ( Tag: M_EXIFEXT; Desc: 'Exif Extended Data'),
     ( Tag: M_COM; Desc: 'Comment'),
     ( Tag: M_IPTC; Desc: 'IPTC data'),
     ( Tag: M_APP14; Desc: 'Photoshop data'),
     ( Tag: 0; Desc: 'Unknown')
    );
 
   Function CvtIrrational( instr:string ):double;
   Function LookupType(idx:integer):string;
 
   Function MakePrintable(s:string):string;
 
   // Formatting callbacks
   Function GpsPosn(instr:string) :string;
   Function GenCompConfig(instr:string): string;
   Function ExposCallBack(instr: string): string;
   Function FlashCallBack(instr: string): string;
   Function ExtractComment(instr: string): string;
   Function SSpeedCallBack(instr: string): string;
   Function xpTranslate(instr: string): string;
 
const
//--------------------------------------------------------------------------
// Describes format descriptor
   BytesPerFormat: array [0..12] of integer = (0,1,1,2,4,8,1,1,2,4,8,4,8);
   NUM_FORMATS = 12;
   FMT_BYTE = 1;
   FMT_STRING = 2;
   FMT_USHORT = 3;
   FMT_ULONG = 4;
   FMT_URATIONAL = 5;
   FMT_SBYTE = 6;
   FMT_UNDEFINED = 7;
   FMT_SSHORT = 8;
   FMT_SLONG = 9;
   FMT_SRATIONAL = 10;
   FMT_SINGLE = 11;
   FMT_DOUBLE = 12;
 
var
  ExifNonThumbnailLength : integer;
  ShowTags: integer;
  ExifTrace: integer = 0;
{$IFDEF dEXIFpredeclare} 
  ImgData:timgData;
{$ENDIF} 
 
implementation
 
uses msData;
 
const
// Compression Type Constants
   JPEG_COMP_TYPE = 6;
   TIFF_COMP_TYPE = 1;
 
//-------------------------------------------------------
// Describes only tag values needed for physical access
// all others are found in tag array.
//-------------------------------------------------------
 
   TAG_EXIF_OFFSET = $8769;
   TAG_GPS_OFFSET = $8825;
   TAG_INTEROP_OFFSET = $A005;
   TAG_SUBIFD_OFFSET = $014A;
 
   TAG_MAKE = $010F;
   TAG_MODEL = $0110;
   TAG_EXIFVER = $9000;
   TAG_DATETIME = $0132;
 
(*
  TAG_EXPOSURETIME    = $829A;
  TAG_FNUMBER          = $829D;
  TAG_SHUTTERSPEED    = $9201;
  TAG_APERTURE        = $9202;
  TAG_MAXAPERTURE      = $9205;
  TAG_FOCALLENGTH      = $920A;
  TAG_FOCALLENGTH35MM  = $A405;            // added by M. Schwaiger
  TAG_SUBJECT_DISTANCE = $9206;
  TAG_LIGHT_SOURCE    = $9208;
  TAG_FOCALPLANEXRES  = $a20E;
  TAG_FOCALPLANEYRES  = $a20F;            // added by M. Schwaiger
  TAG_FOCALPLANEUNITS  = $a210;
*)
 
 
   TAG_THUMBTYPE = $0103;
   TAG_DATETIME_ORIGINAL = $9003;
   TAG_DATETIME_DIGITIZED = $9004;
   TAG_USERCOMMENT = $9286;
   TAG_FLASH = $9209;
   TAG_MAKERNOTE = $927C;
   TAG_EXIF_IMAGEWIDTH = $A002;
   TAG_EXIF_IMAGELENGTH = $A003;
   TAG_IMAGEWIDTH = $0100;
   TAG_IMAGELENGTH = $0101;
 
   GPSCnt = 30;
   ExifTagCnt = 250;
   TotalTagCnt = GPSCnt+ExifTagCnt;
 
{  Many tags added based on Php4 source...
http://lxr.php.net/source/php4/ext/exif/exif.c
}
 
var
 TagTable : array [0..ExifTagCnt] of TTagEntry =
// TagTable : array of TTagEntry =
// TagTable : TTagDefArray [0..ExifTagCnt] =
// TagTable: TTagDefArray =
 ((Tag: $001; Name:'InteroperabilityIndex'  ),
  (Tag: $002; Name:'InteroperabilityVersion'),
  (Tag: $00B; Name:'ACDComment'             ),
  (Tag: $0FE; Name:'NewSubfileType'         ),
  (Tag: $0FF; Name:'SubfileType'            ),
  (Tag: $100; Name:'ImageWidth'             ),
  (Tag: $101; Name:'ImageLength'            ),
  (Tag: $102; Name:'BitsPerSample'          ),
  (Tag: $103; Name:'Compression'            ;Code:'6:Jpeg,3:Uncompressed,1:TIFF'),
  (Tag: $106; Name:'PhotometricInterpretation'; Code:'1:Monochrome, 2:RGB, 6:YCbCr'),
  (Tag: $10A; Name:'FillOrder'              ),
  (Tag: $10D; Name:'DocumentName'           ),
  (Tag: $10E; Name:'ImageDescription'       ),
  (Tag: $10F; Name:'Make'                   ),
  (Tag: $110; Name:'Model'                  ),
  (Tag: $111; Name:'StripOffsets'           ),
  (Tag: $112; Name:'Orientation'            ; Code:'1:Normal,3:Rotated 180°,6:CounterClockwise 90°,8:Clockwise 90°'),
  (Tag: $115; Name:'SamplesPerPixel'        ),
  (Tag: $116; Name:'RowsPerStrip'           ),
  (Tag: $117; Name:'StripByteCounts'        ),
  (Tag: $118; Name:'MinSampleValue'         ),
  (Tag: $119; Name:'MaxSampleValue'         ),
  (Tag: $11A; Name:'XResolution'            ; FormatS:'%5.2f'),
  (Tag: $11B; Name:'YResolution'            ; FormatS:'%5.2f'),
  (Tag: $11C; Name:'PlanarConfiguration'    ),
  (Tag: $11D; Name:'PageName'               ),
  (Tag: $11E; Name:'XPosition'              ),
  (Tag: $11F; Name:'YPosition'              ),
  (Tag: $120; Name:'FreeOffsets'            ),
  (Tag: $121; Name:'FreeByteCounts'         ),
  (Tag: $122; Name:'GrayReponseUnit'        ),
  (Tag: $123; Name:'GrayReponseCurve'       ),
  (Tag: $124; Name:'T4Options'              ),
  (Tag: $125; Name:'T6Options'              ),
  (Tag: $128; Name:'ResolutionUnit'         ; Code:'1:None Specified,2:Inch,3:Centimeter'), // ; Code:''
  (Tag: $129; Name:'PageNumber'             ), // ; Code:''
  (Tag: $12D; Name:'TransferFunction'       ),
  (Tag: $131; Name:'Software'               ),
  (Tag: $132; Name:'DateTime'               ),
  (Tag: $13B; Name:'Artist'                 ),
  (Tag: $13C; Name:'HostComputer'           ),
  (Tag: $13D; Name:'Predictor'              ),
  (Tag: $13E; Name:'WhitePoint'             ),
  (Tag: $13F; Name:'PrimaryChromaticities'  ),
  (Tag: $140; Name:'ColorMap'               ),
  (Tag: $141; Name:'HalfToneHints'          ),
  (Tag: $142; Name:'TileWidth'              ),
  (Tag: $143; Name:'TileLength'             ),
  (Tag: $144; Name:'TileOffsets'            ),
  (Tag: $145; Name:'TileByteCounts'         ),
  (Tag: $14A; Name:'SubIFDs'                ),
  (Tag: $14C; Name:'InkSet'                 ),
  (Tag: $14D; Name:'InkNames'               ),
  (Tag: $14E; Name:'NumberOfInks'           ),
  (Tag: $150; Name:'DotRange'               ),
  (Tag: $151; Name:'TargetPrinter'          ),
  (Tag: $152; Name:'ExtraSample'            ),
  (Tag: $153; Name:'SampleFormat'           ),
  (Tag: $154; Name:'SMinSampleValue'        ),
  (Tag: $155; Name:'SMaxSampleValue'        ),
  (Tag: $156; Name:'TransferRange'          ),
  (Tag: $157; Name:'ClipPath'               ),
  (Tag: $158; Name:'XClipPathUnits'         ),
  (Tag: $159; Name:'YClipPathUnits'         ),
  (Tag: $15A; Name:'Indexed'                ),
  (Tag: $15B; Name:'JPEGTables'             ),
  (Tag: $15F; Name:'OPIProxy'               ),
  (Tag: $200; Name:'JPEGProc'               ),
  (Tag: $201; Name:'JPEGInterchangeFormat'  ),
  (Tag: $202; Name:'JPEGInterchangeFormatLength'),
  (Tag: $203; Name:'JPEGRestartInterval'    ),
  (Tag: $205; Name:'JPEGLosslessPredictors' ),
  (Tag: $206; Name:'JPEGPointTransforms'    ),
  (Tag: $207; Name:'JPEGQTables'            ),
  (Tag: $208; Name:'JPEGDCTables'           ),
  (Tag: $209; Name:'JPEGACTables'           ),
  (Tag: $211; Name:'YCbCrCoefficients'      ),
  (Tag: $212; Name:'YCbCrSubSampling'       ),
  (Tag: $213; Name:'YCbCrPositioning'       ; Code:'1:Centered,2:Co-sited'),
  (Tag: $214; Name:'ReferenceBlackWhite'    ),
  (Tag: $2BC; Name:'ExtensibleMetadataPlatform' ),
  (Tag: $301; Name:'Gamma'                     ),
  (Tag: $302; Name:'ICCProfileDescriptor'      ),
  (Tag: $303; Name:'SRGBRenderingIntent'       ),
  (Tag: $304; Name:'ImageTitle'                ),
  (Tag: $1000; Name:'RelatedImageFileFormat' ),
  (Tag: $1001; Name:'RelatedImageWidth'      ),
  (Tag: $1002; Name:'RelatedImageHeight'     ),
  (Tag: $5001; Name:'ResolutionXUnit'        ),
  (Tag: $5002; Name:'ResolutionYUnit'        ),
  (Tag: $5003; Name:'ResolutionXLengthUnit'  ),
  (Tag: $5004; Name:'ResolutionYLengthUnit'  ),
  (Tag: $5005; Name:'PrintFlags'             ),
  (Tag: $5006; Name:'PrintFlagsVersion'      ),
  (Tag: $5007; Name:'PrintFlagsCrop'         ),
  (Tag: $5008; Name:'PrintFlagsBleedWidth'   ),
  (Tag: $5009; Name:'PrintFlagsBleedWidthScale'),
  (Tag: $500A; Name:'HalftoneLPI'            ),
  (Tag: $500B; Name:'HalftoneLPIUnit'        ),
  (Tag: $500C; Name:'HalftoneDegree'         ),
  (Tag: $500D; Name:'HalftoneShape'          ),
  (Tag: $500E; Name:'HalftoneMisc'           ),
  (Tag: $500F; Name:'HalftoneScreen'         ),
  (Tag: $5010; Name:'JPEGQuality'            ),
  (Tag: $5011; Name:'GridSize'               ),
  (Tag: $5012; Name:'ThumbnailFormat'        ),
  (Tag: $5013; Name:'ThumbnailWidth'         ),
  (Tag: $5014; Name:'ThumbnailHeight'        ),
  (Tag: $5015; Name:'ThumbnailColorDepth'    ),
  (Tag: $5016; Name:'ThumbnailPlanes'        ),
  (Tag: $5017; Name:'ThumbnailRawBytes'      ),
  (Tag: $5018; Name:'ThumbnailSize'          ),
  (Tag: $5019; Name:'ThumbnailCompressedSize'),
  (Tag: $501A; Name:'ColorTransferFunction'  ),
  (Tag: $501B; Name:'ThumbnailData'          ),
  (Tag: $5020; Name:'ThumbnailImageWidth'    ),
  (Tag: $5021; Name:'ThumbnailImageHeight'   ),
  (Tag: $5022; Name:'ThumbnailBitsPerSample' ),
  (Tag: $5023; Name:'ThumbnailCompression'   ),
  (Tag: $5024; Name:'ThumbnailPhotometricInterp'),
  (Tag: $5025; Name:'ThumbnailImageDescription' ),
  (Tag: $5026; Name:'ThumbnailEquipMake'     ),
  (Tag: $5027; Name:'ThumbnailEquipModel'    ),
  (Tag: $5028; Name:'ThumbnailStripOffsets'  ),
  (Tag: $5029; Name:'ThumbnailOrientation'   ),
  (Tag: $502A; Name:'ThumbnailSamplesPerPixel'),
  (Tag: $502B; Name:'ThumbnailRowsPerStrip'  ),
  (Tag: $502C; Name:'ThumbnailStripBytesCount'),
  (Tag: $502D; Name:'ThumbnailResolutionX'   ),
  (Tag: $502E; Name:'ThumbnailResolutionY'   ),
  (Tag: $502F; Name:'ThumbnailPlanarConfig'  ),
  (Tag: $5030; Name:'ThumbnailResolutionUnit'),
  (Tag: $5031; Name:'ThumbnailTransferFunction'),
  (Tag: $5032; Name:'ThumbnailSoftwareUsed'  ),
  (Tag: $5033; Name:'ThumbnailDateTime'      ),
  (Tag: $5034; Name:'ThumbnailArtist'        ),
  (Tag: $5035; Name:'ThumbnailWhitePoint'    ),
  (Tag: $5036; Name:'ThumbnailPrimaryChromaticities'),
  (Tag: $5037; Name:'ThumbnailYCbCrCoefficients'    ),
  (Tag: $5038; Name:'ThumbnailYCbCrSubsampling'     ),
  (Tag: $5039; Name:'ThumbnailYCbCrPositioning'     ),
  (Tag: $503A; Name:'ThumbnailRefBlackWhite' ),
  (Tag: $503B; Name:'ThumbnailCopyRight'     ),
  (Tag: $5090; Name:'LuminanceTable'         ),
  (Tag: $5091; Name:'ChrominanceTable'       ),
  (Tag: $5100; Name:'FrameDelay'             ),
  (Tag: $5101; Name:'LoopCount'              ),
  (Tag: $5110; Name:'PixelUnit'              ),
  (Tag: $5111; Name:'PixelPerUnitX'          ),
  (Tag: $5112; Name:'PixelPerUnitY'          ),
  (Tag: $5113; Name:'PaletteHistogram'       ),
  (Tag: $800D; Name:'ImageID'                ),
  (Tag: $80E3; Name:'Matteing'               ), //* obsoleted by ExtraSamples */
  (Tag: $80E4; Name:'DataType'               ), //* obsoleted by SampleFormat */
  (Tag: $80E5; Name:'ImageDepth'             ),
  (Tag: $80E6; Name:'TileDepth'              ),
  (Tag: $828D; Name:'CFARepeatPatternDim'    ),
  (Tag: $828E; Name:'CFAPattern'             ),
  (Tag: $828F; Name:'BatteryLevel'           ),
  (Tag: $8298; Name:'Copyright'              ),
  (Tag: $829A; Name:'ExposureTime'             ; Formats:'%s sec'),
  (Tag: $829D; Name:'FNumber'                  ; FormatS:'F%0.1f'),
  (Tag: $83BB; Name:'IPTC/NAA'                 ; Desc:'IPTC/NAA'),
  (Tag: $84E3; Name:'IT8RasterPadding'         ),
  (Tag: $84E5; Name:'IT8ColorTable'            ),
  (Tag: $8649; Name:'ImageResourceInformation' ),
  (Tag: $8769; Name:'ExifOffset'               ),
  (Tag: $8773; Name:'InterColorProfile'        ),
  (Tag: $8822; Name:'ExposureProgram'          ; Code:
        '0:Unidentified,1:Manual,2:Normal,3:Aperture priority,'+
        '4:Shutter priority,5:Creative(slow),'+
        '6:Action(high-speed),7:Portrait mode,8:Landscape mode'),
  (Tag: $8824; Name:'SpectralSensitivity'    ),
  (Tag: $8825; Name:'GPSInfo'                ),
  (Tag: $8827; Name:'ISOSpeedRatings'        ),
  (Tag: $8828; Name:'OECF'                   ),
  (Tag: $8829; Name:'Interlace'              ),
  (Tag: $882A; Name:'TimeZoneOffset'         ),
  (Tag: $882B; Name:'SelfTimerMode'          ),
  (Tag: $9000; Name:'ExifVersion'            ),
  (Tag: $9003; Name:'DateTimeOriginal'       ),
  (Tag: $9004; Name:'DateTimeDigitized'      ),
  (Tag: $9101; Name:'ComponentsConfiguration'; Callback: GenCompConfig),
  (Tag: $9102; Name:'CompressedBitsPerPixel' ),
  (Tag: $9201; Name:'ShutterSpeedValue'      ; Callback: SSpeedCallBack),
  (Tag: $9202; Name:'ApertureValue'          ; FormatS:'F%0.1f'),
  (Tag: $9203; Name:'BrightnessValue'        ),
  (Tag: $9204; Name:'ExposureBiasValue'      ),
  (Tag: $9205; Name:'MaxApertureValue'       ; FormatS:'F%0.1f'),
  (Tag: $9206; Name:'SubjectDistance'        ),
  (Tag: $9207; Name:'MeteringMode'           ; Code:'0:Unknown,1:Average,2:Center,3:Spot,4:MultiSpot,5:MultiSegment,6:Partial'),
  (Tag: $9208; Name:'LightSource'            ; Code:'0:Unidentified,1:Daylight,2:Fluorescent,3:Tungsten,10:Flash,17:Std A,18:Std B,19:Std C'),
  (Tag: $9209; Name:'Flash'                  ; CallBack:FlashCallBack),
  (Tag: $920A; Name:'FocalLength'            ; FormatS:'%5.2f mm'),
  (Tag: $920B; Name:'FlashEnergy'             ),
  (Tag: $920C; Name:'SpatialFrequencyResponse'),
  (Tag: $920D; Name:'Noise'                   ),
  (Tag: $920E; Name:'FocalPlaneXResolution'   ), // Tag: $920E - -
  (Tag: $920F; Name:'FocalPlaneYResolution'   ),    // Tag: $920F - -
  (Tag: $9210; Name:'FocalPlaneResolutionUnit'; Code:'1:None Specified,2:Inch,3:Centimeter'), // Tag: $9210 - -
  (Tag: $9211; Name:'ImageNumber'            ),
  (Tag: $9212; Name:'SecurityClassification' ),
  (Tag: $9213; Name:'ImageHistory'           ),
  (Tag: $9214; Name:'SubjectLocation'        ),
  (Tag: $9215; Name:'ExposureIndex'          ),
  (Tag: $9216; Name:'TIFF/EPStandardID'      ),
  (Tag: $9217; Name:'SensingMethod'          ),
  (Tag: $923F; Name:'StoNits'                ),
  (Tag: $927C; Name:'MakerNote'              ),
  (Tag: $9286; Name:'UserComment'            ; Callback: ExtractComment),
  (Tag: $9290; Name:'SubSecTime'             ),
  (Tag: $9291; Name:'SubSecTimeOriginal'     ),
  (Tag: $9292; Name:'SubSecTimeDigitized'    ),
  (Tag: $953C; Name:'ImageSourceData'        ), // "Adobe Photoshop Document Data Block": 8BIM...
  (Tag: $9C9B; Name:'Title'                  ; Callback: xpTranslate), // Win XP specific, Unicode
  (Tag: $9C9C; Name:'Comments'               ; Callback: xpTranslate), // Win XP specific, Unicode
  (Tag: $9C9D; Name:'Author'                 ; Callback: xpTranslate), // Win XP specific, Unicode
  (Tag: $9C9E; Name:'Keywords'               ; Callback: xpTranslate), // Win XP specific, Unicode
  (Tag: $9C9F; Name:'Subject'                ; Callback: xpTranslate), // Win XP specific, Unicode
  (Tag: $A000; Name:'FlashPixVersion'        ),
  (Tag: $A001; Name:'ColorSpace'             ; Code:'0:sBW,1:sRGB'),
  (Tag: $A002; Name:'ExifImageWidth'         ),
  (Tag: $A003; Name:'ExifImageLength'        ),
  (Tag: $A004; Name:'RelatedSoundFile'       ),
  (Tag: $A005; Name:'InteroperabilityOffset' ),
  (Tag: $A20B; Name:'FlashEnergy'            ), // Tag: $920B in TIFF/EP
  (Tag: $A20C; Name:'SpatialFrequencyResponse'), // Tag: $920C - -
  (Tag: $A20E; Name:'FocalPlaneXResolution'   ), // Tag: $920E - -
  (Tag: $A20F; Name:'FocalPlaneYResolution'   ),    // Tag: $920F - -
  (Tag: $A210; Name:'FocalPlaneResolutionUnit'; Code:'1:None Specified,2:Inch,3:Centimeter'), // Tag: $9210 - -
  (Tag: $A211; Name:'ImageNumber'             ),
  (Tag: $A212; Name:'SecurityClassification'  ),
  (Tag: $A213; Name:'ImageHistory'            ),
  (Tag: $A214; Name:'SubjectLocation'         ),
  (Tag: $A215; Name:'ExposureIndex'           ),
  (Tag: $A216; Name:'TIFF/EPStandardID'       ; Desc:'TIFF/EPStandardID' ),
  (Tag: $A217; Name:'SensingMethod'           ; Code:'0:Unknown,1:MonochromeArea,'+
    '2:OneChipColorArea,3:TwoChipColorArea,4:ThreeChipColorArea,'+
    '5:ColorSequentialArea,6:MonochromeLinear,7:TriLinear,'+
    '8:ColorSequentialLinear'),        // Tag: $9217 - -
  (Tag: $A300; Name:'FileSource'              ; Code:'1:Unknown,3:Digital Still Camera'),
  (Tag: $A301; Name:'SceneType'               ; Code:'0:Unknown,1:Directly Photographed'),
  (Tag: $A302; Name:'CFAPattern'              ),
  (Tag: $A401; Name:'CustomRendered'          ; Code:'0:Normal process,1:Custom process'),
  (Tag: $A402; Name:'ExposureMode'            ; Code:'0:Auto,1:Manual,2:Auto bracket'),
  (Tag: $A403; Name:'WhiteBalance'            ; Code:'0:Auto,1:Manual'),
  (Tag: $A404; Name:'DigitalZoomRatio'        ),
  (Tag: $A405; Name:'FocalLengthin35mmFilm'   ; Desc:'Focal Length in 35mm Film'; FormatS:'%5.2f mm'),
  (Tag: $A406; Name:'SceneCaptureType'        ; Code:'0:Standard,1:Landscape,2:Portrait,3:Night scene'),
  (Tag: $A407; Name:'GainControl'             ; Code:'0:None,1:Low gain up,2:High gain up,3:Low gain down,4:High gain down'),
  (Tag: $A408; Name:'Contrast'                ; Code:'0:Normal,1:Soft,2:Hard'),
  (Tag: $A409; Name:'Saturation'              ; Code:'0:Normal,1:Low,2:High'),
  (Tag: $A40A; Name:'Sharpness'               ; Code:'0:Normal,1:Soft,2:Hard'),
  (Tag: $A40B; Name:'DeviceSettingDescription'),
  (Tag: $A40C; Name:'SubjectDistanceRange'    ; Code:'0:Unknown,1:Macro,2:Close view,3:Distant view'),
  (Tag: $A420; Name:'ImageUniqueID'           ; Code:'0:Close view,1:Distant view'),
  (Tag: 0; Name:'Unknown'));
 
 GPSTable : array [0..GPSCnt] of TTagEntry =
 ((Tag: $000; Name:'GPSVersionID'           ),
  (Tag: $001; Name:'GPSLatitudeRef'         ),
  (Tag: $002; Name:'GPSLatitude'            ; CallBack:GpsPosn),
  (Tag: $003; Name:'GPSLongitudeRef'        ),
  (Tag: $004; Name:'GPSLongitude'           ; CallBack:GpsPosn),
  (Tag: $005; Name:'GPSAltitudeRef'         ; Code:'0:Sealevel'),
  (Tag: $006; Name:'GPSAltitude'            ),
  (Tag: $007; Name:'GPSTimeStamp'           ; CallBack:CvtTime),
  (Tag: $008; Name:'GPSSatellites'          ),
  (Tag: $009; Name:'GPSStatus'              ),
  (Tag: $00A; Name:'GPSMeasureMode'         ),
  (Tag: $00B; Name:'GPSDOP'                 ),
  (Tag: $00C; Name:'GPSSpeedRef'            ),
  (Tag: $00D; Name:'GPSSpeed'               ),
  (Tag: $00E; Name:'GPSTrackRef'            ),
  (Tag: $00F; Name:'GPSTrack'               ),
  (Tag: $010; Name:'GPSImageDirectionRef'   ),
  (Tag: $011; Name:'GPSImageDirection'      ),
  (Tag: $012; Name:'GPSMapDatum'            ),
  (Tag: $013; Name:'GPSDestLatitudeRef'     ),
  (Tag: $014; Name:'GPSDestLatitude'        ; CallBack:GpsPosn),
  (Tag: $015; Name:'GPSDestLongitudeRef'    ),
  (Tag: $016; Name:'GPSDestLongitude'       ; CallBack:GpsPosn),
  (Tag: $017; Name:'GPSDestBearingkRef'     ),
  (Tag: $018; Name:'GPSDestBearing'         ),
  (Tag: $019; Name:'GPSDestDistanceRef'     ),
  (Tag: $01A; Name:'GPSDestDistance'        ),
  (Tag: $01B; Name:'GPSProcessingMode'      ),
  (Tag: $01C; Name:'GPSAreaInformation'     ),
  (Tag: $01D; Name:'GPSDateStamp'           ),
  (Tag: $01E; Name:'GPSDifferential'        )
  );
 
  tagInit : boolean = false;
 
Procedure FixTagTable(var tags:array of TTagEntry);
var i:integer;
begin
  for i := low(tags) to high(tags) do
  begin
    if Length(tags[i].Desc) <= 0 then
      tags[i].Desc := tags[i].Name;
  end;
end;
 
Function InsertSpaces(instr:string):string;
var i:integer;
  rslt:string;
  tc:char;
  lastUc:boolean;
begin
  LastUC := true;
  rslt := copy(instr,1,1);
  for i := 2 to length(instr) do
  begin
    tc := instr[i];
    if (tc >= 'A') and (tc <= 'Z') then
    begin
      if LastUC then
        rslt := rslt+tc
      else
        rslt := rslt+' '+tc;
      LastUc := true;
    end
    else
    begin
      lastUC := false;
      rslt := rslt+tc;
    end;
  end;
  result := rslt;
end;
 
Procedure FixTagTableParse(var tags:array of TTagEntry);
var i:integer;
begin
  for i := low(tags) to high(tags) do
  begin
    if Length(tags[i].Desc) <= 0 then
      tags[i].Desc := InsertSpaces(tags[i].Name);
  end;
end;
 
procedure LoadTagDescs(fancy:boolean = false);
begin
  if tagInit
    then exit
    else tagInit := true;
  if fancy then
  begin
    FixTagTableParse(TagTable);
    FixTagTableParse(GPSTable);
  end
  else
  begin
    FixTagTable(TagTable);
    FixTagTable(GPSTable);
  end;
end;
 
Function CvtIrrational( instr:string ):double;
var b1,b2:string;
    intMult,op:integer;
begin
  result := 0.0;
  instr := trim(instr);
  try
    op := pos(' ',instr);
    if op > 0 then
    begin
      intMult := StrToint(copy(instr,1,op-1));
      instr := copy(instr,op+1,length(instr));
    end
    else
      intMult := 0;
    op := pos('/',instr);
    b1 := copy(instr,1,op-1);
    b2 := copy(instr,op+1,length(instr));
    result := (intMult*StrToInt(b2)+StrToInt(b1)) / StrToInt(b2);
  except
  end;
end;
 
function LookupMTagID(idx:integer; ManuTable: array of TTagEntry):integer;
var i:integer;
begin
  result := -1;
  for i := 0 to high(ManuTable) do
    if ManuTable[i].Tag = idx then
    begin
      result := i;
      break;
    end;
end;
 
function LookupType(idx:integer):string;
var i:integer;
begin
  result := 'Unknown';
  for i := 0 to (sizeof(processTable) div sizeof(TTagEntry))-1 do
    if ProcessTable[i].Tag = idx then
      result := ProcessTable[i].desc;
end;
 
// These destructors provided by Keith Murray
// of byLight Technologies - Thanks!
Destructor TImageInfo.Destroy;
begin
  SetLength(fITagArray,0);
  inherited;
end;
 
Destructor TImgdata.Destroy;
begin
  if assigned(ExifObj) then
    ExifObj.free;
  if assigned(IptcObj) then
    IptcObj.free;
  inherited;
end;
 
// This function returns the index of a tag name
// in the tag buffer.
Function TImageInfo.LookupTag(SearchStr:string):integer;
var i: integer;
begin
 SearchStr := UpperCase(SearchStr);
 result := -1;
 for i := 0 to fiTagCount-1 do
   if UpperCase(fiTagArray[i].Name) = SearchStr then
   begin
     result := i;
     break;
   end;
end;
 
// This function returns the data value for a
// given tag name.
Function TImageInfo.LookupTagVal(SearchStr:string):string;
var i: integer;
begin
 SearchStr := UpperCase(SearchStr);
 result := '';
 for i := 0 to fiTagCount-1 do
   if UpperCase(fiTagArray[i].Name) = SearchStr then
   begin
     result := fiTagArray[i].Data;
     break;
   end;
end;
 
// This function returns the data value for a
// given tag name.
Function TImageInfo.LookupTagInt(SearchStr:string):integer;
var i: integer;
begin
 SearchStr := UpperCase(SearchStr);
 result := -1;
 for i := 0 to fiTagCount-1 do
   if UpperCase(fiTagArray[i].Name) = SearchStr then
   begin
     result := strtoint(fiTagArray[i].Data);
     break;
   end;
end;
 
// This function returns the index of a tag name
// in the tag buffer. It searches by the description
// which is most likely to be used as a label
Function TImageInfo.LookupTagByDesc(SearchStr:string):integer;
var i: integer;
begin
 SearchStr := UpperCase(SearchStr);
 result := -1;
 for i := 0 to FITagCount-1 do
   if UpperCase(fiTagArray[i].Desc) = SearchStr then
   begin
     result := i;
     break;
   end;
end;
 
Function TImageInfo.GetTagByDesc(SearchStr:string):TTagEntry;
var i:integer;
begin
  i := LookupTagByDesc(SearchStr);
  if i >= 0 then
    result := fiTagArray[i]
  else
    result := EmptyEntry;
end;
 
// This function returns the index of a tag definition
// for a given tag name.
function TImageInfo.LookupTagDefn(item: string): integer;
var i:integer;
begin
  result := -1;
  for i := 0 to ExifTagCnt-1 do
  begin
    if lowercase(item) = lowercase(TagTable[i].Name) then
    begin
      result := i;
      break;
    end;
  end;
end;
 
function LookupTagByID(idx:integer;TagType:integer=ExifTag):integer;
var i:integer;
begin
  result := -1;
  case tagType of
    ThumbTag,
    ExifTag: for i := 0 to ExifTagCnt do
               if TagTable[i].Tag = idx then
                 result := i;
     GpsTag: for i := 0 to GPSCnt do
               if GPSTable[i].Tag = idx then
                 result := i;
  else
  end;
end;
 
function FetchTagByID(idx:integer;TagType:integer=ExifTag):TTagEntry;
var i:integer;
begin
  result := TagTable[ExifTagCnt];
  case tagType of
    ThumbTag,
    ExifTag: for i := 0 to ExifTagCnt-1 do
               if TagTable[i].Tag = idx then
                 result := TagTable[i];
     GpsTag: for i := 0 to GPSCnt-1 do
               if GPSTable[i].Tag = idx then
                 result := GPSTable[i];
  else
  end;
end;
 
function LookupCode(idx:integer;TagType:integer=ExifTag):string; overload;
var i:integer;
begin
  result := '';
  case tagType of
    ThumbTag,
    ExifTag: for i := 0 to ExifTagCnt do
               if TagTable[i].Tag = idx then
                 result := TagTable[i].Code;
     GpsTag: for i := 0 to GPSCnt do
               if GPSTable[i].Tag = idx then
                 result := GPSTable[i].Code;
  else
  end;
end;
 
function LookupCode(idx:integer;TagTbl:array of TTagEntry):string; overload;
var i:integer;
begin
  result := '';
  for i := 0 to high(TagTbl) do
    if TagTbl[i].Tag = idx then
      result := TagTbl[i].Code;
end;
 
 
// Careful : this function's arguments are always
// evaluated which may have unintended side-effects
// (thanks to Jan Derk for pointing this out)
function siif( const cond:boolean; const s1:string; const s2:string=''):string;
begin
  if cond
    then result := s1
    else result := s2;
end;
 
procedure TImageInfo.Assign(Source: TImageInfo);
begin
  CameraMake := Source.CameraMake;
  CameraModel := Source.CameraModel;
  DateTime := Source.DateTime;
  Height := Source.Height;
  Width := Source.Width;
  FlashUsed := Source.FlashUsed;
  Comments := Source.Comments;
  MakerNote := Source.MakerNote;
  TraceStr := Source.TraceStr;
  msTraceStr := Source.msTraceStr;
  msAvailable := Source.msAvailable;
  msName := Source.msName;
end;
 
const BadVal = -1;
 
function TImageInfo.ExifDateToDateTime(dstr:string):TDateTime;
type
  TConvert= packed record
     year: Array [1..4] of char; f1:char;
     mon: Array [1..2] of Char; f2:char;
     day: Array [1..2] of Char; f3:char;
     hr: Array [1..2] of Char; f4:char;
     min: Array [1..2] of Char; f5:char;
     sec: Array [1..2] of Char;
  end;
  PConvert= ^TConvert;
begin
   try
     with PConvert( @dstr[1] )^ do
       Result := EncodeDate( StrToInt( year ),
                             StrToInt( mon ),
                             StrToInt( day ))
              + EncodeTime( StrToInt( hr ),
                             StrToInt( min ),
                             StrToInt( sec ), 0);
   except
     result := 0;
   end;
end;
 
function TImageInfo.ExtrDateTime(oset:integer):TDateTime;
var tmpStr:string;
begin
  tmpStr := copy(parent.exifSegment^.data,oset,19);
  result := ExifDateToDateTime(tmpStr);
end;
 
// 2001:01:09 16:17:32
Procedure TImageInfo.SetDateTimeStr(oset:integer; TimeIn:TDateTime);
var tmp:string;
  i:integer;
begin
  tmp := FormatDateTime('yyyy:mm:dd hh:nn:ss',TimeIn);
  for i := 1 to length(tmp) do
    parent.ExifSegment^.data[oset+i-1] := tmp[i];
end;
 
function TImageInfo.GetImgDateTime:TDateTime;
var x: TDateTime;
begin
  x := 0.0;
  if dt_oset > 0 then
    x := ExtrDateTime(dt_oset)
  else if dt_orig_oset > 0 then
    x := ExtrDateTime(dt_orig_oset)
  else if dt_digi_oset > 0 then
    x := ExtrDateTime(dt_digi_oset);
  result := x;
end;
 
Procedure TImageInfo.AdjDateTime(days,hours,mins,secs:integer);
var delta:double;
    x: TDateTime;
begin
  // hrs/day min/day sec/day
  delta := days + (hours/24)+ (mins/1440) + (secs/86400);
  if dt_oset > 0 then
  begin
    x := ExtrDateTime(dt_oset);
    SetDateTimeStr(dt_oset,x+delta);
  end;
  if dt_orig_oset > 0 then
  begin
    x := ExtrDateTime(dt_orig_oset);
    SetDateTimeStr(dt_orig_oset,x+delta);
  end;
  if dt_digi_oset > 0 then
  begin
    x := ExtrDateTime(dt_digi_oset);
    SetDateTimeStr(dt_digi_oset,x+delta);
  end;
end;
 
Procedure TImageInfo.OverwriteDateTime(InTime:tdatetime);
begin
  if dt_oset > 0 then
    SetDateTimeStr(dt_oset,InTime);
  if dt_orig_oset > 0 then
    SetDateTimeStr(dt_orig_oset,InTime);
  if dt_digi_oset > 0 then
    SetDateTimeStr(dt_digi_oset,InTime);
end;
 
Function CvtTime(instr:string) :string;
var i,sl:integer;
    tb:string;
    tHours,tMin,tSec:double;
begin
   sl := length(DexifDataSep);
   result := instr; // if error return input string
   i := pos(DexifDataSep,instr);
   tb := copy(instr,1,i-1); // get first irrational number
   tHours := CvtIrrational(tb); // bottom of lens speed range
   instr := copy(instr,i+sl-1,64);
   i := pos(DexifDataSep,instr);
   tb := copy(instr,1,i-1); // get second irrational number
   tMin := CvtIrrational(tb); // minimum focal length
   instr := copy(instr,i+1,64);
   tSec := CvtIrrational(instr); // maximum focal length
   // Ok we'll send the result back as Degrees with
   // Decimal Minutes. Alternatively send back as Degree
   // Minutes, Seconds or Decimal Degrees.
   result := format('%0.0f:%0.0f:%0.0f', [tHours,tMin,tSec]);
end;
 
 
Function GenCompConfig(instr:string) :string;
var i,ti:integer;
    ts:string;
begin
  ts := '';
  for i := 1+1 to 4+1 do // skip first char...
  begin
    ti := integer(instr[i]);
    case ti of
      1: ts := ts+'Y';
      2: ts := ts+'Cb';
      3: ts := ts+'Cr';
      4: ts := ts+'R';
      5: ts := ts+'G';
      6: ts := ts+'B';
    else
    end;
  end;
  result := ts;
end;
 
Function GpsPosn(instr:string) :string;
var i,sl:integer;
    tb:string;
    gDegree,gMin,gSec:double;
begin
   sl := length(DexifDataSep);
   result := instr; // if error return input string
   i := pos(DexifDataSep,instr);
   tb := copy(instr,1,i-1); // get first irrational number
   gDegree := CvtIrrational(tb); // degrees
   instr := copy(instr,i+sl-1,64);
   i := pos(DexifDataSep,instr);
   tb := copy(instr,1,i-1); // get second irrational number
   gMin := CvtIrrational(tb); // minutes
   instr := copy(instr,i+sl-1,64);
   gSec := CvtIrrational(instr); // seconds
   if gSec = 0 then // camera encoded as decimal minutes
   begin
     gSec := ((gMin-trunc(gMin))*100); // seconds as a fraction of degrees
     gSec := gSec * 0.6; // convert to seconds
     gMin := trunc(gMin); // minutes is whole portion
   end;
   // Ok we'll send the result back as Degrees with
   // Decimal Minutes. Alternatively send back as Degree
   // Minutes, Seconds or Decimal Degrees.
   case GpsFormat of
      gf_DD: result :=
          format('%1.4f Decimal Degrees',[gDegree + ((gMin + (gSec/60))/60)]);
      gf_DM: result :=
          format('%0.0f Degrees %1.2f Minutes',[gDegree, gMin + (gsec/60)]);
     gf_DMS: result :=
          format('%0.0f Degrees %0.0f Minutes %0.0f Seconds', [gDegree,gMin,gSec]);
   else
   end;
end;
 
function DecodeField(DecodeStr,idx:string):string;
var stPos:integer;
    ts:string;
begin
   result := '';
   idx := DexifDecodeSep+trim(idx)+':'; // ease parsing
   decodeStr := DexifDecodeSep+decodeStr+DexifDecodeSep;
   stPos := pos(idx,DecodeStr);
   if stPos > 0 then
   begin
     ts := copy(DecodeStr,stPos+length(idx),length(decodeStr));
     result := copy(ts,1,pos(DexifDecodeSep,ts)-1);
   end
end;
 
function TImageInfo.AddTagToArray(nextTag:iTag):integer;
begin
  if nextTag.tag <> 0 then // Empty fields are masked out
  begin
    if fITagCount >= MaxTag-1 then
    begin
      inc(MaxTag,TagArrayGrowth);
      SetLength(fITagArray,MaxTag);
    end;
    fITagArray[fITagCount] := nextTag;
    inc(fITagCount);
  end;
  result := fITagCount-1;
end;
 
function TImageInfo.AddTagToThumbArray(nextTag: iTag): integer;
begin
  if nextTag.tag <> 0 then // Empty fields are masked out
  begin
    if fIThumbCount >= MaxThumbTag-1 then
    begin
      inc(MaxThumbTag,TagArrayGrowth);
      SetLength(fIThumbArray,MaxThumbTag);
    end;
    fIThumbArray[fIThumbCount] := nextTag;
    inc(fIThumbCount);
  end;
  result := fIThumbCount-1;
end;
 
function TImageInfo.CvtInt(buff:string):longint;
var i:integer;
    r:Int64;
begin
  r := 0;
  try
  if MotorolaOrder then
    for i := 1 to length(buff) do
      r := r*256+ord(buff[i])
  else
    for i := length(buff) downto 1 do
      r := r*256+ord(buff[i]);
  except
  end;
  result := longint(r);
end;
 
function TImageInfo.FormatNumber(buffer:string;fmt:integer;
    fmtStr:string;decodeStr:string=''):string;
var buff2,os:string;
    i,vlen:integer;
    tmp,tmp2:longint;
    dv:double;
begin
  os := '';
  vlen := BytesPerFormat[fmt];
  if vlen = 0 then
  begin
    result := '0';
    exit;
  end;
  for i := 0 to min((length(buffer) div vlen),8)-1 do
  begin
    if os <> 'then
      os := os+DexifDataSep; // Used for data display
    buff2 := copy(buffer,(i*vlen)+1,vlen);
    case fmt of
      FMT_SBYTE,
      FMT_BYTE,
      FMT_USHORT,
      FMT_ULONG,
      FMT_SSHORT,
      FMT_SLONG: begin
                       tmp := CvtInt(buff2);
                       if (decodeStr = '') or not DexifDecode then
                         os := os + defIntFmt(tmp) // IntToStr(tmp)
                       else
                         os := os + DecodeField(decodeStr,IntToStr(tmp)); //+
// ' ('+IntToStr(tmp)+')';
                     end;
      FMT_URATIONAL,
      FMT_SRATIONAL: begin
                       tmp := CvtInt(copy(buff2,1,4));
                       tmp2 := CvtInt(copy(buff2,5,4));
                       os := os + defFracFmt(tmp,tmp2); //format('%d/%d',[tmp,tmp2]);
                       if (decodeStr <> '') or not DexifDecode then
                         os := os + DecodeField(decodeStr,os); // +' ('+os+')';
                     end;
      FMT_SINGLE,
      FMT_DOUBLE: begin // not used anyway
                       os := os+ '-9999.99'; // not sure how to
                     end; // interpret endian issues
    else
      os := os + '?';
    end;
  end;
  if fmtStr <> 'then
  begin
    if pos('%s', fmtStr) > 0 then
    begin
      os := format(fmtStr,[os]);
    end
    else
    begin
      dv := GetNumber(buffer,fmt);
      os := format(fmtStr,[dv]);
    end;
  end;
  result := os;
end;
 
function TImageInfo.GetNumber(buffer:string;fmt:integer):double;
var os:double;
    tmp:longint;
    dbl:double absolute tmp;
    tmp2:longint;
begin
  try
    case fmt of
      FMT_SBYTE,
      FMT_BYTE,
      FMT_USHORT,
      FMT_ULONG,
      FMT_SSHORT,
      FMT_SLONG: os := CvtInt(buffer);
      FMT_URATIONAL,
      FMT_SRATIONAL: begin
                       tmp := CvtInt(copy(buffer,1,4));
                       tmp2 := CvtInt(copy(buffer,5,4));
                       os := tmp / tmp2;
                     end;
      FMT_SINGLE: os := dbl;
      FMT_DOUBLE: os := dbl;
    else
      os := 0;
    end;
  except
    os := 0;
  end;
  result := os;
end;
 
function MakePrintable(s:string):string;
var r:string;
  i:integer;
begin
  for i := 1 to min(length(s),50) do
    if not (ord(s[i]) in [32..255]) then
      r := r+'.
    else
      r := r+s[i];
  result := r;
end;
 
function MakeHex(s:string):string;
var r:string;
  i:integer;
begin
  for i := 1 to min(length(s),16) do
    r := r+IntToHex(ord(s[i]),2)+' ';
  if length(s) > 16 then
    r := r+'...';
  result := r;
end;
 
//--------------------------------------------------------------------------
// Process one of the nested EXIF directories.
//--------------------------------------------------------------------------
procedure TImageInfo.ProcessExifDir(DirStart, OffsetBase, ExifLength: longint;
  tagType:integer = ExifTag; prefix:string='');
var ByteCount:integer;
  tag,TFormat,components:integer;
  de,DirEntry,OffsetVal,NumDirEntries,ValuePtr,subDirStart:Longint;
  RawStr,Fstr,transStr:string;
  msInfo: tmsInfo;
  lookupE, newE: TTagEntry;
  tmpTR:string;
begin
  NumDirEntries := Get16u(DirStart);
  if (ExifTrace > 0) then
    TraceStr := TraceStr +#13#10+
      format('Directory: Start, entries = %d, %d',[DirStart, NumDirEntries]);
  if (DirStart+2+(NumDirEntries*12)) > (DirStart+OffsetBase+ExifLength) then
  begin
    Parent.ErrStr := 'Illegally sized directory';
    exit;
  end;
//Parent.ErrStr:=
//format('%d,%d,%d,%d+%s',[DirStart,NumDirEntries,OffsetBase,ExifLength,
//parent.errstr]);
// Uncomment to trace directory structure
  if (tagType = ExifTag) and (ThumbStart = 0) and not TiffFmt then
  begin
    DirEntry := DirStart+2+12*NumDirEntries;
    ThumbStart := Get32u(DirEntry);
    ThumbLength := OffsetBase+ExifLength-ThumbStart;
  end;
 
  for de := 0 to NumDirEntries-1 do
  begin
    DirEntry := DirStart+2+12*de;
    Tag := Get16u(DirEntry);
    TFormat := Get16u(DirEntry+2);
    Components := Get32u(DirEntry+4);
    ByteCount := Components * BytesPerFormat[TFormat];
    if ByteCount = 0 then
      continue;
    If ByteCount > 4 then
    begin
      OffsetVal := Get32u(DirEntry+8);
      ValuePtr := OffsetBase+OffsetVal;
    end
    else
      ValuePtr := DirEntry+8;
 
    RawStr := copy(parent.EXIFsegment^.data,ValuePtr,ByteCount);
    fstr := '';
 
    if BuildList in [GenString,GenAll] then
    begin
      LookUpE := FetchTagByID(tag,tagType);
 
      with LookUpE do
      begin
        case tformat of
          FMT_UNDEFINED: fStr := '"'+StrBefore(RawStr,#0)+'"';
             FMT_STRING: fStr := copy(parent.EXIFsegment^.data,
               ValuePtr,ByteCount-1);
        else
          fStr := FormatNumber(RawStr, TFormat, FormatS, Code);
        end;
        if (Tag > 0) and assigned(callback) and DexifDecode then
          fstr := Callback(fStr)
        else
          fstr := MakePrintable(fstr);
        transStr := Desc;
      end;
 
     Case tag of
       TAG_USERCOMMENT:
         begin
           // here we strip off comment header
           Comments := trim(copy(RawStr,9,ByteCount-9));
           fStr := Comments; // old one is erroneous
 
           CommentPosn := ValuePtr;
           CommentSize := ByteCount-9;
         end;
       else
       end;
 
     tmpTR := #13#10+
          siif(ExifTrace > 0,'tag[$'+inttohex(tag,4)+']: ','')+
          transStr+DexifDelim+fstr+
          siif(ExifTrace > 0,' [size: '+inttostr(ByteCount)+']','')+
          siif(ExifTrace > 0,' [start: '+inttostr(ValuePtr)+']','');
 
      if tagType = ThumbTag then
          Thumbtrace := ThumbTrace + tmpTR
      else
          TraceStr := TraceStr + tmpTR;
      end;
// Additional processing done here:
 
     Case tag of
       TAG_SUBIFD_OFFSET,
       TAG_EXIF_OFFSET,
       TAG_INTEROP_OFFSET:
         begin
           try
             SubdirStart := OffsetBase + LongInt(Get32u(ValuePtr));
             ProcessExifDir(SubdirStart, OffsetBase, ExifLength, ExifTag);
           except
           end;
         end;
       TAG_GPS_OFFSET:
         begin
           try
             SubdirStart := OffsetBase + LongInt(Get32u(ValuePtr));
             ProcessExifDir(SubdirStart, OffsetBase, ExifLength, GpsTag);
           except
           end;
         end;
       TAG_MAKE: CameraMake := fstr;
       TAG_MODEL: CameraModel := fstr;
       TAG_EXIFVER: ExifVersion := rawstr;
       TAG_DATETIME:
         begin
           dt_oset := ValuePtr;
           DateTime := fstr;
         end;
       TAG_DATETIME_ORIGINAL:
         begin
           dt_orig_oset := ValuePtr;
           DateTime := fstr;
         end;
       TAG_DATETIME_DIGITIZED:
         begin
           dt_digi_oset := ValuePtr;
         end;
       TAG_MAKERNOTE: begin
            MakerNote := RawStr;
            MakerOffset := ValuePtr;
            Msinfo := tmsinfo.create(TiffFmt,self);
            msAvailable := msInfo.ReadMSData(self);
            FreeAndNil(msinfo);
          end;
       TAG_FLASH:
                FlashUsed := round(getNumber(RawStr, TFormat));
       TAG_IMAGELENGTH,
       TAG_EXIF_IMAGELENGTH:
           begin
             HPosn := DirEntry+8;
             Height := round(getNumber(RawStr, TFormat));
           end;
       TAG_IMAGEWIDTH,
       TAG_EXIF_IMAGEWIDTH:
           begin
             WPosn := DirEntry+8;
             Width := round(getNumber(RawStr, TFormat));
           end;
       TAG_THUMBTYPE:
           if tagType = ThumbTag then
             ThumbType := round(getNumber(RawStr, TFormat));
     else
       // no special processing
     end;
 
      if BuildList in [GenList,GenAll] then
      begin
        try
          NewE := LookupE;
          NewE.Data := fstr;
          NewE.Raw := RawStr;
          NewE.Size := length(RawStr);
          NewE.PRaw := ValuePtr;
          NewE.TType := tFormat;
          if tagType = ThumbTag then
            AddTagToThumbArray(NewE)
          else
            AddTagToArray(NewE);
        except
          // if we're here: unknown tag.
          // item is recorded in trace string
        end;
    end;
 
  end;
end;
 
Procedure TImageInfo.AddMSTag(fname,fstr:string;fType:word);
var {lookupE,} newE: TTagEntry;
begin
  if BuildList in [GenList,GenAll] then
  begin
    try
      newE.Name := fname;
      newE.Desc := fname;
      NewE.Data := fstr;
      NewE.Raw := fStr;
      NewE.Size := length(fStr);
      NewE.PRaw := 0;
      NewE.TType := fType;
      NewE.TID := 1; // MsSpecific
      AddTagToArray(NewE);
    except
      // if we're here: unknown tag.
      // item is recorded in trace string
    end;
  end;
end;
 
Procedure TImageInfo.ProcessThumbnail;
var start:integer;
begin
  start := ThumbStart+9;
  ProcessExifDir(start, 9, ThumbLength-12,ThumbTag,'Thumbnail');
end;
 
Procedure TImageInfo.removeThumbnail;
var newSize:integer;
begin
  newSize := ThumbStart-6;
  with parent do
  begin
    SetLength(ExifSegment^.data,newSize);
    ExifSegment^.size := newSize;
  // size calculations should really be moved to save routine
    ExifSegment^.data[1] := char(newSize div 256);
    ExifSegment^.data[2] := char(newSize mod 256);
  end;
end;
 
procedure TImageInfo.ProcessHWSpecific(MakerBuff:string;
                TagTbl:Array of TTagEntry;
                DirStart:longint;
                MakerOffset:Longint;
                spOffset:integer = 0);
var NumDirEntries:integer;
    de,ByteCount,TagID:integer;
    DirEntry,tag,TFormat,components:integer;
    OffsetVal,ValuePtr:Longint;
    RawStr,Fstr,Fstr2,TagStr,ds:string;
    OffsetBase: longint;
    NewE:TTagEntry;
begin
  DirStart := DirStart+1;
  OffsetBase := DirStart-MakerOffset+1;
  SetDataBuff(MakerBuff);
  try
    NumDirEntries := Get16u(DirStart);
    for de := 0 to NumDirEntries-1 do
    begin
      DirEntry := DirStart+2+12*de;
      Tag := Get16u(DirEntry);
      TFormat := Get16u(DirEntry+2);
      Components := Get32u(DirEntry+4);
      ByteCount := Components * BytesPerFormat[TFormat];
      OffsetVal := 0;
      If ByteCount > 4 then
      begin
        OffsetVal := Get32u(DirEntry+8);
        ValuePtr := OffsetBase+OffsetVal;
      end
      else
        ValuePtr := DirEntry+8;
 
      // Adjustment needed by Olympus Cameras
      if ValuePtr+ByteCount > length(MakerBuff) then
        RawStr := copy(parent.DataBuff,OffsetVal+spOffset,ByteCount)
      else
        RawStr := copy(MakerBuff,ValuePtr,ByteCount);
 
      TagID := LookupMTagID(tag,TagTbl);
      if TagID < 0
        then TagStr := 'Unknown
        else TagStr := TagTbl[TagID].Desc;
      fstr := '';
      if UpperCase(TagStr) = 'SKIPthen
        continue;
 
    if BuildList in [GenList,GenAll] then
    begin
       case tformat of
           FMT_STRING: fStr := '"'+strbefore(RawStr,#0)+'"';
        FMT_UNDEFINED: fStr := '"'+RawStr+'"';
// FMT_STRING: fStr := '"'+copy(MakerBuff,ValuePtr,ByteCount-1)+'"';
      else
        try
          ds := siif(dEXIFdecode, LookupCode(tag,TagTbl),'');
          if TagID < 0
            then fStr := FormatNumber(RawStr, TFormat, '', '')
            else fStr := FormatNumber(RawStr, TFormat, TagTbl[TagID].FormatS, ds);
        except
          fStr := '"'+RawStr+'"';
        end;
      end;
 
      rawDefered := false;
      if (TagID > 0) and assigned(TagTbl[TagID].CallBack) and DexifDecode then
        fstr2 := TagTbl[TagID].CallBack(fstr)
      else
        fstr2 := MakePrintable(fstr);
 
      if (ExifTrace > 0) then
      begin
        if not rawDefered then
          msTraceStr := msTraceStr +#13#10+
            'tag[$'+inttohex(tag,4)+']: '+
           TagStr+DexifDelim+fstr2+
           ' [size: '+inttostr(ByteCount)+']'+
           ' [raw: '+MakeHex(RawStr)+']'+
           ' [start: '+inttostr(ValuePtr)+']
        else
          msTraceStr := msTraceStr +#13#10+
            'tag[$'+inttohex(tag,4)+']: '+
           TagStr+DexifDelim+
           ' [size: '+inttostr(ByteCount)+']'+
           ' [raw: '+MakeHex(RawStr)+']'+
           ' [start: '+inttostr(ValuePtr)+']'+
           fstr2;
      end
      else
      begin
        if not rawDefered then
          msTraceStr := msTraceStr +#13#10+
            TagStr+DexifDelim+fstr2
        else
          msTraceStr := msTraceStr+
            fstr2+ // has cr/lf as first element
            #13#10+TagStr+DexifDelim+fstr;
      end;
      (*
      msTraceStr := msTraceStr +#13#10+
        siif(ExifTrace > 0,'tag[$'+inttohex(tag,4)+']: ','')+
        TagStr+DexifDelim+fstr+
        siif(ExifTrace > 0,' [size: '+inttostr(ByteCount)+']','')+
        siif(ExifTrace > 0,' [raw: '+MakeHex(RawStr)+']','')+
        siif(ExifTrace > 0,' [start: '+inttostr(ValuePtr)+']','');
      *)
 
    end;
 
      if (BuildList in [GenList,GenAll]) and (TagID > 0) then
      begin
        try
          NewE := TagTbl[TagID];
          NewE.Data := fstr;
          NewE.Raw := RawStr;
          NewE.TType := tFormat;
          NewE.TID := 1; // MsSpecific
          AddTagToArray(NewE);
        except
          // if we're here: unknown tag.
          // item is recorded in trace string
        end;
      end;
 
     end;
 
  except
     on e:exception do
       Parent.ErrStr := 'Error Detected = '+e.message;
   end;
 
   SetDataBuff(parent.DataBuff);
end;
 
 
Function ExtractComment(instr: string): string;
begin
// CommentHeader := copy(instr,1,8); // fixed length string
  result := copy(instr,9,maxint);
end;
 
Function FlashCallBack(instr: string): string;
var tmp: integer;
    tmpS: string;
begin
  tmp := strToInt(instr);
  tmps := siif(tmp and 1 = 1,'On','Off'); // bit0
  tmps := tmps+siif(tmp and 6 = 2,', UNKNOWN'); // bit1
  tmps := tmps+siif(tmp and 6 = 4,', no strobe return'); // bit2
  tmps := tmps+siif(tmp and 6 = 6,', strobe return'); // bit1+2
  tmps := tmps+siif(tmp and 24 = 8,', forced'); // bit3
  tmps := tmps+siif(tmp and 24 = 16,', surpressed'); // bit4
  tmps := tmps+siif(tmp and 24 = 24,', auto mode'); // bit3+4
  tmps := tmps+siif(tmp and 32 = 32,', no flash function'); // bit5
  tmps := tmps+siif(tmp and 64 = 64,', red-eye reduction'); // bit6
  result := tmps;
end;
 
function ExposCallBack(instr: string):string;
var expoTime:double;
begin
  expoTime := strToFloat(instr);
  result := Format('%4.4f sec',[expoTime])+
    siif(ExpoTime <= 0.5,
      format(' (1/%d)',[round(1/ExpoTime)]),'');
// corrected by M. Schwaiger - adding ".5" is senseless when using "round"!
end;
 
function SSpeedCallBack(instr: string):string;
var expoTime:double;
begin
  expoTime := CvtIrrational(instr);
  expoTime := (1/exp(expoTime*ln(2)));
  result := Format('%4.4f sec',[expoTime])+
    siif(ExpoTime <= 0.5,
      format(' (1/%d)',[round(1/ExpoTime)]),'');
end;
 
function xpTranslate(instr: string):string;
var i:integer;
    ts:string;
    cv:char;
begin
  ts := '';
  for i := 1 to StrCount(instr,',') do
    if odd(i) then
    begin
       cv := chr(strtoint(StrNth(instr,',',i)));
       if cv <> #0 then
         ts := ts+cv;
    end;
  result := ts;
end;
 
function TImageInfo.toLongString: string;
var tmpStr:string;
begin
  if parent.ExifSegment = nil then
    result := '
  else if Parent.errstr <> '<none>then
    result := 'File Name: '  + ExtractFileName(parent.Filename) + crlf +
            'Exif Error: '+Parent.errstr
  else
  begin
    result := 'File Name: '   + ExtractFileName(parent.Filename) + crlf +
            'File Size: '   + IntToStr(parent.FileSize div 1024)+ 'k'   + crlf +
            'File Date: '   + dateToStr(parent.FileDateTime) + crlf +
 
            'Photo Date: '  + DateTime + crlf +
            'Make (Model): '  + CameraMake + ' ('+CameraModel+')' + crlf +
            'Dimensions: '  + IntToStr(Width) + ' x '+
                              IntToStr(Height);
    if BuildList in [GenString,GenAll] then
    begin
 
      tmpStr := LookupTagVal('ExposureTime');
      if tmpStr <> 'then
        result := result+crlf+'Exposure Time: '+tmpStr
      else
      begin
        tmpStr := LookupTagVal('ShutterSpeedValue');
        if tmpStr <> 'then
          result := result+crlf+'Exposure Time: '+tmpStr
      end;
 
      tmpStr := LookupTagVal('FocalLength');
      if tmpStr <> 'then
        result := result+crlf+'Focal Length: '+tmpStr;
 
      tmpStr := LookupTagVal('FocalLengthin35mm');
      if tmpStr <> 'then
        result := result+crlf+'Focal Length (35mm): '+tmpStr;
 
      tmpStr := LookupTagVal('FNumber');
      if tmpStr <> 'then
        result := result+crlf+'FNumber: '+tmpStr;
 
      tmpStr := LookupTagVal('ISOSpeedRatings');
      if tmpStr <> 'then
        result := result+crlf+'ISO: '+tmpStr;
    end;
    result := result + crlf +
      'Flash: ' + siif(odd(FlashUsed),'Yes','No');
  end;
end;
 
function TImageInfo.toString: string;
begin
  if parent.ExifSegment = nil then
    result := '
  else if Parent.errstr <> '<none>then
    result := ExtractFileName(parent.Filename) + ' Exif Error: '+Parent.errstr
  else
    result := ExtractFileName(parent.Filename) + ' ' +
            IntToStr(parent.FileSize div 1024)+'k '+
            Copy(DateTime,1,10) + ' '+
            IntToStr(Width)+'w '+IntToStr(Height)+'h
            +siif(odd(FlashUsed),' Flash','');
end;
 
(*************************************************
The following methods write data back into the
EXIF buffer.
*************************************************)
 
 
procedure TImageInfo.SetExifComment( newComment:string);
begin
  WriteThruString('UserComment','ASCII'#0#0#0+newComment);
end;
 
procedure TImageInfo.AdjExifSize(nh,nw:longint);
begin
  if (Height <=0) or (Width <=0) then
    exit;
  if (nw <> Width) or (nh <> Height) then
  begin
    parent.WriteInt32(parent.ExifSegment^.data,nh,hPosn);
    parent.WriteInt32(parent.ExifSegment^.data,nw,wPosn);
  end;
end;
 
procedure TImageInfo.TagWriteThru16(te:ttagentry;NewVal16:word);
begin
  parent.WriteInt16(parent.ExifSegment^.data,newVal16,te.praw);
end;
 
procedure TImageInfo.TagWriteThru32(te:ttagentry;NewVal32:longint);
begin
  parent.WriteInt16(parent.ExifSegment^.data,newVal32,te.praw);
end;
 
function TImageInfo.WriteThruInt(tname:string;value:longint):boolean;
var te:ttagentry;
  vlen:integer;
begin
  result := false; // failure
  te := Data[tname];
  if te.Tag = 0 then
    exit;
   result := true; // success
   vlen := BytesPerFormat[te.TType];
   if vlen = 2 then
     TagWriteThru16(te,word(value))
   else
   if vlen = 4 then
     TagWriteThru32(te,value)
   else
     result := false; // don't recognize the type
end;
 
function TImageInfo.WriteThruString(tname:string;value:String):boolean;
var te:ttagentry;
  i,sPosition:integer;
begin
  result := false; // failure
  te := Data[tname];
  if te.Tag = 0 then
    exit;
  with parent.ExifSegment^ do
  begin
    sPosition := te.PRaw;
    for i := 0 to te.Size-2 do
      if i > length(value)-1 then
        data[i+sPosition] := #0
      else
        data[i+sPosition] := value[i+1];
    data[te.Size-1] := #0; // strings are null terminated
  end;
  result := true; // success
end;
 
//
// Sample call -
// ImgData.ExifObj.WriteThruInt('Orientation',3);
//
//*********************************************
 
constructor TImageInfo.Create(p: timgdata; buildCode: integer = GenAll);
begin
  inherited create;
  LoadTagDescs(True); // initialize global structures
  FITagCount := 0;
  buildList := BuildCode;
  parent := p;
end;
 
constructor TImgData.Create(buildCode: integer = GenAll);
begin
  inherited create;
  buildList := BuildCode;
  reset;
end;
 
function TImageInfo.GetTagElement(TagID: integer): TTagEntry;
begin
  result := fITagArray[TagID]
end;
 
procedure TImageInfo.SetTagElement(TagID: integer;
  const Value: TTagEntry);
begin
  fITagArray[TagID] := Value;
end;
 
function TImageInfo.GetTagByName(TagName: string): TTagEntry;
var i:integer;
begin
  i := LookupTag(TagName);
  if i >= 0 then
    result := fITagArray[i]
  else
    result := EmptyEntry;
end;
 
procedure TImageInfo.SetTagByName(TagName: string; const Value: TTagEntry);
var i:integer;
begin
  i := LookupTag(TagName);
  if i >= 0 then
    fITagArray[i] := Value
  else
  begin
    AddTagToArray(value);
  end;
end;
 
function TImageInfo.IterateFoundTags(TagId: integer;
        var retVal:TTagEntry):boolean;
begin
  FillChar(retVal,sizeof(retVal),0);
  while (iterator < FITagCount) and (FITagArray[iterator].TID <> TagId) do
    inc(iterator);
  if (iterator < FITagCount) then
  begin
    retVal := FITagArray[iterator];
    inc(iterator);
    result := true;
  end
  else
    result := false;
end;
 
procedure TImageInfo.ResetIterator;
begin
   iterator := 0;
end;
 
function TImageInfo.IterateFoundThumbTags(TagId: integer;
        var retVal:TTagEntry):boolean;
begin
  FillChar(retVal,sizeof(retVal),0);
  while (iterThumb < FIThumbCount) and (FITagArray[iterThumb].TID <> TagId) do
    inc(iterThumb);
  if (iterThumb < FIThumbCount) then
  begin
    retVal := FIThumbArray[iterThumb];
    inc(iterThumb);
    result := true;
  end
  else
    result := false;
end;
 
procedure TImageInfo.ResetThumbIterator;
begin
   iterThumb := 0;
end;
 
function TImageInfo.GetRawFloat( tagName: string ):double;
var tiq :TTagEntry;
begin
  tiq := GetTagByName( tagName );
  if tiq.Tag = 0 // EmptyEntry
    then result := 0.0
    else result := GetNumber(tiq.Raw, tiq.TType);
end;
 
function TImageInfo.GetRawInt( tagName: string ):integer;
begin
  result := round(GetRawFloat(tagName));
end;
 
// Unfortunatly if we're calling this function there isn't
// enough info in the EXIF to calculate the equivalent 35mm
// focal length and it needs to be looked up on a camera
// by camera basis. - next rev - maybe
Function TImageInfo.LookupRatio:double;
var estRatio:double;
  upMake,upModel:string;
begin
  upMake := copy(uppercase(cameramake),1,5);
  upModel := copy(uppercase(cameramodel),1,5);
  estRatio := 4.5; // ballpark for *my* camera -
  result := estRatio;
end;
 
procedure TImageInfo.Calc35Equiv;
const Diag35mm : double = 43.26661531; // sqrt(sqr(24)+sqr(36))
var tmp:integer;
  CCDWidth, CCDHeight,
    fpu, fl, fl35, ratio : double;
  NewE, LookUpE : TTagEntry;
begin
  if LookUpTag('FocalLengthin35mmFilm') >= 0 then
    exit; // no need to calculate - already have it
 
  CCDWidth := 0.0;
  CCDHeight := 0.0;
  tmp := GetRawInt('FocalPlaneResolutionUnit');
  if (tmp <= 0) then
     tmp := GetRawInt('ResolutionUnit');
  case tmp of
    2: fpu := 25.4; // inch
    3: fpu := 10; // centimeter
  else
    fpu := 0.0
  end;
 
  fl := GetRawFloat('FocalLength');
  if (fpu = 0.0) or (fl = 0.0) then
    exit;
 
  tmp := GetRawInt('FocalPlaneXResolution');
  if (tmp > 0) then
    CCDWidth := Width * fpu / tmp;
  tmp := GetRawInt('FocalPlaneYResolution');
  if (tmp > 0) then
    CCDHeight := Height * fpu / tmp;
 
  if CCDWidth*CCDHeight <= 0 then // if either is zero
  begin
    if not estimateValues then
      exit;
    ratio := LookupRatio()
  end
  else
    ratio := Diag35mm / sqrt (sqr (CCDWidth) + sqr (CCDHeight));
 
  fl35 := fl * ratio;
 
// now load it into the tag array
    tmp := LookupTagDefn('FocalLengthIn35mmFilm');
    LookUpE := TagTable[tmp];
    NewE := LookupE;
    NewE.Data := Format('%5.2f',[fl35]);
    NewE.Raw := '';
    NewE.FormatS := '%s mm';
    NewE.TType := FMT_SRATIONAL;
    AddTagToArray(NewE);
    TraceStr := TraceStr+#13#10+
          siif(ExifTrace > 0,'tag[$'+inttohex(tmp,4)+']: ','')+
          NewE.Desc+DexifDelim+NewE.Data+
          siif(ExifTrace > 0,' [size: 0]','')+
          siif(ExifTrace > 0,' [start: 0]','');
end;
 
function TImageInfo.EXIFArrayToXML: tstringlist;
var buff:tstringlist;
  i:integer;
begin
  buff := TStringList.Create;
  buff.add(' <EXIFdata>');
  for i := 0 to fiTagCount-1 do
    with fITagArray[i] do
    begin
      buff.add(' <'+name+'>');
      if tag in [105,120] // headline and image caption
        then buff.add(' <![CDATA['+data+']]>')
        else buff.add(' '+data);
      buff.add(' </'+name+'>');
    end;
  buff.add(' </EXIFdata>');
  result := buff;
end;
 
function getbyte( var f : tstream) : byte;
var a : byte;
begin
  f.Read(a,1);
  result := a;
end;
 
//--------------------------------------------------------------------------
// Here we implement the Endian Independent layer. Outside
// of these methods we don't care about endian issues.
//--------------------------------------------------------------------------
function tEndInd.GetDataBuff: string;
begin
  result := llData;
end;
 
procedure tEndInd.SetDataBuff(const Value: string);
begin
  llData := Value;
end;
 
procedure tEndInd.WriteInt16(var buff:string;int,posn:integer);
begin
  if MotorolaOrder then
  begin
    buff[posn+1] := char(int mod 256);
    buff[posn] := char(int div 256);
  end
  else
  begin
    buff[posn] := char(int mod 256);
    buff[posn+1] := char(int div 256);
  end
end;
 
procedure tEndInd.WriteInt32(var buff:string;int,posn:longint);
begin
  if MotorolaOrder then
  begin
    buff[posn+3] := char(int mod 256);
    buff[posn+2] := char((int shr 8) mod 256);
    buff[posn+1] := char((int shr 16) mod 256);
    buff[posn] := char((int shr 24) mod 256);
  end
  else
  begin
    buff[posn] := char(int mod 256);
    buff[posn+1] := char((int shr 8) mod 256);
    buff[posn+2] := char((int shr 16) mod 256);
    buff[posn+3] := char((int shr 24) mod 256);
  end
end;
 
//--------------------------------------------------------------------------
// Convert a 16 bit unsigned value from file's native byte order
//--------------------------------------------------------------------------
function tEndInd.Get16u(oset:integer):word;
// var hibyte,lobyte:byte;
begin
// To help debug, uncomment the following two lines
// hibyte := byte(llData[oset+1]);
// lobyte := byte(llData[oset]);
  if MotorolaOrder
    then result := (byte(llData[oset]) shl 8)
           or byte(llData[oset+1])
    else result := (byte(llData[oset+1]) shl 8)
           or byte(llData[oset]);
end;
 
//--------------------------------------------------------------------------
// Convert a 32 bit signed value from file's native byte order
//--------------------------------------------------------------------------
function tEndInd.Get32s(oset:integer):Longint;
begin
  if MotorolaOrder
    then result := (byte(llData[oset]) shl 24)
           or (byte(llData[oset+1]) shl 16)
           or (byte(llData[oset+2]) shl 8)
           or byte(llData[oset+3])
    else result := (byte(llData[oset+3]) shl 24)
           or (byte(llData[oset+2]) shl 16)
           or (byte(llData[oset+1]) shl 8)
           or byte(llData[oset]);
end;
 
//--------------------------------------------------------------------------
// Convert a 32 bit unsigned value from file's native byte order
//--------------------------------------------------------------------------
function tEndInd.Put32s(data:Longint):string;
var data2:integer;
     buffer:string[4] absolute data2;
     bbuff:char;
begin
  data2 := data;
  if MotorolaOrder then
  begin
    bbuff := buffer[1];
    buffer[1] := buffer[4];
    buffer[4] := bbuff;
    bbuff := buffer[2];
    buffer[2] := buffer[3];
    buffer[3] := bbuff;
  end;
  result := buffer;
end;
 
//--------------------------------------------------------------------------
// Convert a 32 bit unsigned value from file's native byte order
//--------------------------------------------------------------------------
function tEndInd.Get32u(oset:integer):Longword;
begin
  result := Longword(Get32S(oset)) and $FFFFFFFF;
end;
 
//--------------------------------------------------------------------------
// The following methods implement the outer parser which
// decodes the segments. Further parsing isthen passed on to
// the TImageInfo (for EXIF) and TIPTCData objects
//--------------------------------------------------------------------------
Procedure TImgData.MakeIPTCSegment(buff:string);
var bl:integer;
begin
  bl := length(buff)+2;
  if IPTCSegment = nil then
  begin
    inc(SectionCnt);
    IPTCSegment := @(sections[SectionCnt]);
  end;
  IPTCSegment^.data := char(bl div 256)+char(bl mod 256)+buff;
  IPTCSegment^.size := bl;
  IPTCSegment^.dtype := M_IPTC;
end;
 
Procedure TImgData.MakeCommentSegment(buff:string);
var bl:integer;
begin
  bl := length(buff)+2;
  if CommentSegment = nil then
  begin
    inc(SectionCnt);
    CommentSegment := @(sections[SectionCnt]);
  end;
  CommentSegment^.data := char(bl div 256)+char(bl mod 256)+buff;
  CommentSegment^.size := bl;
  CommentSegment^.dtype := M_COM;
end;
 
Function TImgData.GetCommentSegment:string;
begin
  result := '';
  if CommentSegment <> nil then
    result := copy(CommentSegment^.data,2,maxint);
end;
 
function TImgData.SaveExif(var jfs2:tstream):longint;
var cnt:longint;
    buff:string;
begin
  cnt:=0;
  buff := #$FF#$D8;
  jfs2.Write(pointer(buff)^,length(buff));
  if ExifSegment <> nil then
    with ExifSegment^ do
    begin
      buff := #$FF+chr(Dtype)+data;
      cnt := cnt+jfs2.Write(pointer(buff)^,length(buff));
    end
  else
    if HeaderSegment <> nil then
      with HeaderSegment^ do
      begin
        buff := chr($FF)+chr(Dtype)+data;
        // buff := #$FF+chr(Dtype)+#$00#$10'JFIF'#$00#$01#$02#$01#$01','#$01','#$00#$00;
        cnt := cnt+jfs2.Write(pointer(buff)^,length(buff));
      end
    else if (cnt = 0) then
    begin
      // buff := chr($FF)+chr(Dtype)+data;
      buff := #$FF+chr(M_JFIF)+#$00#$10'JFIF'#$00#$01#$02#$01#$01','#$01','#$00#$00;
      cnt := cnt+jfs2.Write(pointer(buff)^,length(buff));
    end;
  if IPTCSegment <> nil then
    with IPTCSegment^ do
    begin
      buff := chr($FF)+chr(Dtype)+data;
      cnt := cnt+jfs2.Write(pointer(buff)^,length(buff));
    end;
  if CommentSegment <> nil then
    with CommentSegment^ do
    begin
      buff := chr($FF)+chr(Dtype)+data;
      cnt := cnt+jfs2.Write(pointer(buff)^,length(buff));
    end;
   result := cnt;
end;
 
function TImgData.ExtractThumbnailBuffer: String;
var
  STARTmarker,STOPmarker:integer;
  tb:string;
begin
  result := '';
  if HasThumbnail then
  begin
    try
      tb := copy(DataBuff,ExifObj.ThumbStart,ExifObj.ThumbLength);
      STARTmarker := pos(#$ff#$d8#$ff#$db,tb);
      if Startmarker = 0 then
        STARTmarker := pos(#$ff#$d8#$ff#$c4,tb);
      if STARTmarker <= 0 then
        exit;
      tb := copy(tb,STARTmarker,length(tb)); // strip off thumb data block
      // ok, this is fast and easy - BUT what we really need
      // is to read the length bytes to do the extraction...
      STOPmarker := pos(#$ff#$d9,tb)+2;
      tb := copy(tb,1,STOPmarker);
      result := tb;
    except
    // result will be empty string...
    end;
  end;
end;
 
{$IFNDEF dExifNoJpeg} 
 
function TImgData.ExtractThumbnailJpeg: TJpegImage;
var ti:TJPEGImage;
  x:TStringStream;
  tb:string;
begin
  result := nil;
  if HasThumbnail and (ExifObj.ThumbType = JPEG_COMP_TYPE) then
  begin
    tb := ExtractThumbnailBuffer();
    if (tb = '') then
      exit;
    x := TStringStream.Create(tb);
    ti := TJPEGImage.Create;
    x.Seek(0,soFromBeginning);
    ti.LoadFromStream(x);
    x.Free;
    result := ti;
  end;
end;
 
procedure TImgData.WriteEXIFJpeg(j:tjpegimage;fname:string;origName:string;
  adjSize:boolean = true);
begin
  if origName = 'then
    origName := fname;
  if not ReadExifInfo(origName) then
  begin
    j.SaveToFile(fname);
    exit;
  end;
  WriteEXIFJpeg(j,fname,adjSize);
end;
 
procedure TImgData.WriteEXIFJpeg(fname:string);
var img:tjpegimage;
begin
  img := TJPEGImage.Create;
  img.LoadFromFile(Filename);
  WriteEXIFJpeg(img,fname,false);
  img.Free;
end;
 
procedure TImgData.WriteEXIFJpeg(j:tjpegimage;fname:string; adjSize:boolean = true);
var jms:tmemorystream;
    jfs:TFileStream;
    pslen:integer;
    tb:array[0..12] of byte;
begin
  pslen := 2;
  jms := tmemorystream.Create;
  try { Thanks to Erik Ludden... } 
    jfs := tfilestream.Create(fname,fmCreate or fmShareExclusive);
    try
      if adjSize and (EXIFobj <> nil) then
        EXIFobj.AdjExifSize(j.height,j.width);
      SaveExif(tstream(jfs));
      j.SaveToStream(jms);
      jms.Seek(2,soFromBeginning);
      jms.Read(tb,12); // a little big to help debug...
      if tb[1] = M_JFIF then // strip header
        pslen := pslen+(tb[2]*256)+tb[3]+2; // size+id bytes
      jms.Seek(pslen,soFromBeginning);
      jms.Read(tb,12);
      if tb[1] = M_EXIF then // strip exif
        pslen := pslen+tb[2]*256+tb[3]+2; // size+id bytes
      jms.Seek(pslen,soFromBeginning);
      jms.Read(tb,12);
      if tb[1] = M_IPTC then // strip iptc
        pslen := pslen+tb[2]*256+tb[3]+2; // size+id bytes
      jms.Seek(pslen,soFromBeginning);
      jms.Read(tb,12);
      if tb[1] = M_COM then // strip comment
        pslen := pslen+tb[2]*256+tb[3]+2; // size+id bytes
      jms.Seek(pslen,soFromBeginning);
      jfs.Seek(0,soFromEnd);
      jfs.CopyFrom(jms,jms.Size-pslen);
    finally
      jfs.Free;
    end
  finally
    jms.Free;
  end
end;
 
{$ENDIF} 
 
function TImgData.GetCommentStr:string;
var buffer:string;
    bufLen:integer;
begin
  buffer := CommentSegment^.Data;
  bufLen := (byte(buffer[1]) shl 8) or byte(buffer[2]);
  result := copy(buffer,3,bufLen-2);
end;
 
function TImgData.ReadExifInfo(fname:string):boolean;
begin
  ProcessFile(fname);
  result := HasMetaData();
end;
 
function TImgData.FillInIptc:boolean;
begin
  if IPTCSegment = nil then
    CreateIPTCObj
  else
    IPTCObj.ParseIPTCArray(IPTCSegment^.Data);
// filename := FName;
  result := IPTCObj.HasData();
end;
 
 
function TImgData.ProcessFile( const FileName :String):boolean;
var extn:string;
begin
  reset;
  result := false;
  if not FileExists(FileName) then
    exit;
  SetFileInfo(FileName);
  try
      errstr := 'Not an EXIF file';
      extn := lowercase(ExtractFileExt(filename));
      if (extn = '.jpg') or (extn = '.jpeg') or (extn = '.jpe') then
      begin
        if not ReadJpegFile(FileName) then
          exit;
      end
      else
      if (extn = '.tif') or (extn = '.tiff') or (extn = '.nef') then
      begin
        if not ReadTiffFile(FileName) then
          exit;
      end
      else
      begin
        exit;
      end;
      errstr := '<none>';
// msAvailable := ReadMSData(Imageinfo);
// msName := gblUCMaker;
      result := true;
  except
    errstr := 'Illegal Exif construction';
  end;
end;
 
procedure TImgData.SetFileInfo(fname:string);
var s:tsearchrec;
    stat:word;
begin
   stat := findfirst(fname,faAnyFile,s);
   if stat = 0 then
   begin
     Filename := fname;
     FileDateTime := FileDateToDateTime(s.Time);
     FileSize := s.Size;
   end;
   FindClose(s);
end;
 
procedure TImgData.CreateIPTCObj;
begin
  MakeIPTCSegment('');
  IPTCobj := TIPTCdata.Create(self);
  // IPTCdata := IPTCobj; // old style global pointer
end;
 
//--------------------------------------------------------------------------
// Parse the marker stream until SOS or EOI is seen;
//--------------------------------------------------------------------------
function TImgData.ReadJpegSections (var f: tstream):boolean;
var a,b:byte;
    ll,lh,itemlen,marker:integer;
begin
  a := getbyte(f);
  b := getbyte(f);
  if (a <> $ff) or (b <> M_SOI) then
  begin
    result := FALSE;
    exit;
  end;
  SectionCnt := 0;
  while SectionCnt < 20 do // prevent overruns on bad data
  begin
    repeat
      marker := getByte(f);
    until marker <> $FF;
    Inc(SectionCnt);
    // Read the length of the section.
    lh := getByte(f);
    ll := getByte(f);
    itemlen := (lh shl 8) or ll;
    with Sections[SectionCnt] do
    begin
      DType := marker;
      Size := itemlen;
      setlength(data,itemlen);
      data[1] := chr(lh);
      data[2] := chr(ll);
      try
        F.Read(data[3],itemlen-2);
      except
        continue;
      end;
    end;
    if (SectionCnt = 5) and not HasMetaData() then
      break; // no exif by 8th - let's not waste time
    case marker of
      M_SOS: begin
               break;
             end;
      M_EOI: begin // in case it's a tables-only JPEG stream
               break;
             end;
      M_COM: begin // Comment section
               CommentSegment := @sections[SectionCnt];
             end;
      M_IPTC: begin // IPTC section
               if (IPTCSegment = nil) then
               begin
                 IPTCSegment := @sections[SectionCnt];
                 IPTCobj := TIPTCdata.Create(self);
                 // IPTCdata := IPTCobj; // old style global pointer
               end;
             end;
      M_JFIF: begin
                // Regular jpegs always have this tag, exif images have the exif
                // marker instead, althogh ACDsee will write images with both markers.
                // this program will re-create this marker on absence of exif marker.
               // dec(SectionCnt);
                HeaderSegment := @sections[SectionCnt];
                // break;
              end;
      M_EXIF: begin
                if ((SectionCnt <= 5) and (EXIFsegment = nil) )then
                begin
                    // Seen files from some 'U-lead' software with Vivitar scanner
                    // that uses marker 31 later in the file (no clue what for!)
                    EXIFsegment := @sections[SectionCnt];
                    EXIFobj := TImageInfo.Create(self,BuildList);
                    EXIFobj.TraceLevel := TraceLevel;
                    // ImageInfo := EXIFobj; // old style global pointer
                    SetDataBuff(EXIFsegment^.data);
                    ProcessEXIF;
                end
                else
                begin
                  // Discard this section.
                  dec(SectionCnt);
                end;
              end;
      M_SOF0..M_SOF15: begin
                 // process_SOFn(Data, marker);
             end;
    else
      // break;
    end;
 end;
 result := HasMetaData();
end;
 
function TImgData.ReadJpegFile(const FileName:string):boolean;
var F: tfilestream;
begin
  TiffFmt := false; // default mode
  F := TFileStream.Create(filename,fmOpenRead or fmShareDenyWrite);
  try
    result := ReadJpegSections(tstream(F));
  except
    result := false;
  end;
  F.Free;
end;
 
function TImgData.ReadTiffSections (var f: tstream):boolean;
var // lh,ll,
    itemlen:integer;
    fmt:string;
begin
  result := true;
  fmt := char(getbyte(f))+char(getbyte(f));
  if (fmt <> 'II') and (fmt <> 'MM') then
  begin
    result := FALSE;
    exit;
  end;
 
  setlength(Sections[1].data,6);
  F.Read(Sections[1].data[1],6);
{
  // length calculations are inconsistant for TIFFs
  lh := byte(Sections[1].data[1]);
  ll := byte(Sections[1].data[2]);

  if MotorolaOrder
    then itemlen := (lh shl 8) or ll
    else itemlen := (ll shl 8) or lh;
}
 
// itemlen := (ll shl 8) or lh;
 
  itemlen := TiffReadLimit;
 
  setlength(Sections[1].data,itemlen);
  F.Read(Sections[1].data[1],itemlen);
 
  SectionCnt := 1;
  EXIFsegment := @(sections[1]);
 
  EXIFobj := TImageInfo.Create(self,BuildList);
  EXIFobj.TraceLevel := TraceLevel;
  ExifObj.TiffFmt := TiffFmt;
  ExifObj.TraceStr := '';
  EXIFsegment := @sections[SectionCnt];
  ExifObj.DataBuff := Sections[1].data;
  ExifObj.parent.DataBuff := Sections[1].data;
  ExifObj.MotorolaOrder := fmt = 'MM';
  EXIFobj.ProcessExifDir(1, -7 , itemlen);
  EXIFobj.Calc35Equiv();
end;
 
function TImgData.ReadTiffFile(const FileName:string):boolean;
var F: tfilestream;
begin
  TiffFmt := true;
  F := TFileStream.Create(filename,fmOpenRead or fmShareDenyWrite);
  try
    result := ReadTiffSections(tstream(F));
  except
    result := false;
  end;
  F.Free;
  TiffFmt := false;
end;
 
Procedure TImgData.ProcessEXIF;
var hdr:string;
    toset:integer;
begin
  if not assigned(ExifObj) then
    ExifObj := TImageInfo.Create(self,BuildList);
  hdr := copy(EXIFsegment^.Data,3,length(validHeader));
  if hdr <> validHeader then
  begin
    errStr := 'Incorrect Exif header';
    exit;
  end;
  if copy(EXIFsegment^.Data,9,2) = 'IIthen
    MotorolaOrder := false
  else if copy(EXIFsegment^.Data,9,2) = 'MMthen
    MotorolaOrder := true
  else
  begin
    errStr := 'Invalid Exif alignment marker';
    exit;
  end;
  ExifObj.TraceStr := '';
  ExifObj.DataBuff := DataBuff;
  ExifObj.MotorolaOrder := MotorolaOrder;
 
  toset := Get32u(17-4);
  if toset = 0
    then ExifObj.ProcessExifDir(17, 9, EXIFsegment^.Size-6)
    else ExifObj.ProcessExifDir(9+toset, 9, EXIFsegment^.Size-6);
  if errstr <> 'then
  begin
    EXIFobj.Calc35Equiv();
  end;
end;
 
procedure TImgData.Reset;
begin
  SectionCnt := 0;
  ExifSegment := nil;
  IPTCSegment := nil;
  CommentSegment := nil;
  HeaderSegment := nil;
  Filename := '';
  FileDateTime := 0;
  FileSize := 0;
  ErrStr := '';
  FreeAndNil(ExifObj);
  FreeAndNil(IptcObj);
  MotorolaOrder := false;
end;
 
function TImgData.HasMetaData: boolean;
begin
  result := (EXIFsegment <> nil) or (CommentSegment <> nil) or
            (IPTCsegment <> nil);
end;
 
function TImgData.HasEXIF: boolean;
begin
  result := (EXIFsegment <> nil);
end;
 
function TImgData.HasThumbnail: boolean;
begin
  result := (EXIFsegment <> nil) and EXIFobj.hasThumbnail;
end;
 
function TImgData.HasIPTC: boolean;
begin
  result := (IPTCsegment <> nil);
end;
 
function TImgData.HasComment: boolean;
begin
  result := (Commentsegment <> nil);
end;
 
function TImageInfo.HasThumbnail: boolean;
begin
  // 19 is minimum valid starting position
  result := (ThumbStart > 21) and (ThumbLength > 256);
end;
 
function TImgData.ReadIPTCStrings(fname: string): tstringlist;
begin
  if ProcessFile(fname) and HasIPTC then
    result := IPTCObj.ParseIPTCStrings(IPTCSegment^.Data)
  else
    result := nil;
end;
 
function TImgData.MetaDataToXML: tstringlist;
var buff,buff2:tstringlist;
  s:tsearchrec;
begin
  if FindFirst(Filename,faAnyFile,s) <> 0 then
  begin
    FindClose(s);
    result := nil;
    exit;
  end;
  buff := TStringList.Create;
  buff.add('<dImageFile>');
  buff.add(' <OSdata>');
  buff.add(' <name> '+ExtractFileName(s.Name)+' </name>');
  buff.add(' <path> '+ExtractFilePath(Filename)+' </path>');
  buff.add(' <size> '+inttostr(s.Size)+' </size>');
  buff.add(' <date> '+DateToStr(FileDateToDateTime(s.time))+' </date>');
  buff.add(' </OSdata>');
  if ExifObj <> nil then
  begin
    buff2 := ExifObj.EXIFArrayToXML;
    if buff2 <> nil then
    begin
      buff.AddStrings(buff2);
      buff2.Clear;
      buff2.Free;
    end;
  end;
  if IptcObj <> nil then
  begin
    buff2 := IptcObj.IPTCArrayToXML;
    if buff2 <> nil then
    begin
      buff.AddStrings(buff2);
      buff2.Clear;
      buff2.Free;
    end;
  end;
  buff.add('</dImagefile>');
  result := buff;
end;
 
function defIntFmt (inInt:integer):string;
begin
  result := IntToStr(inInt)
end;
 
function defRealFmt(inReal:double):string;
begin
  result := FloatToStr(inReal);
end;
 
function GCD(a, b : integer):integer;
begin
  try
  if (b mod a) = 0 then
    Result := a
  else
    Result := GCD(b, a mod b);
  except
    result := 1
  end;
end;
 
 
function fmtRational( num,den:integer):string;
var
  gcdVal,intPart,fracPart,newNum,newDen: integer;
  outStr:String;
begin
  // first, find the values
  gcdVal := GCD(num,den);
  newNum := num div gcdVal; // reduce the numerator
  newDen := den div gcdVal; // reduce the denominator
  intPart := newNum div newDen;
  fracPart := newNum mod newDen;
 
  // now format the string
  outStr := '';
  if intPart <> 0 then
     outStr := inttostr(intPart)+' ';
  if fracPart <> 0 then
       outStr := outStr + inttostr(fracPart)+'/'+inttostr(newDen);
  result := trim(outstr); // trim cleans up extra space
end;
 
function defFracFmt(inNum,inDen:integer):string;
begin
  result := format('%d/%d',[inNum,inDen]);
 // result := fmtRational(inNum,inDen);
 //
 // It turns out this is not a good idea generally
 // because some std. calculation use rational
 // representations internally
end;
 
{$IFDEF dEXIFpredeclare} 
 
initialization
  ImgData := TImgData.create;
finalization
  ImgData.Free;
{$ENDIF} 
end.
  Mit Zitat antworten Zitat