Recent

Author Topic: [SOLVED] 2 problems with this procedure to rotate a Bitmap by 90/180/270°  (Read 1130 times)

Hartmut

  • Hero Member
  • *****
  • Posts: 855
I am looking for a procedure to rotate a Bitmap by 90/180/270°. In this forum I found such a procedure made by wp in reply #16 of https://forum.lazarus.freepascal.org/index.php/topic,63277.0.html with a small improvement in reply #19.

On Windows it works perfectly, but on Linux I face 2 problems:
a) when I rotate by 180° and 'ABitmap.Width' is not divisible by 8, then the result is an empty (black) Bitmap.
b) when I rotate by 90° or 270° and 'ABitmap.Height' is not divisible by 8, then the result is an empty (black) Bitmap.

I attached a compilable project to test this:

Code: Pascal  [Select][+][-]
  1. program project1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6.  {$IFDEF UNIX}{$IFDEF UseCThreads}
  7.  cthreads,
  8.  {$ENDIF}{$ENDIF}
  9.  Interfaces, // this includes the LCL widgetset
  10.  Forms, // Unit1,
  11.  { you can add units after this }
  12.  sysutils, Graphics, LCLType, IntfGraphics;
  13. {$R *.res}
  14.  
  15. type
  16.   TImgRotation = (
  17.     irError, irNormal, irMirrorHor, irRotate180, irMirrorVert,
  18.     irMirrorHorRot270, irRotate90, irMirrorHorRot90, irRotate270
  19.   );  // all angle are clockwise
  20.  
  21. procedure RotateBitmap(const ABitmap: TBitmap; Angle: TImgRotation);
  22. Var
  23.   bmp: TBitmap;
  24.   srcImg, dstImg: TLazIntfImage;
  25.   imgHandle, imgMaskHandle: HBitmap;
  26.   i, j: integer;
  27.   w1, h1: Integer;  // Input bitmap width and height diminished by 1
  28. Begin
  29.   Assert(ABitmap <> nil, 'RotateBitmap: Input bitmap is expected not to be nil.');
  30.  
  31.   if (Angle = irError) or (Angle = irNormal) then
  32.     exit;
  33.  
  34.   w1 := ABitmap.Width - 1;
  35.   h1 := ABitmap.Height - 1;
  36.   srcImg := TLazIntfImage.Create(0, 0);
  37.   try
  38.     srcImg.LoadFromBitmap(ABitmap.Handle, ABitmap.MaskHandle);
  39.     bmp := TBitmap.Create;
  40.     try
  41.       bmp.PixelFormat := pf32Bit; // added due to reply #19
  42.       dstImg := TLazIntfImage.Create(0, 0);
  43.       try
  44.         if Angle in [irRotate90, irRotate270, irMirrorHorRot90, irMirrorHorRot270] then
  45.         begin
  46.           bmp.SetSize(ABitmap.Height, ABitmap.Width);
  47.           dstImg.LoadFromBitmap(bmp.Handle, bmp.MaskHandle);
  48.           case Angle of
  49.             irRotate90:
  50.               for i:=0 to w1 do
  51.                 for j:=0 to h1 do
  52.                   dstImg.Colors[h1-j, i] := srcImg.Colors[i, j];
  53.             irRotate270:
  54.               for i:=0 to w1 do
  55.                 for j:=0 to h1 do
  56.                   dstImg.Colors[j, w1-i] := srcImg.Colors[i, j];
  57.             irMirrorHorRot90:
  58.               for i:=0 to w1 do
  59.                 for j:=0 to h1 do
  60.                   dstImg.Colors[h1-j, w1-i] := srcImg.Colors[i, j];
  61.             irMirrorHorRot270:
  62.               for i:=0 to w1 do
  63.                 for j:=0 to h1 do
  64.                   dstImg.Colors[j, i] := srcImg.Colors[i, j];
  65.           end;
  66.         end else
  67.         if Angle in [irRotate180, irMirrorHor, irMirrorVert] then
  68.         begin
  69.           bmp.SetSize(ABitmap.Width, ABitmap.Height);
  70.           dstImg.LoadFromBitmap(bmp.Handle, bmp.MaskHandle);
  71.           case Angle of
  72.             irRotate180:
  73.               for i:=0 to w1 do
  74.                 for j:=0 to h1 do
  75.                   dstImg.Colors[w1-i, h1-j] := srcImg.Colors[i, j];
  76.             irMirrorHor:
  77.               for j:=0 to h1 do
  78.                 for i:=0 to w1 do
  79.                   dstImg.Colors[w1-i, j] := srcImg.Colors[i, j];
  80.             irMirrorVert:
  81.               for i:=0 to w1 do
  82.                 for j:=0 to h1 do
  83.                   dstImg.Colors[i, h1-j] := srcImg.Colors[i, j];
  84.           end;
  85.         end;
  86.         dstImg.CreateBitmaps(imgHandle, imgMaskHandle, false);
  87.         bmp.Handle := ImgHandle;
  88.         bmp.MaskHandle := ImgMaskHandle;
  89.       finally
  90.         dstImg.Free;
  91.       end;
  92.       ABitmap.Assign(bmp);
  93.     finally
  94.       bmp.Free;
  95.     end;
  96.   finally
  97.     srcImg.Free;
  98.   end;
  99. end; {RotateBitmap}
  100.  
  101. procedure convert(fspecIn,fspecOut: string; rot: TImgRotation);
  102.    {converts Graphic file 'fspecIn' to 'fspecOut' and does rotation by 'rot'}
  103.    var PT: TPicture;
  104.    begin
  105.    PT:=TPicture.Create;
  106.    PT.LoadFromFile(fspecIn);
  107.    RotateBitmap(PT.Bitmap,rot);
  108.    PT.SaveToFile(fspecOut);
  109.    PT.Free;
  110.    writeln('done');
  111.    end; {convert}
  112.  
  113. begin {main}
  114. if ParamCount <> 2 then
  115.    begin
  116.    writeln('Usage: <inputfile> <outputfile>');
  117.    exit;
  118.    end;
  119.  
  120. if not FileExists(ParamStr(1)) then
  121.    begin
  122.    writeln('Inputfile not found!');
  123.    exit;
  124.    end;
  125.  
  126. convert(ParamStr(1),ParamStr(2),irRotate180); // use 90/180/270°
  127. end.

