Recent

Author Topic: TImage loading jpg files  (Read 289 times)

msimeon

  • New member
  • *
  • Posts: 18
TImage loading jpg files
« on: August 05, 2019, 05:20:07 pm »
I have an application that displays images by loading them from file:
    Image2: TImage;
options: Proportional=true; Stretch in and out enabled; Strech enabled does not make a difference;
...
   Image2.Picture.LoadFromFile(dirname+'\'+selectedName);

Some images in portrait shape load correctly, others are rotated. When I resize an image that was getting rotated then the resized copy loads Ok (I am using ACDSee Photo Manager 12), even if I did not actually change the size.

I tried using the package "Images For Lazarus" and TJPGImage, same problem !

Help most welcome


wp

  • Hero Member
  • *****
  • Posts: 5992
Re: TImage loading jpg files
« Reply #1 on: August 05, 2019, 06:03:34 pm »
This issue is in the images. When I take pictures on my smart phone some of them are upside down because I hold the phone the wrong way. Looking at these images in my EXIF viewer (https://sourceforge.net/p/lazarus-ccr/svn/HEAD/tree/components/fpexif/examples/metadata_viewer/) I see that that the EXIF tag "Orientation" has the value "Rotate 180"; images with "upright" orientation have the value "Horizontal (normal)" here. So, in order to correctly detect the orientation your image viewer must read the Orientation tag in the EXIF segment (primary image directory) and rotate the image accordingly. I remember that there once was a discussion about lossless jpg rotation.
Lazarus trunk / fpc 3.0.4 / all 32-bit on Win-10

msimeon

  • New member
  • *
  • Posts: 18
Re: TImage loading jpg files
« Reply #2 on: August 05, 2019, 06:35:52 pm »
Tank you.  I'll get an exif viewer and check some of my files !

wp

  • Hero Member
  • *****
  • Posts: 5992
Re: TImage loading jpg files
« Reply #3 on: August 05, 2019, 07:08:38 pm »
Just put together the following function "DetectImageRotation" which scans the EXIF segment for the Orientation tag and returns its value (working, but not carefully tested):
Code: Pascal  [Select]
  1. type
  2.   TExifOrientation = (
  3.     eoError, eoNormal, eoMirrorHor, eoRotate180, eoMirrorVert,
  4.     eoMirrorHorRot270, eoRotate90, eoMirrorHorRot90, eoRotate270
  5.   );  // all angle are clockwise  
  6.  
  7. function DetectImageRotation(AStream: TStream): TExifOrientation;
  8. type
  9.   TIFDRecord = packed record
  10.     TagID: Word;
  11.     DataType: Word;
  12.     DataCount: DWord;
  13.     DataValue: DWord;
  14.   end;
  15.   TTiffHeader = packed record
  16.     BOM: Array[0..1] of AnsiChar;   // 'II' for little endian, 'MM' for big endian
  17.     Signature: Word;   // Signature (42)
  18.     IFDOffset: DWord;  // Offset where image data begin, from start of TIFF header
  19.   end;
  20.  
  21. const
  22.   EXIF_SIGNATURE: array[0..5] of AnsiChar = ('E', 'x', 'i', 'f', #0, #0);
  23.   LITTLE_ENDIAN_BOM: array[0..1] of AnsiChar = ('I', 'I');
  24.   BIG_ENDIAN_BOM: array[0..1] of AnsiChar = ('M', 'M');
  25. var
  26.   bigEndian: Boolean;
  27.  
  28.   function FixEndian16(AValue: Word): Word;
  29.   begin
  30.     if bigEndian then
  31.       Result := BEtoN(AValue)
  32.     else
  33.       Result := LEtoN(AValue);
  34.   end;
  35.  
  36.   function FixEndian32(AValue: DWord): DWord;
  37.   begin
  38.     if bigEndian then
  39.       Result := BEtoN(AValue)
  40.     else
  41.       Result := LEtoN(AValue);
  42.   end;
  43.  
  44. var
  45.   p0, p: Int64;
  46.   streamSize: Int64;
  47.   marker: byte;
  48.   size: Word;
  49.   exif_hdr: array [0..5] of ansichar;
  50.   tiffhdr_start: Int64;
  51.   tiff_hdr: TTiffHeader;
  52.   numRecords: Word;
  53.   i: Integer;
  54.   ifdRec: TIFDRecord;
  55. begin
  56.   Result := eoError;
  57.  
  58.   if (LEToN(AStream.ReadWord) <> $D8FF) then
  59.     exit;
  60.  
  61.   streamsize := AStream.Size;
  62.   p0 := AStream.Position;
  63.   p := p0;
  64.  
  65.   while p < streamSize do begin
  66.     repeat
  67.       marker := AStream.ReadByte;
  68.     until marker <> $FF;
  69.     size := BEtoN(AStream.ReadWord) - 2;
  70.     p := AStream.Position;
  71.  
  72.     if marker = $E1 then begin  // EXIF marker
  73.       // Read EXIF header
  74.       AStream.Read(exif_hdr[0], SizeOf(exif_hdr));
  75.       if not CompareMem(@exif_hdr[0], @EXIF_SIGNATURE[0], SizeOf(exif_hdr)) then
  76.       begin
  77.         // No EXIF structure found
  78.         AStream.Position := p0;
  79.         exit;
  80.       end;
  81.  
  82.       // Read TIFF header
  83.       tiffhdr_start := AStream.Position;
  84.       AStream.Read(tiff_hdr, SizeOf(tiff_hdr));
  85.       if CompareMem(@tiff_hdr.BOM[0], @BIG_ENDIAN_BOM[0], SizeOf(BIG_ENDIAN_BOM)) then
  86.         bigEndian := true
  87.       else
  88.       if CompareMem(@tiff_hdr.BOM[0], @LITTLE_ENDIAN_BOM[0], SizeOf(LITTLE_ENDIAN_BOM)) then
  89.         bigEndian := false
  90.       else begin
  91.         // no valid TIFF header
  92.         AStream.Position := p0;
  93.         exit;
  94.       end;
  95.       tiff_hdr.Signature := FixEndian16(tiff_hdr.Signature);
  96.       if tiff_hdr.Signature <> 42 then begin
  97.         // no valid TIFF header
  98.         AStream.Position := p0;
  99.         exit;
  100.       end;
  101.  
  102.       // Determine where the first directory (IFD0) begins...
  103.       tiff_hdr.IFDOffset := FixEndian32(tiff_hdr.IFDOffset);
  104.       // ... and move stream to there.
  105.       AStream.Position := tiffhdr_start + tiff_hdr.IFDOffset;
  106.  
  107.       // Read count of IFD entries
  108.       numRecords := FixEndian16(AStream.ReadWord);
  109.  
  110.       // Read EXIF records from IFD0
  111.       for i := 0 to numRecords-1 do begin
  112.         AStream.Read(ifdRec, SizeOf(ifdRec));
  113.         ifdRec.TagID := FixEndian16(ifdRec.TagID);
  114.         // Found Orientation tag
  115.         if ifdRec.TagID = $0112 then begin
  116.           ifdRec.DataValue := FixEndian32(ifdRec.DataValue);
  117.           Result := TExifOrientation(ifdRec.DataValue);
  118.           AStream.Position := p0;
  119.           exit;
  120.         end;
  121.       end;
  122.     end;
  123.     AStream.Position := p + size;
  124.   end;
  125. end;

[EDIT]
And if you are seeking code to rotate and flip images according to the found ExifOrientation you can find the related procedure "RotateBitmap()" in the fpexif MetadataViewer demo, unit https://sourceforge.net/p/lazarus-ccr/svn/HEAD/tree/components/fpexif/examples/metadata_viewer/mdvmain.pas
« Last Edit: August 06, 2019, 12:45:41 am by wp »
Lazarus trunk / fpc 3.0.4 / all 32-bit on Win-10

msimeon

  • New member
  • *
  • Posts: 18
Re: TImage loading jpg files
« Reply #4 on: August 06, 2019, 09:46:36 am »
Thank you again. Now I have to work on it !!!

msimeon

  • New member
  • *
  • Posts: 18
Re: TImage loading jpg files
« Reply #5 on: August 07, 2019, 09:23:43 am »
I have been using your comments and pointers to existing code to replace TImage.Picture.LoadFromFile by my own  loadFile(fileName,dirname :string; Image :TImage) that will test ImageOrientation in file info and if need be call RotateBitmap.

It works fine !
Thanks again