[Lazarus] Jpeg Exif reader was: TImage shows loaded image rotated by 90?

LacaK lacak at zoznam.sk
Thu Sep 29 12:28:19 CEST 2016


Attached improved code.
(as far as there is a lot of Exif tags, not all are parsed and placed in 
FExif record, but all are in FIFD array)
-Laco.

>> I have created small Exif reader for my own needs.
>> I have looked also in FCL TFPReaderJpeg (which uses pasjpeg) if there
>> is
>> no support, but I do not see.
>> If there would be interest for extending functionality of this reader
>> to
>> support reading of Exif information I can prepare patch.
>>
>> Here is my simple implementation if somebody is interested:
>>
-------------- next part --------------
unit JpegExif;

{$IFDEF FPC}
  {$mode objfpc}
{$ENDIF}
{$H+}

interface

uses
  Classes, SysUtils;

type

  { TJpegExifReader }

  TJpegExifReader = class
    private
      type
        TIFDEntry = record
          TagNo: Word;
          DataType: Word;
          Count: DWord;
          Value: DWord; // This  tag  records  the  offset  from  the  start  of  the  TIFF  header  to  the  position  where  the  value  itself  is  recorded.
                        // In cases where the value fits in 4 bytes, the value itself is recorded. If the value is smaller than 4 bytes, the value is stored in the 4-byte area starting from the left, i.e., from the lower end
        end;

        TExifData = record
          Make,
          Model,
          Software,
          DateTime,
          Artist: string;
          Orientation: smallint;
          XResolution,
          YResolution: double;
          ResolutionUnit: smallint;
          ExposureTime: double;
          FNumber: double;
          ColorSpace: Word;
          ImageUniqueID: string;
        end;

      var
        FBA: Word;

      function Swap(w: Word): Word; overload;
      function Swap(dw: DWord): DWord; overload;
    public
      FIFD: array of TIFDEntry;
      FExif: TExifData;
    public
      constructor Create(const FileName: string); overload;
      constructor Create(Stream: TFileStream); overload;

      function Orientation: smallint;
      function XResolution: double;
      function YResolution: double;
      function ResolutionUnit: string;
      function DateTime: TDateTime;
  end;


implementation

{ TJpegExifReader }

function TJpegExifReader.Swap(w: Word): Word;
begin
  if FBA = $4D4D then
    // Motorola align: first byte in data is highest byte (big endian)
    Result := BEtoN(w)
  else
    // Intel align: first byte in data is lowest byte (little endian)
    Result := LEtoN(w);
end;
function TJpegExifReader.Swap(dw: DWord): DWord;
begin
  if FBA = $4D4D then
    Result := BEtoN(dw)
  else
    Result := LEtoN(dw);
end;

constructor TJpegExifReader.Create(const FileName: string);
var
  Stream: TFileStream;
begin
  Stream := TFileStream.Create(FileName, fmOpenRead+fmShareDenyWrite);
  try
    Create(Stream);
  finally
    Stream.Free;
  end;
end;

constructor TJpegExifReader.Create(Stream: TFileStream);
type
  TRATIONAL=record
    numerator: dword;
    denominator: dword;
  end;

var
  b: Byte;
  w, Marker, Size: Word;
  dw: DWord;
  i,c: Integer;
  S: AnsiString;
  R: TRATIONAL;
  D: Double;

  function ReadIFD(Offset: DWord): integer;
  var i,j: integer;
  begin
    Stream.Position := 12 + Offset;
    Stream.Read(w, SizeOf(w));         // No of IFD entries
    Dec(Size, 2);
    Result := Swap(w);
    j := Length(FIFD);
    SetLength(FIFD, j+Result);
    // Read IFD entries
    for i:=j to j+Result-1 do begin
      Stream.Read(FIFD[i], SizeOf(TIFDEntry));
      Dec(Size, SizeOf(TIFDEntry));
      FIFD[i].TagNo    := Swap(FIFD[i].TagNo);
      FIFD[i].DataType := Swap(FIFD[i].DataType);
      FIFD[i].Count    := Swap(FIFD[i].Count);
    end;
    Stream.Read(dw, SizeOf(dw));       // Offset to next IFD (0=last)
  end;

