{****************************************************************************}
{Unit VNM_EXIF - it is a addon unit for graphics library VenomGFX.           }
{It can extract a EXIF info from .JPG graphics files                         }
{   The unit is prepared for easy localization                               }
{   Source code is derived from Delphi component Exif made by SimBa          }
{   aka Dimoniusis (contact dimonius@mail333.com)                            }
{   Into my system of graphic unit adapted in August 2016 (Laaca)            }
{****************************************************************************}

unit Vnm_Exif;
{$IFDEF FPC}
   {$CALLING OLDFPCCALL}
   {$ASMMODE INTEL}
   {$IFDEF VER2}{$DEFINE NEWFPC}{$ENDIF}
   {$IFDEF VER3}{$DEFINE NEWFPC}{$ENDIF}
{$ENDIF}

interface
uses GrpFile;

type
  TIfdTag = packed record
    ID      : Word;       //Tag number
    Typ     : Word;       //Type tag
    Count   : dword;   //tag length
    Offset  : dword;   //Offset / Value
  end;

  TGpsTag = packed record
    ID      : Word;       //Tag number
    Typ     : Word;       //Type tag
    Count   : dword;   //tag length
    Offset  : dword;   //Offset / Value
  end;

  PExif = ^TExif;
  TExif = Object
      FImageDesc          : String;     //Picture description
      FMake               : String;     //Camera manufacturer
      FModel              : String;     //Camere model
      FSoftware           : String;     //Software (firmware) used for recording
      FOrientation        : Byte;       //Image orientation - 1 normal
      FOrientationIndex   : Byte;
      FCopyright          : String;     //Copyright
      FValid              : Boolean;    //Has valid Exif header
      FDateTime           : String;     //Date and Time of Change
      FDateTimeOriginal   : String;     //Original Date and Time
      FDateTimeDigitized  : String;     //Camshot Date and Time
      FUserComments       : String;     //User Comments

      FExposure           : String;     //Exposure
      FFstops             : String;
      FShutterSpeed       : string;
      FAperture           : string;
      FMaxAperture        : string;

      FExposureProgram    : Byte;
      FExposureProgramIndex:Byte;
      FPixelXDimension    : dword;
      FPixelYDimension    : dword;
      FXResolution        : dword;
      FYResolution        : dword;
      FMeteringMode       : byte;
      FMeteringModeIndex  : byte;
      FLightSource        : Byte;
      FLightSourceIndex   : Byte;
      FZoom               : string[10];
      FZoomIndex          : byte;

      FFlash              : Byte;
      FFlashIndex         : Byte;
      FISO                : Word;
      FArtist             : string;
      FCompressedBPP      : string;

      GPSver              :string[20];
      GPSaltref           :string[1];
      GPSlatref           :char;
      GPSlongref          :char;

      GPSlat1,GPSlat2,GPSlat3:string[10];
      GPSlon1,GPSlon2,GPSlon3:string[10];
      GPSalt                 :string[10];
      GPSdir                 :string[12];

      GPS_deg_sep,GPS_min_sep,GPS_sec_sep:string[4];

      f                   : PGrpStream;
      ifdp                : dword;
      gpsi                : dword;
      FSwap               : boolean;

      orient_ss:^string;
      expltype_ss:^string;
      meter_ss:^string;
      flash_ss:^string;
      lightsrc_ss:^string;


      Function Get_Meter_Desc:string;virtual;
      Function Get_Exptype_Desc:string;virtual;
      Function Get_Orient_Desc:string;virtual;
      Function Get_Flash_Desc:string;virtual;
      Function Get_LightSrc_Desc:string;virtual;

      Function Get_GPS_latitude:string;virtual;
      Function Get_GPS_longitude:string;virtual;
      Function Get_GPS_altitude:string;virtual;
      Function Get_GPS_direction:string;virtual;

      Function Get_GPS_lat_char:string;virtual;
      Function Get_GPS_lon_char:string;virtual;

      Constructor Init;
      Procedure FreeVariables;virtual;
      function  ReadAsci(const Offset, Count: dword): String;
      function  ReadRatio(const Offset: dword; frac: boolean; places:byte): String; overload;
      function  ReadRatio(const Offset: dword): single; overload;
      procedure ReadTag(var tag: TIfdTag);
      function ReadLongIntValue(const Offset: dword): LongInt;
      procedure ReadFromFile(const FileName: String);virtual;
      Function TranslateMessage(s:string):string;virtual;
      Destructor Done;virtual;
  end;

