Jump to content
aehimself

TDBImage descendant

Recommended Posts

Hello,

 

I'm in the process of fixing some unusual amount of memory leaks and code errors in a legacy codebase. We have a custom TDBImage descendant, which was leaking TJPEGImages like there's no tomorrow.

 

The code used to look like this:

constructor TMyDBImage.Create(AOwner: TComponent);
begin
 […]
  Picture.Graphic := TJPEGImage.Create;
 […]
end;

procedure TMyDBImage.DataChange(Sender: TObject);
begin
  Picture.Graphic := TJPEGImage.Create;
  FPictureLoaded := False;
  if FAutoDisplay and
    (DataSource.DataSet.State <> dsInactive) then
    LoadPicture;
end;

procedure TMyDBImage.LoadPicture(ARefreshDataSet: Boolean = False);
begin
 […]
if not DataSource.DataSet.EOF and DataLink.Field.IsBlob then
    Picture.Graphic.Assign(DataLink.Field)
  else
    Picture.Graphic := TJPEGImage.Create;  // Empty picture
end;

I guess they wanted to clear the image, so I changed all those TJPEGImage.Create-s with a simple nil assignments (Picture.Graphic := nil). I also changed the loading mechanism:

procedure TMyDBImage.LoadPicture(ARefreshDataSet: Boolean = False);
Var
 jpeg: TJPEGImage;
begin
  [...]

  if not DataSource.DataSet.EOF and DataLink.Field.IsBlob then
    Begin
      jpeg := TJPEGImage.Create;
      Try
       jpeg.Assign(DataLink.Field);
       Picture.Assign(jpeg);
//       Picture.Bitmap.Assign(jpeg);  This does not work either
      Finally
       jpeg.Free;
      End;
    End
  else
    Picture.Graphic := nil;  // Empty picture
end;

The leaks are gone, but the picture is not shown in the component. I already confirmed that "jpeg" is filled correctly (stop with debugger, execute a .SaveToFile and check the result) but I can not really make the DBimage's Picture to load it. Based on my previous codes the Picture.Bitmap.Assign should be fine; I guess in this case it's the nil assignment of the .Graphic property which is not letting it to work?

 

Where is my logic wrong?

 

Thanks!

Share this post


Link to post

Yep. As I'm .nil-ing that, it'd throw an AV. The loading has been re-written, and is now:

Picture.Assign(jpeg);

Share this post


Link to post

Ah, I see, but, Picture.Assign in this case calls TGraphic.Assign(Source) where source is your "jpeg", and it assigns by reference, so you must not free it, it will be free'd on reassigning or destroying the component. As far as I can see in the sources.

Share this post


Link to post

Good idea. Commented the .Free out:

      jpeg := TJPEGImage.Create;
      Try
       jpeg.Assign(DataLink.Field);
       Picture.Assign(jpeg);
      Finally
//       jpeg.Free;
      End;

Bad thing is, it still does not work (picture isn't shown) and it leaks a TBitmapImage, a TJPEGImage, a TBitmap, a TJPEGData and a TMemoryStream object upon destruction 😞

 

Share this post


Link to post
1 hour ago, aehimself said:

Picture.Graphic := TJPEGImage.Create;

You should be aware that assigning to TPicture.Graphic will create an internal copy of the source (see TPicture.SetGraphic), so you get a memory leak with the above code.

As TPicture.Assign also uses SetGraphic internally, the above ist still valid for that one.

 

Not refreshing the display may have a different cause.

Share this post


Link to post
1 hour ago, Uwe Raabe said:

You should be aware that assigning to TPicture.Graphic will create an internal copy of the source (see TPicture.SetGraphic), so you get a memory leak with the above code.

Which the TPicture.Graphic help also clearly state:

Quote
Note: When assigning the Graphic property, TPicture assigns the properties of a another TGraphic object. It does not take ownership of the specified object.

...and the TPicture.Assign help is also pretty clear:

Quote

Copies one object to another by copying the contents of that object to the other.

 

Share this post


Link to post
1 hour ago, Uwe Raabe said:

You should be aware that assigning to TPicture.Graphic will create an internal copy of the source (see TPicture.SetGraphic), so you get a memory leak with the above code.

I know. This is why I am attempting to get rid of it.

 

1 hour ago, Uwe Raabe said:

Not refreshing the display may have a different cause.

Well, it is working if we are leaking TJPEGImages, which I find really strange. Also attempting to Self.Invalidate / Self.Repaint / Self.Refresh after loading the picture makes no difference.

Share this post


Link to post

If any future visitor is wondering, a simple

 Picture.Assign(DataLink.Field);

solved the problem, there was no need for a middle TJPEGImage. It's still a mystery for me though, why it is not working with a TJPEGImage and working without. Especially since they were all loaded correctly.

 

Especially since the above works fine with a normal TImage, but not from inside a TDBImage descendant.

  • Like 1

Share this post


Link to post
2 hours ago, aehimself said:

It's still a mystery for me though, why it is not working with a TJPEGImage and working without. Especially since they were all loaded correctly.

I think i saw something similar in the past, but didn't to throw some wild guess, now after you solved this, can you try to call DIBNeeded to enforce the generation of internal bitmap, may be something is wrong in one of those assign(s), one seems to be skipping the bitmap assigning, can you confirm ? of course if you are willing to waste some time on that, for public knowledge 🙂

Share this post


Link to post

@Kas Ob. Often the experience gathered during the journey is more valuable than reaching your destination. Noone will actually see that I worked on this component, but I'm richer with a bit of knowledge. Yes, I would be more than willing to check your idea, however...

 

Today, when I restored the faulty TJPEGImage assignments, it simply started to work...?! Noone else touched this code, the image is the same. Yesterday it was an empty white rectangle, today the image shows up...

  • Like 1

Share this post


Link to post
42 minutes ago, aehimself said:

Today, when I restored the faulty TJPEGImage assignments, it simply started to work...?! Noone else touched this code, the image is the same. Yesterday it was an empty white rectangle, today the image shows up...

I saw this behaviour few times, it does happen with PNG and virtual image lists, couldn't track it to specific reason.

Share this post


Link to post
1 hour ago, Kas Ob. said:

I saw this behaviour few times, it does happen with PNG and virtual image lists, couldn't track it to specific reason.

This makes no sense to me. I was in the belief that a code either works or not. Unless if it's a problem with TDBImage and/or VCL of course; but that is out of my reach anyway.

If I can manage to reproduce the issue somehow, I will attempt to fix it with DIBNeeded and will not forget to share the result.

  • Like 1

Share this post


Link to post
11 minutes ago, aehimself said:

This makes no sense to me.

True, yet there is the OS intraction with its messages, this for me it was more reproducible on Windows 8, later it was harder to reproduce in 8.1 so may be still something is missing in Windows 10, 

How to reproduce: leave your Delphi built application running and switch users on Windows, if the application is doing redraw images when you are on different user ( like loading compressed from resources or files then update the UI), it might happen and you see that the images are blank sqaures, i saw that with PNG files.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×