0

On a windows 10 system with 16GB ram, my program (which uses under 500mb ram at most) randomly throws "Not enough memory resources are available to process this command" exceptions when decoding JPEG files (ones I created using code). The JPEG files are valid and load without issue when calling the same function again.

The JPEG loading code is run in a separate thread than the main UI and the exception is always in the same spot (LoadedImage.Height := gdiBitmap.GetHeight), here is the code (loads JPEG from Stream):

function LoadJPEGImageGDIPlus(lStream : TStream; LoadedImage : TBitmap; LoadEXIF : Boolean{$IFDEF LOCALTRACE}; ThreadID : WideString{$ENDIF}) : Boolean;
var
  gdiBitmap  : TGPBitmap;
  gfx        : TGPGraphics;
  I          : Integer;
  gdiStatus  : TStatus;
begin
  Result    := True;
  gfx       := nil;
  gdiBitmap := nil;

  LoadedImage.Canvas.Lock;

  Try
    gdiBitmap := TGPBitmap.Create(TStreamAdapter.Create(lStream));
    LoadedImage.PixelFormat := pf32bit;
    LoadedImage.Width  := gdiBitmap.GetWidth;
    LoadedImage.Height := gdiBitmap.GetHeight;

    I := 0;
    Repeat
      gfx := TGPGraphics.Create(LoadedImage.Canvas.Handle);
      gdiStatus := gfx.GetLastStatus;
      If gdiStatus <> ok then
      Begin
        FreeAndNil(gfx);
        Inc(I);
        Sleep(16);
      End;
    Until (gdiStatus = ok) or (I > 150);

    If gdiStatus = ok then
    Begin
      gdiStatus := gfx.DrawImage(gdiBitmap,0,0,LoadedImage.Width,LoadedImage.Height);
      If gdiStatus <> ok then
      Begin
        Result := False;
      End;
      FreeAndNil(gfx);
    End
      else
    Begin
      Result := False;
    End;
  Except
    on E : Exception do
    Begin
      Result := False;
    End
      else
    Begin
      Result := False;
    End;
  End;

  If gdiBitmap <> nil then FreeAndNil(gdiBitmap);
  If gfx <> nil then FreeAndNil(gfx);

  LoadedImage.Canvas.Unlock;
end;

Any idea how to resolve this issue, am I not using GDI+ correctly?

bLight
  • 803
  • 7
  • 23
  • 2
    (Off topic: Never do `if x <> nil then FreeAndNil(x)` because `FreeAndNil` does `Free` which does the non-`nil` check for you. Also: You must protect your resources with `try..finally` blocks. Assume every line can raise an exception unless you are absolutely sure it cannot.) – Andreas Rejbrand Aug 31 '21 at 13:12
  • Like I wrote, the app uses 500mb MAX, these JPEG images are under 1024x1024px in resolution, so I'm not even close to the memory limit for 32bit apps. – bLight Aug 31 '21 at 13:14
  • 3
    Memory might be fragmented. I mean, even if your flat has 30 unused square metres of floor space, you might still not be able to place a 30-square-metre rug there (unless you cut it into pieces using scissors). – Andreas Rejbrand Aug 31 '21 at 13:14
  • It may be an issue with `TStreamAdapter` not released. See [this](https://stackoverflow.com/questions/22846335/why-is-this-tstreamadapter-not-released). – Olivier Aug 31 '21 at 13:38
  • Wouldn't a TStreamAdapter not being released trigger a memory leak? I've used both FastMM and madExcept to rule out memory leaks. – bLight Aug 31 '21 at 13:42
  • 2
    TStreamAdapter is definitely not constructed correctly here, but if declaration of TGPBitmap.Create hasn't changed since D7, then it will accidentally work without leaking. Still it should be corrected to `TStreamAdapter.Create(lStream) as IStream` to ensure proper reference counting – Dalija Prasnikar Aug 31 '21 at 13:46
  • I cleaned up the trace code from the original post. What is better to do "gdiBitmap := TGPBitmap.Create(TStreamAdapter.Create(lStream) as IStream);" or to use "sAdapter := TStreamAdapter.Create(lStream); gdiBitmap := TGPBitmap.Create(sAdapter); sAdapter := nil" ? – bLight Aug 31 '21 at 13:56
  • If this is a memory fragmentation issue, what is the best solution to work around the issue? – bLight Aug 31 '21 at 14:44
  • BTW what's the point of that repeat/until loop? It looks weird. – Olivier Aug 31 '21 at 15:39
  • Oliver: To get around other random errors thrown by this function. – bLight Aug 31 '21 at 15:48
  • 1
    That's definitely not the right way to fix an issue. Also why are you using GDI+ in the first place? – Olivier Aug 31 '21 at 15:56
  • Oliver: I did some benchmarks and under win10, GDI+ seems to be the fastest and most compatible (supports most JPEG formats/colorspaces) JPEG decoder that works with Delphi 7. – bLight Aug 31 '21 at 16:04
  • You're not checking the status for `gdiBitmap`. If the loading fails, you get random values for `GetWidth` and `GetHeight`, which can easily trigger an out of memory exception if the product is too large. It would explain why the error occurs at `LoadedImage.Height := gdiBitmap.GetHeight;`. – Olivier Aug 31 '21 at 17:05
  • @Olivier Check how, "If gdiBitmap = nil"? shouldn't it trigger an exception on "TGPBitmap.Create" (it doesn't)? – bLight Aug 31 '21 at 17:18
  • 1
    `gdiBitmap.GetLastStatus`, just like you did for `gfx`. – Olivier Aug 31 '21 at 17:21
  • Putting the unstableSource aside: if i rember correct: There where 2 limiting Factors. Small allocated Images where placed in a special buffer. If this buffer is fulll your getting this error. Second Limit was something with GDI Enabled Handles. RIVA TNT 128 Cards had a notorious small Limit especially in Terminal Environments. We needed around 3000 GDI Handles for the Visual App Part and the GDI Handles where exhausted. We did get the same Error Message. (Happended on Windows 2000 and XP, dont know if win10 has still the same limits ) – Quelltextknecht Sep 01 '21 at 14:34
  • How are you creating and calling this function from threads? And how many images are you talking about? What are their sizes. It is quite possible that you just have too many images loaded in memory at some point and regardless of how much memory your system has, Delphi 7 application is still 32bit one, with limited amount of memory it can address. – Dalija Prasnikar Sep 01 '21 at 20:01
  • @DalijaPrasnikar It seems my issue is memory fragmentation due to using too many transient threads frafmenting ram. We're only talking about 100 images in somewhat low resolution and ram usage is under 500mb, so I don't believe memory limit is an issue. – bLight Sep 12 '21 at 16:31
  • Yes, you probably have a problem because too many threads are running at the same time, but chances that memory fragmentation is the issue are rather low. Anyway, besides some unnecessary parts, code you have shown here is not the cause, problem is in other code using it and you cannot get proper answer without showing that code and what you do with the images after they are loaded. Also exact sizes (or range) of images in pixels are also important here. "Somewhat low resolution" is not precise enough. – Dalija Prasnikar Sep 14 '21 at 07:32

0 Answers0