And I attached 3 demo files:
 - one has 400x400 pixels and rotate by 90/180/270° works perfectly.
 - one has 399x400 pixels and rotate by 180° results is an empty (black) Bitmap.
 - one has 400x399 pixels and rotate by 90 or 270° results is an empty (black) Bitmap.

Versions:
 - Windows 7 (32-bit) with Lazarus 2.0.10
 - Linux Ubuntu 22.04 (64-bit) with Lazarus 3.4.0 (same results with Lazarus 2.0.10)

I'm still a beginner to Graphics. Can please somebody help to fix this problem? Thanks in advance.
« Last Edit: November 16, 2024, 11:47:00 am by Hartmut »

Hartmut

  • Hero Member
  • *****
  • Posts: 855
Re: 2 problems with this procedure to rotate a Bitmap by 90/180/270°
« Reply #1 on: November 14, 2024, 06:39:00 pm »
In reply #7 from VTwin in https://forum.lazarus.freepascal.org/index.php/topic,8282.0.html I found another procedure to rotate a Bitmap:

Code: Pascal  [Select][+][-]
  1. procedure RotateBitmap90(const bitmap: TBitmap);
  2.    {rotates a Bitmap 90° to the left}
  3. var
  4.   tmp: TBitmap;
  5.   src, dst: TLazIntfImage;
  6.   ImgHandle, ImgMaskHandle: HBitmap;
  7.   i, j, t, u, v: integer;
  8. begin
  9.   tmp := TBitmap.create;
  10.   tmp.Width := Bitmap.Height;
  11.   tmp.Height := Bitmap.Width;
  12.  
  13.   dst := TLazIntfImage.Create(0, 0);
  14.   dst.LoadFromBitmap(tmp.Handle, tmp.MaskHandle);
  15.  
  16.   src := TLazIntfImage.Create(0, 0);
  17.   src.LoadFromBitmap(bitmap.Handle, bitmap.MaskHandle);
  18.  
  19.   u := bitmap.width - 1;
  20.   v := bitmap.height - 1;
  21.  
  22.   for i := 0 to u do begin
  23.     t := u - i;
  24.     for j := 0 to v do
  25.       dst.Colors[j, t] := src.Colors[i, j];
  26.   end;
  27.  
  28.   dst.CreateBitmaps(ImgHandle, ImgMaskHandle, false);
  29.   tmp.Handle := ImgHandle;
  30.   tmp.MaskHandle := ImgMaskHandle;
  31.   dst.Free;
  32.   bitmap.Assign(tmp);
  33.   tmp.Free;
  34.   src.Free;
  35. end;

