View Issue Details

IDProjectCategoryView StatusLast Update
0035553LazarusLCLpublic2020-04-17 19:28
ReporterAnthony Walter Assigned To 
PrioritynormalSeverityminorReproducibilityhave not tried
Status newResolutionopen 
PlatformLinuxOSUbuntu 
Product Version2.0.2 
Summary0035553: TPortableNetworkGraphic ScanLine property does not correctly work with certain image sizes, also R and B pizels are sometimes s
DescriptionWhen developing my Image Shop example program I have encountered the following bugs with TPortableNetworkGraphic property on Linux using the Gtk2 widgetset.

If you attempt to write pixel values to a 32 bpp TPortableNetworkGraphic on Linux 64 bit with the gtk2 toolkit the colors of the image are corrupted if the image width falls within certain values. In the "Steps To Reproduce" is an example program demonstrating the problem.

When the Width is set to a value of 400, the image appears as fuschia image and is correct.

When the Width is set to a value of 300, the image appears as a series of vertical bars.

See attached screen shot.

https://cache.getlazarus.org/images/png_error.png

Additionally, the red, green, blue and alpha components occasionally swap red and blue components. This problem occurs on various systems without any reason. Sometimes even changing red and blue values on the same computer with no changes to code. This problem can occur without accessing the ScanLine property. Sometimes simple loading and drawing a TPortableNetworkGraphic shows an image with the red and blue components visually reversed.
Steps To Reproduceunit Unit1;

{$mode delphi}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs;

{ TForm1 }

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormPaint(Sender: TObject);
  private
    FImage: TPortableNetworkGraphic;
  public

  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

type
  TPixel = record B, G, R, A: Byte; end;
  PPixel = ^TPixel;

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
var
  A: PPixel;
  I: Integer;
begin
  FImage := TPortableNetworkGraphic.Create;
  FImage.Width := 300; // works correctly if Width is 400
  FImage.Height := 300;
  FImage.PixelFormat := pf32bit;
  A := FImage.ScanLine[0];
  for I := 1 to FImage.Width * FImage.Height do
  begin
    A.R := $FF;
    A.G := 0;
    A.B := $FF;
    A.A := $FF;
    Inc(A);
  end;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FImage.Free;
end;

procedure TForm1.FormPaint(Sender: TObject);
begin
  Canvas.Draw(0, 0, FImage);
end;

end.
TagsNo tags attached.
Fixed in Revision
LazTarget
WidgetsetGTK 2
Attached Files

Activities

Anthony Walter

2019-05-10 04:00

reporter  

png_error.png (13,214 bytes)   
png_error.png (13,214 bytes)   

Anthony Walter

2019-05-10 07:04

reporter   ~0116109

Okay I am not sure how to add a related issue, but I've tracked down the problem and it's being caused by this fix:

https://bugs.freepascal.org/view.php?id=8553

And here is the relevant code from line 358 in gtk2lclintf.inc:

          // gtkPixbuf doesn't like invalid dataSize/MaskSize < 32. issue 0008553.
          if (ARawImage.MaskSize > 0) and (ImgDepth = 32) then
          begin
            // seem that gdkPixbuf does not like many of our masks
            ADivResult := 0;
            ARemainder := 0;
            DivMod(ARawImage.DataSize, ARawImage.MaskSize, ADivResult, ARemainder);
            CreateWithAlpha := ARemainder = 0;
            {$IFDEF VerboseRawImage}
            if not CreateWithAlpha then
              DebugLn('TGtk2WidgetSet.CreateBitmapFromRawImage B WARNING: This image have invalid DataSize / MaskSize.');
            {$ENDIF}
          end;

So it would seem certain width have a ARemainder other than 0 causing the underlying platform object created a few lines later at gdk_pixbuf_new_from_data to ignore the alpha channel and treat a 32bit image as a 24 bit image. Essentially losing 1 byte for every pixel passed in each successive pixel. I am not sure what the code is trying to do with the whole ARemainder thing, but it's obviously not working as it should.

If I change this line:

CreateWithAlpha := True; // ARemainder = 0;

The problem is fixed, but likely breaks whatever someone tried to fix with issue 0008553. Can someone tell me how to add related tags in this new mantis interface and repopen the old issue? Also if anyone who touched on this section of code before could take a look at the problem they might have a better idea why ARemainder is needed with 32 bpp images.

Finally, this might possibly be related to the red and blue channel swap, but then again it might not be. It could be a separate problem.

Anthony Walter

2019-05-10 07:47

reporter   ~0116110

After a bit more examination it would seem that this issue is related to a fix for gtk2 and masks. I don't want to go any further with my investigation as my thoughts are a bit extreme so I'll leave you with these questions.

Do we need image masks if the image is 32 bpp?

Why isn't TRasterImage.GetMasked being used when to signal ASkipMask in any calls to this function?
  code: function RawImage_CreateBitmaps(const ARawImage: TRawImage; out ABitmap, AMask: HBitmap; ASkipMask: Boolean): Boolean;

If ASkipMask was passed properly in the chain of create bitmap calls then line 358 in gtk2lclintf.inc could be changed to:
  code: if (not ASkipMask) and (ARawImage.MaskSize > 0) and (ImgDepth = 32) then

Which fixes the problem. But I hesitate to change code in TRasterImage which is not using GetMasked in setting the ASkipMask argument for all platforms. Someone other than me should look into that. Until then I would assume that 32 bpp image are not masked in the gtk2 (it doesn't make sense to me to mask a 32 bpp image), set ASkipMask to True in that case and remove fix 8553.

Anton Kavalenka

2020-04-17 19:28

reporter   ~0122210

In general case it is wrong. Gdk pixbuf require stride length rounded to 32bit.

A := FImage.ScanLine[0];
 for I := 1 to FImage.Width * FImage.Height do
  begin
    A.R := $FF;
    A.G := 0;
    A.B := $FF;
    A.A := $FF;
    Inc(A);
  end;

i.e. each line begins with address aligned to dword.
So for each line the FImage.ScanLine[i] should be retrieved separately.

Issue History

Date Modified Username Field Change
2019-05-10 04:00 Anthony Walter New Issue
2019-05-10 04:00 Anthony Walter File Added: png_error.png
2019-05-10 07:04 Anthony Walter Note Added: 0116109
2019-05-10 07:47 Anthony Walter Note Added: 0116110
2020-04-17 19:28 Anton Kavalenka Note Added: 0122210