begin
  inherited Create;

  Stream.Position := 0;
  Stream.Read(w,SizeOf(w));
  if w <> NtoLE($D8FF) then Exit;      // 0-1: Jpeg SOI (Start of image: FFD8)
  Stream.Read(Marker, SizeOf(w));      // 2-3: Application marker
  Stream.Read(Size, SizeOf(Size));     // 4-5: Size of APP1 data area (high byte first)
  Size := BEtoN(Size);
  Dec(Size, 2);
  Stream.Read(dw, SizeOf(dw));         // 6-9: 'Exif' of 'JFIF'
  Dec(Size, 4);
  // APP0 (Application marker: FFE0) and 'JFIF'
  if (Marker = NtoLE($E0FF)) and (dw = NtoLE($4649464A)) then
    begin
    Stream.Read(b, 1);                 // 10: 0
    Stream.Read(w, SizeOf(w));         // 11-12: JFIF version
    Stream.Read(b, 1);                 // 13: Density units (1-in, 2-px)
    FExif.ResolutionUnit := b+1;
    Stream.Read(w, SizeOf(w));         // 14-15: XDensity
    FExif.XResolution := BEtoN(w);
    Stream.Read(w, SizeOf(w));         // 16-17: YDensity
    FExif.YResolution := BEtoN(w);
    end
  // APP1 (Application marker: FFE1) and 'Exif'
  else if (Marker = NtoLE($E1FF)) and (dw = NtoLE($66697845)) then
    begin
    Stream.Read(w, SizeOf(w));
    if w <> NtoLE($0000) then Exit;    // 10-11: 0000
    Dec(Size, 2);
    // TIFF header
    Stream.Read(FBA, SizeOf(FBA));     // 12-13: byte order
    if (FBA<>$4949) and (FBA<>$4D4D) then Exit; // 4949=Intel, 4D4D=Motorola
    Stream.Read(w, SizeOf(w));
    if Swap(w) <> $002A then Exit;     // Tag Mark
    Stream.Read(dw, SizeOf(dw));       // Offset to first IFD (usualy 8)
    Dec(Size, 8);

    // IFD0: Image file directory (main image)
    ReadIFD(8);

    // Parse IFD entries
    i := 0;
    while i < Length(FIFD) do begin
      case FIFD[i].DataType of
        1: w:=1; // unsigned byte
        2: w:=1; // ascii string (terminated with 0)
        3: w:=2; // unsigned short (2 bytes)
        4: w:=4; // unsigned long (4 bytes)
        5: w:=8; // unsigned rational (4+4 long)
        else w:=1;
      end;
      c := w * FIFD[i].Count;

      if c > 4 then begin
        // Value contains offset from TIFF header to data
        Stream.Position := 12 + Swap(FIFD[i].Value);
        case FIFD[i].DataType of
          2: begin
             Dec(c);
             SetLength(S, c);
             Stream.Read(S[1], c);
             end;
          5: begin
             Stream.Read(R, SizeOf(R));
             D := Swap(R.numerator) / Swap(R.denominator);
             end;
        end;
      end
      else
        case FIFD[i].DataType of
          3: w := Swap(Word(FIFD[i].Value));
          4: dw := Swap(FIFD[i].Value);
        end;

      case FIFD[i].TagNo of
        // IFD0 Tags
        $010F: FExif.Make := S;
        $0110: FExif.Model := S;
        $0112: FExif.Orientation := w;
        $011A: FExif.XResolution := D;
        $011B: FExif.YResolution := D;
        $0128: FExif.ResolutionUnit := w;
        $0131: FExif.Software := S;
        $0132: FExif.DateTime := S;
        $013B: FExif.Artist := S;
        $8769: ReadIFD(dw); // offset to Exif SubIFD
        // Exif SubIFD Tags
        $829A: FExif.ExposureTime := D;
        $829D: FExif.FNumber := D;
        $A001: FExif.ColorSpace := w;
        $A420: FExif.ImageUniqueID := S;
      end;
      Inc(i);
    end;
  end;
end;

function TJpegExifReader.Orientation: smallint;
begin
  case FExif.Orientation of
    3:   Result := 180; // The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side.
    6:   Result := 270; // The 0th row is the visual right-hand side of the image, and the 0th column is the visual top.
    8:   Result := 90;  // The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom.
    else Result := 0;
  end;
end;

function TJpegExifReader.XResolution: double;
begin
  Result := FExif.XResolution;
end;
function TJpegExifReader.YResolution: double;
begin
  Result := FExif.YResolution;
end;
function TJpegExifReader.ResolutionUnit: string;
begin
  case FExif.ResolutionUnit of
    2:   Result := 'in';
    3:   Result := 'cm';
    else Result := '';
  end;
end;

function TJpegExifReader.DateTime: TDateTime;
begin
  if Length(FExif.DateTime) = 19 then
    Result := ComposeDateTime(
              StrToDate(Copy(FExif.DateTime,1,10), 'yyyy:mm:dd', ':'),
              StrToTime(Copy(FExif.DateTime,12,8), ':')
              )
  else
    Result := 0;
end;

end.



More information about the Lazarus mailing list