I tested this new procedure and it does not have my problem, that the result is an empty (black) Bitmap, when 'Bitmap.Height' is not divisible by 8. But it has another problem: with this new procedure a lot of picture files become a wrong background color.

I compared this new procedure with the old one from wp in my 1st post and the only real difference I found, is this line which only exists in the 1st procedure:
Code: Pascal  [Select][+][-]
  1. bmp.PixelFormat := pf32Bit; // see line 41

I tested the procedure in my 1st post without this command and then it behaves identical to the new procedure:
 - it does not longer have the problem, that the result is an empty (black) Bitmap, when 'ABitmap.Height' is not divisible by 8
 - but it has the other problem: a lot of picture files become a wrong background color.

Again: the problems only occour on Linux, not on Windows. Does this new information help to fix this problem on Linux?

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11947
  • FPC developer.
Re: 2 problems with this procedure to rotate a Bitmap by 90/180/270°
« Reply #2 on: November 14, 2024, 09:45:29 pm »
I don't use tbitmap, so unfortunately can't exactly pinpoint where the problem is, but  it seems you copy over pixel data (color[]), but not other properties like transparency, bit depth etc. Probably the problem lies there.

OTOH, the linux only problem seems to indicate to your widgetset's LCL backend or the kit itself (GTK or QT)

On Windows bitmaps are defined as having a width that is divisible by 4. Maybe the Linux graphic system has native size or so
« Last Edit: November 14, 2024, 10:00:03 pm by marcov »

wp

  • Hero Member
  • *****
  • Posts: 12476
Re: 2 problems with this procedure to rotate a Bitmap by 90/180/270°
« Reply #3 on: November 15, 2024, 01:01:55 am »
This seems to be yet another gtk2 issue: I can confirm the issue only on Linux gtk2, but not on qt5 and qt6.

You should file a bugreport.

Hartmut

  • Hero Member
  • *****
  • Posts: 855
Re: 2 problems with this procedure to rotate a Bitmap by 90/180/270°
« Reply #4 on: November 15, 2024, 06:59:55 pm »
Thanks a lot to marcov and wp for your replies. I tested with qt5 and can confirm, that the issue there not exists. I will file a bug report. Unfortunately I cannot switch my whole project to qt5 (too many other problems).

On Windows bitmaps are defined as having a width that is divisible by 4. Maybe the Linux graphic system has native size or so