implementation
uses Dos{$IFDEF NEWFPC},Math{$ENDIF};

type
  TMarker = packed record
    Marker  : Word;      //Section marker
    Len     : Word;      //Length Section
    Indefin : Array [0..4] of Char; //Indefiner - "Exif" 00, "JFIF" 00 and ets
    Pad     : Char;      //0x00
  end;

  TIFDHeader = packed record
    pad       : Byte; //00h
    ByteOrder : Word; //II (4D4D) or MM
    i42       : Word; //2A00 (magic number from the 'Hitchhikers Guide'
    Offset    : dword; //0th offset IFD
    Count     : Word;     // number of IFD entries
  end;


const
orient_def:string='Normal*Mirrored*Rotated 180*Rotated 180*mirrored*Rotated 90 left and mirrored*Rotated 90 right*Rotated 90 right and mirrored*Rotated 90 left*Unknown';
expltype_def:string='Unknown*Manual Control*Normal Program*Aperture Priority*Shutter Priority*Creative Program*Action Program*Portrait Mode*Landscape Mode';
meter_def:string='Unknown*Average*Center Weighted Average*Spot*Multi Spot*Pattern*Partial*Other';
flash_def:string='No Flash*Flash*Flash No Strobe*Flash Strobe*Flash (Auto)*No Flash';
lightsrc_def:string='Unknown*Daylight*Flourescent*Tungsten*Flash*Standard Light A*Standard Light B*Standard Light C*D55*D65*D75*Other*Unknown';

function SwapEndianW(AValue: Word): Word;
begin
SwapEndianW:=Word((AValue shr 8) or (AValue shl 8));
end;


function SwapEndianD(AValue: DWord): DWord;
begin
SwapEndianD:=(AValue shl 24)
           or ((AValue and $0000FF00) shl 8)
           or ((AValue and $00FF0000) shr 8)
           or (AValue shr 24);
end;


function Mid (S:string; B,E: longint): string;
{Vraci cast podretezce S pocinaje B-tym znakem a E-tym konce}
begin
Mid:=Copy(s,b,e-b+1);
end;


function MyStr (Cislo: longint): string;
var Vysledek : string;
begin { MyStr }
  Str (Cislo, Vysledek);
  MyStr := Vysledek;
end;  { MyStr }


function SkipEndZeros(s:string):string;
var i: Byte;
begin { SkipEndSpaces }
  i := Length (S);
  if i>0 then
     begin
     while (S[i] in [' ','0']) and (i>0) do Dec (i);
     if s[i]='.' then Dec(i);
     SkipEndZeros := Mid (S,1,i);
     end
     else SkipEndZeros:='';
end;  { SkipEndSpaces }


