Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / lazarus / 2016-09

TImage shows loaded image rotated by 90?

9 messages · 4 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

LacaK

Hi,

I have JPEG image with width=2448 and height=3264.

All programs in Windows show image with respect to his dimensions, so shorter is X and higher is Y.

But when I load this image into TImage control (using Picture property) image is shown rotated by 90 degrees, with width=3264 and height=2448

Original image is on
http://moja.uschovna.zoznam.sk/link/c13c4eb8-c035-4879-879e-de04f8e9c13c

All what is needed for reproducing is put TImage on TForm and load picture ...

Why this happens ?

TIA

-Laco.

Reply by email

Marc Weustink
to LacaK
LacaK wrote
Hi, I have JPEG image with width=2448 and height=3264.

No

Show quoted text 5 lines
All programs in Windows show image with respect to his dimensions, so shorter is X and higher is Y. But when I load this image into TImage control (using Picture property) image is shown rotated by 90 degrees, with width=3264 and height=2448

That is correct. If you look at the exif info, you read:

Filename - 6.jpg
...
ImageWidth - 3264
ImageLength - 2448
...
ExifImageWidth - 3264
ExifImageHeight - 2448
...
Orientation - Right top

What you see is the result of "smart" image viewers, which read the exif info and read that you held the camera sideways and there fore rotate the image.
the image istself is still 3264x2448

Marc

Reply by email

LacaK
to Marc Weustink

Dňa 23.9.2016 o 14:47 Marc Weustink napísal(a):

Show quoted text 14 lines
LacaK wrote: > Hi, > > I have JPEG image with width=2448 and height=3264. No > All programs in Windows show image with respect to his dimensions, so > shorter is X and higher is Y. > > But when I load this image into TImage control (using Picture property) > image is shown rotated by 90 degrees, with width=3264 and height=2448 That is correct. If you look at the exif info, you read:

How do you read this Exif info ?
When I right click image and look at Details tab I see there only width=2448 and height=3264

But when I use on-line exif viewer (at http://regex.info/exif.cgi) there are swaped results and:
OrientationRotate = 90 CW

So it seems, that you are right ;-)

Next question then is: how can I detect this in Lazarus and do same as "smart viewers" do ?
(in other words: is there and component, which allows me read this EXIF information from JPEG image ?)

Thanks
-Laco.

Show quoted text 20 lines
Filename - 6.jpg ... ImageWidth - 3264 ImageLength - 2448 ... ExifImageWidth - 3264 ExifImageHeight - 2448 ... Orientation - Right top What you see is the result of "smart" image viewers, which read the exif info and read that you held the camera sideways and there fore rotate the image. the image istself is still 3264x2448 Marc

Reply by email

Marc Weustink
to LacaK
LacaK wrote 17 lines
Dňa 23.9.2016 o 14:47 Marc Weustink napísal(a): > LacaK wrote: >> Hi, >> >> I have JPEG image with width=2448 and height=3264. > > No > >> All programs in Windows show image with respect to his dimensions, so >> shorter is X and higher is Y. >> >> But when I load this image into TImage control (using Picture property) >> image is shown rotated by 90 degrees, with width=3264 and height=2448 > > That is correct. If you look at the exif info, you read: How do you read this Exif info ?

with a decent viewer ;) (in windows I use irfanview)

When I right click image and look at Details tab I see there only width=2448 and height=3264

I see too