When I omit command 'bmp.PixelFormat := pf32Bit' (see line #41 in code) then the "size divisible by 8 problem" disappears. So from my point of view I don't see a Linux OS limitation (but I maybe wrong).

Quote
I don't use tbitmap, so unfortunately can't exactly pinpoint where the problem is, but  it seems you copy over pixel data (color[]), but not other properties like transparency, bit depth etc. Probably the problem lies there.

This sounds plausible. Does someone know how to copy other properties like transparency, bit depth etc.?

wp

  • Hero Member
  • *****
  • Posts: 12476
Re: 2 problems with this procedure to rotate a Bitmap by 90/180/270°
« Reply #5 on: November 15, 2024, 08:06:53 pm »
In PixelFormat pf32bit, every pixel consists of 4 bytes. For optimization purposes, I guess the width then is assumed to be divisible by 4.

If you can afford skipping the right- or left-most pixels you can truncate the image width to the next smallest multiple of 4, this should fix the issue with the code that you posted. Or you could round to the next larger mutliple of 4 and fill up with transparent pixels
« Last Edit: November 15, 2024, 08:08:56 pm by wp »

wp

  • Hero Member
  • *****
  • Posts: 12476
Re: 2 problems with this procedure to rotate a Bitmap by 90/180/270°
« Reply #6 on: November 15, 2024, 11:55:19 pm »
I think this is the correct way to make sure that the image width of the destination image is a multiple of 4: Set the LineEnd parameter of the DataDescription of the LazIntfImage to rileDWordboundary - this makes sure that each pixel row ends at a DWord (32bit) boundary; if the image width is too narrow zero bytes are added up to this limit.

The following (slightly more compact) procedure rotates both transparent and opaque images correctly in gtk2, as well as in gtk3, qt5, qt6 and win32/64 (cocoa untested, but is expected to work as well):
Code: Pascal  [Select][+][-]
  1. uses
  2.   GraphType, IntfGraphics, Graphics;
  3. procedure RotateBitmap(const ABitmap: TCustomBitmap; Angle: TImgRotation);
  4. Var
  5.   srcImg, dstImg: TLazIntfImage;
  6.   i, j: Integer;
  7.   dstWidth, dstHeight: integer;
  8.   descr: TRawImageDescription;
  9.   w1, h1: Integer;  // Input bitmap width and height diminished by 1
  10. Begin
  11.   Assert(ABitmap <> nil, 'RotateBitmap: Input bitmap is expected not to be nil.');
  12.  
  13.   if (Angle = irError) or (Angle = irNormal) then
  14.     exit;
  15.  
  16.   w1 := ABitmap.Width - 1;
  17.   h1 := ABitmap.Height - 1;
  18.   srcImg := ABitmap.CreateIntfImage;
  19.   try
  20.     if Angle in [irRotate90, irRotate270, irMirrorHorRot90, irMirrorHorRot270] then
  21.     begin
  22.       dstWidth := ABitmap.Height;
  23.       dstHeight := ABitmap.Width;
  24.     end else
  25.     begin
  26.       dstWidth := ABitmap.Width;
  27.       dstHeight := ABitmap.Height;
  28.     end;
  29.  
  30.     dstImg := TLazIntfImage.CreateCompatible(srcImg, dstWidth, dstHeight);
  31.     if (ABitmap.PixelFormat = pf32Bit) then
  32.     begin
  33.       descr := dstImg.DataDescription;
  34.       descr.LineEnd := rileDWordBoundary;
  35.       dstImg.DataDescription := descr;
  36.     end;
  37.     try
  38.       case Angle of
  39.         irRotate90:
  40.           for i:=0 to w1 do
  41.             for j:=0 to h1 do
  42.               dstImg.Colors[h1-j, i] := srcImg.Colors[i, j];
  43.         irRotate270:
  44.           for i:=0 to w1 do
  45.             for j:=0 to h1 do
  46.               dstImg.Colors[j, w1-i] := srcImg.Colors[i, j];
  47.         irMirrorHorRot90:
  48.           for i:=0 to w1 do
  49.             for j:=0 to h1 do
  50.               dstImg.Colors[h1-j, w1-i] := srcImg.Colors[i, j];
  51.         irMirrorHorRot270:
  52.           for i:=0 to w1 do
  53.             for j:=0 to h1 do
  54.               dstImg.Colors[j, i] := srcImg.Colors[i, j];
  55.         irRotate180:
  56.           for i:=0 to w1 do
  57.             for j:=0 to h1 do
  58.               dstImg.Colors[w1-i, h1-j] := srcImg.Colors[i, j];
  59.         irMirrorHor:
  60.           for j:=0 to h1 do
  61.             for i:=0 to w1 do
  62.               dstImg.Colors[w1-i, j] := srcImg.Colors[i, j];
  63.         irMirrorVert:
  64.           for i:=0 to w1 do
  65.             for j:=0 to h1 do
  66.               dstImg.Colors[i, h1-j] := srcImg.Colors[i, j];
  67.       end;
  68.       ABitmap.LoadFromIntfImage(dstImg);
  69.     finally
  70.       dstImg.Free;
  71.     end;
  72.   finally
  73.     srcImg.Free;
  74.   end;
  75. end; {RotateBitmap}

Added this code to the wiki: https://wiki.freepascal.org/Developing_with_Graphics#Rotating_and_mirroring_a_bitmap
« Last Edit: November 16, 2024, 12:09:40 am by wp »

Hartmut

  • Hero Member
  • *****
  • Posts: 855
Re: 2 problems with this procedure to rotate a Bitmap by 90/180/270°
« Reply #7 on: November 16, 2024, 11:46:28 am »
Great!! Now it's the ultimative procedure for rotating and mirroring a Bitmap! I tested it with many picture files on Windows and Linux (gtk2 and qt5) and always got perfect results. And I'm really happy, that the size of the Bitmap itself is never rounded up or down, if it is not a multiple of 8 or 4. So this magic appears only inside 'dstImg'.

Again I'm impressed what you all know. Thank you so much!

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 11947
  • FPC developer.
Re: 2 problems with this procedure to rotate a Bitmap by 90/180/270°
« Reply #8 on: November 16, 2024, 10:35:10 pm »
On Windows bitmaps are defined as having a width that is divisible by 4. Maybe the Linux graphic system has native size or so

Correction, bytes per horizontal line divisible by 4, not pixels per horizontal line.

 

TinyPortal © 2005-2018