function SkipEndSpaces (S:string):string;
var i: Byte;
begin { SkipEndSpaces }
  i := Length (S);
  if i>0 then
     begin
     while (S[i] in [' ',#0]) and (i>0) do Dec (i);
     SkipEndSpaces := Mid (S,1,i);
     end
     else SkipEndSpaces:='';
end;  { SkipEndSpaces }


function pSearch (Text:pchar;const S:string;Poz:longint):longint;
{ Funkce hleda string S v textu Text od pozice Pos (vcetne)}
var i, N: longint;
    c:char;
    d:boolean;

begin {Search}
if poz<1 then poz:=1;
dec(poz,2);
N:=Length(S);       {delka hledaneho retezce}
repeat
d:=true;
for i:=1 to N do
   begin
   c:=text[poz+i];
   if c=#0 then
      begin
      pSearch:=0;
      Exit;
      end
      else if c<>s[i] then begin inc(poz);d:=false;Break;end;
   end;
if D then begin pSearch:=poz+2;Exit;end;
until false;
end;


Function Search(Text:string;const S:string;Poz:longint):longint;
begin
text:=text+#0;
Search:=pSearch(@text[1],s,poz);
end;


Function VratSlovo(s:string;n:byte):string;
var a,b,c:byte;
begin
if n=0 then begin VratSlovo:='';Exit;end;
a:=1;
b:=1;
for c:=1 to n do
   begin
   while s[b]='*' do inc(b);
   a:=b;
   b:=Search(s,'*',a);
   end;

if b=0 then b:=Length(s) else dec(b);
VratSlovo:=Mid(s,a,b);
end;



Function ExistFile(s:string):boolean;
{Zjisti,zda dany soubor existuje }
var r:searchrec;
begin
if s='' then begin ExistFile:=false;Exit;end;
FindFirst(s,archive+hidden+readonly+sysfile,r);
if DosError=0 then ExistFile:=true else ExistFile:=false;
FindClose(r);
end;



procedure TExif.ReadTag(var tag: TIfdTag);
begin
  f^.ReadStream(tag,12);
  if FSwap then with tag do begin // motorola or intel byte order ?
    ID  := SwapEndianW(ID);
    Typ := SwapEndianW(Typ);
    Count := SwapEndianD(dword(Count));
    if (Typ=1) or (Typ=3) then
      Offset := (Offset shr 8) and $FF
    else
      Offset  := SwapEndianD(dword(Offset));
    end
  else with tag do begin
    if ID<>$8827 then  //ISO Metering Mode not need conversion
      if (Typ=1) or (Typ=3) then
        Offset := Offset and $FF; // other bytes are undefined but maybe not zero
  end;
end;

function TExif.ReadAsci(const Offset, Count: dword): String;
var
  fp: LongInt;
  i: Word;
  s: string;
  c: char;
begin
  s:='';
  fp:=f^.GetPos; //Save file offset
  f^.Seek(offset);

    i:=1;
    repeat
      f^.ReadStream(c,1);
      if c<>#0 then s:=s+c;
      inc(i);
    until (i>=Count) or (c=#0);

  ReadAsci:=SkipEndSpaces(s);
  f^.Seek(fp);     //Restore file offset
end;

function TExif.ReadLongIntValue(const Offset: dword): LongInt;
var
  fp,r: LongInt;
begin
  fp:=f^.GetPos; //Save file offset
  f^.Seek(Offset);

    f^.ReadStream(R, 4);
    if FSwap then R:=SwapEndianD(dword(R));

  f^.Seek(fp); //Restore file offset
  ReadLongIntValue:=r;
end;

function TExif.ReadRatio(const Offset: dword; frac: boolean; places:byte): String;
var
  fp: LongInt;
  nom,denom: dword;
  r:string;

begin
  fp:=f^.GetPos; //Save file offset
  f^.Seek(Offset);
  r:='';

    f^.ReadStream(nom,4);
    f^.ReadStream(denom,4);
    if FSwap then begin     // !!!
      nom := SwapEndianD(dword(nom));
      denom := SwapEndianD(dword(denom));
    end;
    if frac then
       begin
       str((nom/denom):1:places, r);
       if (length(r)>0) and (r[length(r)]='0') then R:=copy(R,1,length(R)-1);
       end
       else if denom<>1000000
               then R:=MyStr(nom)+'/'+MyStr(denom)
               else R:='0';

  f^.Seek(fp);     //Restore file offset
  ReadRatio:=R;
end;


function TExif.ReadRatio(const Offset: dword): single;
var
  fp: LongInt;
  nom,denom: dword;
  R:single;
begin
  fp:=f^.GetPos;       //Save file offset
  f^.Seek(Offset);

    f^.ReadStream(nom,4);
    f^.ReadStream(denom,4);
    if FSwap then begin     // !!!
      nom := SwapEndianD(dword(nom));
      denom := SwapEndianD(dword(denom));
    end;
    R:=nom/denom;

  f^.Seek(fp);     //Restore file offset
  ReadRatio:=R;
end;


Function TExif.Get_Meter_Desc:string;
begin
if meter_ss<>nil then Get_Meter_Desc:=VratSlovo(meter_ss^,FMeteringModeIndex)
                 else Get_Meter_Desc:='';
end;


Function TExif.Get_Exptype_Desc:string;
begin
if expltype_ss<>nil then Get_Exptype_Desc:=VratSlovo(expltype_ss^,FExposureProgramIndex)
                    else Get_Exptype_Desc:='';
end;


Function TExif.Get_Orient_Desc:string;
begin
if orient_ss<>nil then Get_Orient_Desc:=VratSlovo(orient_ss^,FOrientationIndex)
                  else Get_Orient_Desc:='';
end;


Function TExif.Get_Flash_Desc:string;
begin
if flash_ss<>nil then Get_Flash_Desc:=VratSlovo(flash_ss^,FFlashIndex)
                 else Get_Flash_Desc:='';
end;


Function TExif.Get_LightSrc_Desc:string;
begin
if lightsrc_ss<>nil then Get_LightSrc_Desc:=VratSlovo(lightsrc_ss^,FLightSourceIndex)
                    else Get_LightSrc_Desc:='';
end;


Function TExif.Get_GPS_latitude:string;
begin
Get_GPS_latitude:=GPSlat1+GPS_deg_sep+GPSlat2+GPS_min_sep+
                  GPSlat3+GPS_sec_sep+Get_GPS_lat_char;
end;


Function TExif.Get_GPS_longitude:string;
begin
Get_GPS_longitude:=GPSlon1+GPS_deg_sep+GPSlon2+GPS_min_sep+
                  GPSlon3+GPS_sec_sep+Get_GPS_lon_char;
end;


Function TExif.Get_GPS_lat_char:string;
begin
Get_GPS_lat_char:=GPSlatref;
end;


Function TExif.Get_GPS_lon_char:string;
begin
Get_GPS_lon_char:=GPSlongref;
end;


Function TExif.Get_GPS_altitude:string;
begin
Get_GPS_altitude:=GPSaltref+GPSalt;
end;


Function TExif.Get_GPS_direction:string;
begin
Get_GPS_direction:=GPSdir;
end;



Function TExif.TranslateMessage(s:string):string;
begin
TranslateMessage:=s;
end;


Constructor TExif.Init;
begin
FreeVariables;

{and these below must be outside "FreeVAriables" as it is called in every}
{image load}
GPS_deg_sep:=#248;
GPS_min_sep:=#239;
GPS_sec_sep:=#239#239;
orient_ss:=@orient_def;
expltype_ss:=@expltype_def;
meter_ss:=@meter_def;
flash_ss:=@flash_def;
lightsrc_ss:=@lightsrc_def;
end;

Procedure TExif.FreeVariables;
begin
  f:=nil;
  ifdp:=0;
  gpsi:=0;
  FImageDesc:='';
  FMake:='';
  FModel:='';
  FSoftware:='';
  FOrientation:=0;
  FOrientationIndex:=0;
  FDateTime:='';
  FCopyright:='';
  FValid:=False;
  FDateTimeOriginal:='';
  FDateTimeDigitized:='';
  FUserComments:='';
  FExposure:='';

  FFstops:='';
  FShutterSpeed := '';
  FAperture := '';
  FExposureProgram:=0;
  FExposureProgramIndex:=0;
  FPixelXDimension:=0;
  FPixelYDimension:=0;
  FMeteringMode:=0;
  FMeteringModeIndex:=0;
  FLightSource:=0;
  FLightSourceIndex:=0;
  FFlash:=0;
  FFlashIndex:=0;
  FISO:=0;
  FCompressedBPP:='';
  FArtist:='';
  FMaxAperture:='';
  FXResolution:=0;
  FYResolution:=0;
  FZoom:='';
  FZoomIndex:=0;

  GPSver:='';
  GPSlatref:=' ';
  GPSlongref:=' ';

  GPSlat1:='';
  GPSlat2:='';
  GPSlat3:='';
  GPSlon1:='';
  GPSlon2:='';
  GPSlon3:='';
  GPSaltref:='';
  GPSalt:='';
  GPSdir:='';
end;


procedure TExif.ReadFromFile(const FileName: String);
var
  j:      TMarker;
  ifd:    TIFDHeader;
  off0:   dword; //Null Exif Offset
  tag:    TIfdTag;
  i,u:    longint;
  n:      Single;
  SOI:    Word; //2 bytes SOI marker. FF D8 (Start Of Image)
  IfdCnt: Word;
  GPSCnt: Word;
  Tmp   : string;

begin
  if not ExistFile(FileName) then exit;
  FreeVariables;

f:=New(PGrpStream,Init(FileName,grpOpenRead));
f^.ReadStream(SOI,2);

  if SOI=$D8FF then
     begin //Is this Jpeg
     f^.ReadStream(j,9);
     if j.Marker=$E0FF then
        begin //JFIF Marker Found
        f^.Seek(20);  //Skip JFIF Header
        f^.ReadStream(j,9);
        end;

     //Search Exif start marker;
     if j.Marker<>$E1FF then
        begin
        i:=0;
        repeat
           u:=f^.ReadStream(SOI,2);
           if u=2 then inc(i);
        until (u<>2) or (i>1000) or (SOI=$E1FF);

        //If we find maker
        if SOI=$E1FF then
           begin
           f^.Seek(f^.GetPos-2); //return Back on 2 bytes
           f^.ReadStream(j,9);     //read Exif header
           end;
        end;

     if j.Marker=$E1FF then
        begin //If we found Exif Section. j.Indefin='Exif'.
        FValid:=True;
        off0:=f^.GetPos+1;    //0'th offset Exif header
        f^.ReadStream(ifd,11);  //Read IDF Header
        FSwap := ifd.ByteOrder=$4D4D; // II or MM  - if MM we have to swap
        if FSwap then
           begin
           ifd.Offset := SwapEndianD(dword(ifd.Offset));
           ifd.Count  := SwapEndianW(ifd.Count);
           end;
        if ifd.Offset <> 8 then
           begin
           f^.Seek(f^.GetPos+abs(ifd.Offset)-8);
           end;

        if (ifd.Count=0) then ifd.Count:=100;

        for i := 1 to ifd.Count do
            begin
            ReadTag(tag);
            case tag.ID of
                   0: break;
{ImageDescription} $010E: FImageDesc:=ReadAsci(tag.Offset+off0, tag.Count);
{Make}             $010F: FMake:=ReadAsci(tag.Offset+off0, tag.Count);
{Model}            $0110: FModel:=ReadAsci(tag.Offset+off0, tag.Count);
{Orientation}      $0112: begin
                          FOrientation:= tag.Offset;
                          if FOrientation in [1..8]
                             then FOrientationIndex:=FOrientation
                             else FOrientationIndex:=9;
                          end;
{DateTime}         $0132: FDateTime:=ReadAsci(tag.Offset+off0, tag.Count);
{CopyRight}        $8298: FCopyright:=ReadAsci(tag.Offset+off0, tag.Count);
{Software}         $0131: FSoftware:=ReadAsci(tag.Offset+off0, tag.Count);
{Artist}           $013B: FArtist:=ReadAsci(tag.Offset+off0, tag.Count);
{Exif IFD Pointer} $8769: ifdp:=Tag.Offset; //Read Exif IFD offset
{GPS pointer-Laaca}$8825: gpsi:=Tag.Offset; //Read GPS info offset
{XResolution}      $011A: FXResolution := ReadLongIntValue(Tag.Offset+off0);
{YResolution}      $011B: FYResolution := ReadLongIntValue(Tag.Offset+off0);
            end;{case}
            end;

        if ifdp>0 then
           begin
           f^.Seek(ifdp+off0);
           f^.ReadStream(IfdCnt,2);
           if FSwap then IfdCnt := SwapEndianW(IfdCnt);
           for i := 1 to IfdCnt do
                begin
                ReadTag(tag);
  {
          You may simple realize read this info:

          Tag |Name of Tag

          9000 ExifVersion
          0191 ComponentsConfiguration
          0392 BrightnessValue
          0492 ExposureBiasValue
          0692 SubjectDistance
          0A92 FocalLength
          9092 SubSecTime
          9192 SubSecTimeOriginal
          9292 SubSecTimeDigitized
          A000 FlashPixVersion
          A001 Colorspace
  }
                case tag.ID of
                     0: break;
{ExposureTime}   $829A: FExposure:=ReadRatio(tag.Offset+off0, false,2);{+TranslateMessage(' seconds');}
{Compressed Bpp} $9102: FCompressedBPP:=ReadRatio(tag.Offset+off0, true,2);
{F-Stop}         $829D: FFStops:=ReadRatio(tag.Offset+off0, true,2);
{FDateTimeOrig}  $9003: FDateTimeOriginal:=ReadAsci(tag.OffSet+off0,tag.Count);
{DateTimeDigit}  $9004: FDateTimeDigitized:=ReadAsci(tag.OffSet+off0,tag.Count);
{ShutterSpeed}   $9201: begin
                        n:=ReadRatio(tag.Offset+off0);
                        if n<65535 then
                           begin
                           str(power(2,n):1:0,tmp);
                           FShutterSpeed:='1/'+tmp+TranslateMessage(' seconds');
                           end else FShutterSpeed:=TranslateMessage('1 second');
                        end;
{ISO Speed}      $8827: FISO:=Tag.Offset;
{Aperture}       $9202: FAperture:=ReadRatio(tag.Offset+off0, true,2);
{Max Aperture}   $9205: FMaxAperture:=ReadRatio(tag.Offset+off0, true,2);
{UserComments}   $9286: FUserComments:=ReadAsci(tag.OffSet+off0,tag.Count);
{Metering Mode}  $9207: begin
                        FMeteringMode := Tag.OffSet;
                        if Tag.OffSet in [0..7]
                           then FMeteringModeIndex:=FMeteringMode+1
                           else FMeteringModeIndex:=1;
                        end;
{Light Source}   $9208: begin
                        FLightSource:=Tag.OffSet;
                        case Tag.OffSet of
                           0: FLightSourceIndex:=1;
                           1: FLightSourceIndex:=2;
                           2: FLightSourceIndex:=3;
                           3: FLightSourceIndex:=4;
                          10: FLightSourceIndex:=5;
                          17: FLightSourceIndex:=6;
                          18: FLightSourceIndex:=7;
                          19: FLightSourceIndex:=8;
                          20: FLightSourceIndex:=9;
                          21: FLightSourceIndex:=10;
                          22: FLightSourceIndex:=11;
                         255: FLightSourceIndex:=12;
                           else FLightSourceIndex:=13;
                        end; {case}
                        end;
{Flash}          $9209: begin
                        FFlash:=Tag.OffSet;
                        case Tag.OffSet of
                           0: FFlashIndex:=1;
                           1: FFlashIndex:=2;
                           5: FFlashIndex:=3;
                           7: FFlashIndex:=4;
                          25: FFlashIndex:=5;
                          else FFlashIndex:=6;
                        end; {case}
                        end;

{Exposure}       $8822: begin
                        FExposureProgram:=Tag.OffSet;
                        if Tag.OffSet in [1..8]
                           then FExposureProgramIndex:=FExposureProgram
                           else FExposureProgramIndex:=9;
                        end;

{PixelXDimension}$A002: FPixelXDimension := Tag.Offset;
{PixelYDimension}$A003: FPixelYDimension := Tag.Offset;
{Zoom}           $A404: begin
                        FZoom:=ReadRatio(tag.Offset+off0, true,4);
                        if FZoom='0' then FZoomIndex:=2 else FZoomIndex:=1;
                        end;

                end;{case}
        end; {...for i := 1 to IfdCnt do begin}
        end; {...if ifdp>0 then begin}


        if gpsi>0 then
           begin
           f^.Seek(gpsi+off0);
           f^.ReadStream(gpsCnt,2);
           if FSwap then GPSCnt := SwapEndianW(GPSCnt);
           for i := 1 to GPSCnt do
                begin
                ReadTag(tag);
                case tag.ID of
{GPS ver. info}      0: GPSver:=Mystr((tag.offset) and 255)+'.'+
                                Mystr((tag.offset shr 8) and 255)+'.'+
                                Mystr((tag.offset shr 16) and 255)+'.'+
                                Mystr((tag.offset shr 24) and 255);

{GPS lat. ref (N/S}  1: GPSlatref:=char(tag.offset);
{GPS lon. ref (E/W}  3: GPSlongref:=char(tag.offset);
{GPS lattitude}      2: begin
                        gpslat1:=SkipEndZeros(ReadRatio(tag.Offset+off0,true,8));
                        gpslat2:=SkipEndZeros(ReadRatio(tag.Offset+off0+8,true,8));
                        gpslat3:=SkipEndZeros(ReadRatio(tag.Offset+off0+16,true,8));
                        end;
{GPS longitude}      4: begin
                        gpslon1:=SkipEndZeros(ReadRatio(tag.Offset+off0,true,8));
                        gpslon2:=SkipEndZeros(ReadRatio(tag.Offset+off0+8,true,8));
                        gpslon3:=SkipEndZeros(ReadRatio(tag.Offset+off0+16,true,8));
                        end;

{GPS altitude ref.}  5: if tag.offset=1 then GPSaltref:='-'  {minus sign}
                                        else GPSaltref:='';

{GPS altitude}       6: GPSalt:=SkipEndZeros(ReadRatio(tag.Offset+off0,true,2));
{GPS direction}    $11: GPSdir:=SkipEndZeros(ReadRatio(tag.Offset+off0,true,2));

                end; {case}
                end;
           end;

        end; {...if j.Marker=$E1FF then begin}
  end; {...if SOI=$D8FF then begin }
  Dispose(f,Done);
  f:=nil;
end;


Destructor TExif.Done;
begin
if f<>nil then begin Dispose(f,Done);f:=nil;end;
end;


{init unit}
end.