Show quoted text 10 lines
But when I use on-line exif viewer (at http://regex.info/exif.cgi) there are swaped results and: OrientationRotate = 90 CW So it seems, that you are right ;-) Next question then is: how can I detect this in Lazarus and do same as "smart viewers" do ? (in other words: is there and component, which allows me read this EXIF information from JPEG image ?)

Google ?

Exif + Delphi -> torry.net -> NativeJpg v.1.33 or TExif v.1.3 or TGJPGCommentInfoP v.1.0

(looking for sources supporting D7)

you may try that.

Marc

Reply by email

LacaK
to Marc Weustink

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:

======================================================================== { 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: string;
           Orientation: smallint;
           XResolution,
           YResolution: double;
           ResolutionUnit: smallint;
         end;
       var
         FBA: Word;
       function Swap(w: Word): Word; overload;
     protected
       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;

{ 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;
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
   w, Size: word;
   dw: dword;
   n,i,c: integer;
   S: AnsiString;
   R: TRATIONAL;
   D: double;
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(w, SizeOf(w));
   if w  NtoLE($E1FF) then Exit;      // 2-3: APP1 (Application
marker: FFE1)
   Stream.Read(Size, SizeOf(Size));     // 4-5: Size of APP1 data area
(high byte first)
   Size := BEtoN(Size);
   Dec(Size, 2);
   // Exif
   Stream.Read(dw, SizeOf(dw));
   if dw  NtoLE($66697845) then Exit; // 6-9: 'Exif'
   Stream.Read(w, SizeOf(w));
   if w  NtoLE($0000) then Exit;      // 10-11: 0000
   Dec(Size, 6);
   // 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);
   // IFD: Image file directory
   SetLength(FIFD, 0);
     Stream.Read(w, SizeOf(w));         // No of IFD entries
     Dec(Size, 2);
     n := Swap(w);
     SetLength(FIFD, Length(FIFD)+n);
     // Read IFD entries
     for i:=0 to n-1 do begin
       Stream.Read(FIFD[i], SizeOf(TIFDEntry));
       Dec(Size, SizeOf(TIFDEntry));
     end;
     Stream.Read(dw, SizeOf(dw));       // Offset to next IFD (0=last)
   // Parse IFD entries
   for i:=0 to n-1 do begin
     case Swap(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 * Swap(FIFD[i].Count);
     if c > 4 then begin
       // Value contains offset from TIFF header to data
       Stream.Position := 12 + Swap(FIFD[i].Value);
       case Swap(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 := R.numerator / R.denominator;
            end;
       end;
     end
     else
       case Swap(FIFD[i].DataType) of
         3: w := Swap(FIFD[i].Value);
       end;
     case Swap(FIFD[i].TagNo) of
       $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;
       $8769: // offset to Exif SubIFD
     end;
   end;
end;
function TJpegExifReader.Orientation: smallint;
begin
   case FExif.Orientation of
     3:   Result := 180;
     6:   Result := 270; // The 0th row is the visual right-hand side of
the image, and the 0th column is the visual top.
     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
   Result := ComposeDateTime(
             StrToDate(Copy(FExif.DateTime,1,10), 'yyyy:mm:dd', ':'),
             StrToTime(Copy(FExif.DateTime,12,8), ':')
             );
end;

========================================================================

Show quoted text 45 lines
>>> I have JPEG image with width=2448 and height=3264. >> >> No >> >>> All programs in Windows show image with respect to his dimensions, so >>> shorter is X and higher is Y. >>> >>> But when I load this image into TImage control (using Picture >>> property) >>> image is shown rotated by 90 degrees, with width=3264 and height=2448 >> >> That is correct. If you look at the exif info, you read: > > How do you read this Exif info ? with a decent viewer ;) (in windows I use irfanview) > When I right click image and look at Details tab I see there only > width=2448 and height=3264 I see too > But when I use on-line exif viewer (at http://regex.info/exif.cgi) there > are swaped results and: > OrientationRotate = 90 CW > > So it seems, that you are right ;-) > > Next question then is: how can I detect this in Lazarus and do same as > "smart viewers" do ? > (in other words: is there and component, which allows me read this EXIF > information from JPEG image ?) Google ? Exif + Delphi -> torry.net -> NativeJpg v.1.33 or TExif v.1.3 or TGJPGCommentInfoP v.1.0 (looking for sources supporting D7) you may try that. Marc

Reply by email

Michael Van Canneyt
to LacaK
LacaK wrote 5 lines
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.

There is interest :)

Michael.

Reply by email

LacaK
to Michael Van Canneyt
Show quoted text 8 lines
> 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. There is interest :)

Hm, looking deeper into TFPCustomImageReader architecture intention of such readers is constitute TFPCustomImage.
Now I am not sure where metadata (Exif) should go ? (As far as TFPCustomImage holds only true image data)

I can extend TFPReaderJpeg, by adding boolean option ("ReadExif") either to constructor or as a standalone property, which will signal to reader if read also this kind of data.
If set to true then in "InternalRead" I can read from supplied stream and store them in record inside TFPReaderJpeg ... so user must ask reader later to obtain exif data

Or do not touch TReaderJpeg and add only new "helper" class which will read from supplied stream exif data (just as in source which I have attached to my prior mail)

What do you prefer ?

-Laco.

Reply by email

Thierry Andriamirado
to LacaK: I have created small Exif reader for my own needs. I have looked also in FCL…
LacaK wrote 10 lines
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:

This is great! Please do, if you have time.
Anyway, thanks for this code.

Thierry

Envoyé de mon téléphone Android avec K-9 Mail. Excusez la brièveté.

Reply by email

LacaK
to Thierry Andriamirado

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.

Show quoted text 10 lines
> 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.

Reply by email