Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Multimedia (https://www.delphipraxis.net/16-multimedia/)
-   -   Erneutes Laden von Bitmaps: Speicherplatz freigeben (https://www.delphipraxis.net/163389-erneutes-laden-von-bitmaps-speicherplatz-freigeben.html)

var-king 27. Sep 2011 11:50

Erneutes Laden von Bitmaps: Speicherplatz freigeben
 
Hallo allerseits,

ich arbeite zur Zeit an einem Programm, das über Bildbearbeitungsfunktionen verfügt, aber auch durch einen schnellen Klick zwischen allen Bildern eines Ordners durchswitchen kann, wie die Vor-Zurück-Pfeile in der Windows-Fotogalerie, wenn man mal schnell Bilder gucken will.

Ich realisiere dies durch Laden des aktuellen Bildes in eine Klasse, unter anderem mit den folgenden Bitmaps
Delphi-Quellcode:
type Tbild = class
  original, current: TBitmap;
  last: array [1..10] of TBitmap;
  {.....}
end;
Diese Bitmaps brauche ich für die Bearbeitungsfunktionen; current stellt die Bitmap mit der aktuellen Version des Bildes dar, ist aber zur Öffnungszeit mit original gleich.

Mein Problem: Beim eben erwähnten "Bilder-Gucken" geht der Speicher sehr schnell fritte, nach ca 20-30 10MP-Bildern ist zumindest an meinem PC Schluss ("Für den angeforderten Befehl steht kein Speicher mehr zur Verfügung"). Natürlich liegt das an den obigen zwölf Versionen meines Bildes.

Allerdings ändert sich dadurch auch nichts, dass ich vor dem Laden eines neuen Bildes versuche, den ganzen Speicherplatz wieder freizugeben:
Habe nun über
freeandnil(bild)
hin zu
Delphi-Quellcode:
  bild.current.Free;
  bild.original.Free;
  for i := 1 to 10 do
    bild.last[i].free;
  bild := TBild.init
alles versucht, es hilft nichts.
Wer hat eine Idee, wo mein Fehler sein könnte, bzw. der Speicher nicht freigegeben werden kann?

Danke im Voraus!

himitsu 27. Sep 2011 12:40

AW: Erneutes Laden von Bitmaps: Speicherplatz freigeben
 
Delphi-Quellcode:
FreeAndNil(Bild)
gibt ja nur das TBild-Objekt frei und setzt die Variable auf nil.
Wenn du also keinen Destructor erstellt hast, in welchem du die enthaltenen TImages freigibst, dann bleiben diese Subobjekte natürlich erhalten.

Eventuell Delphi-Referenz durchsuchenFreeAndNil auch für die Subbilder nutzen, damit du mit Delphi-Referenz durchsuchenAssigned erkennen kannst, ob da jeweils ein TBitmap-Objekt enthalten ist.



Was hast du denn sonst noch für Codes?
Das kann ja nicxht alles sein.

var-king 27. Sep 2011 13:50

AW: Erneutes Laden von Bitmaps: Speicherplatz freigeben
 
Gut, aber dann müsste es doch mit dem letzten Codebeispiel klappen? Da "free"e ich ja die Subbilder einzeln.

var-king 27. Sep 2011 13:56

AW: Erneutes Laden von Bitmaps: Speicherplatz freigeben
 
Es tut sich also auch tatsächlich nichts, wenn ich so alle Bilder in "Bild" einzeln freeandnille:

Delphi-Quellcode:
 
  freeandnil(bild.current);
  freeandnil(bild.original);
  for i := 1 to 10 do
    freeandnil(bild.last[i]);
  freeandnil(bild);
  bild := Tbild.init;
  bild.open(pname);

himitsu 27. Sep 2011 14:47

AW: Erneutes Laden von Bitmaps: Speicherplatz freigeben
 
Jupp, diese Bilder würdes du damit freigeben,

aber wie erstellst du die Bilder?
Womöglich überschreibst du da ständig die Komponenten und vergißt die Alten freizugeben.

DeddyH 27. Sep 2011 14:51

AW: Erneutes Laden von Bitmaps: Speicherplatz freigeben
 
Es wäre ohnehin sauberer, wenn die Klasse TBild die Bitmaps selbst freigibt (spätestens im Destruktor). Dann bräuchte man von außen nämlich nur diese Instanz freizugeben, und alle Bilder wären sauber entsorgt.

var-king 27. Sep 2011 21:32

AW: Erneutes Laden von Bitmaps: Speicherplatz freigeben
 
Gut, also jetzt mal systematisch.

Wenn ich zu einem anderen Bild "umschalte", geschieht Folgendes:

Delphi-Quellcode:
  bild.destroy;
  bild := Tbild.init;
  bild.open(pname);
Dabei ist bild.destroy der von mir definierte Destructor, ja, mit override:

Delphi-Quellcode:
destructor Tbild.destroy;
var i: integer;
begin
  current.Free;
  original.Free;
  for i := 1 to 10 do
    last[i].Free
end;
Der Constructor führt Folgendes durch:

Delphi-Quellcode:
constructor TBild.init;
var i: integer;
begin
  current := TBitMap.create;
  for i := 1 to 10 do
    last[i] := TBitMap.create;
  original := TBitMap.create;
  changed := false;
  showtext := true;
  setlength(textarray,0)
end;
Und schließlich hänge ich der Übersichtlichkeit halber auch noch die ganze open-Prozedur an:

Delphi-Quellcode:
procedure TBild.Open(pname: string);
var jpg: TJPEGImage;
    i: integer;
begin
  checksave;
  if lowercase(extractfileext(pname))='.jpg' then
  begin
    jpg := TJPEGImage.Create;
    jpg.loadfromfile(pname);
    original.assign(jpg);
  end else
  if lowercase(extractfileext(pname))='.bmp' then
    original.loadfromfile(pname);

  for i := 1 to 10 do
    last[i].Assign(original);
  name := pname;
  current.Assign(original);
  changed := false;
  application.title := Programmtitel+' |  '+extractfilename(bild.name);
  with form1 do
  begin
    dateiliste(ExtractFilePath(bild.name), form1.datlist.Items);
    lb_groesse.Caption := inttostr(current.width)+'x'+inttostr(current.Height);
    lb_resolution.caption := 'Auflösung: 100% - '+inttostr(current.width)+'x'+inttostr(current.Height);
    form1.Caption := application.Title;
    lb_name.caption := extractfilename(bild.name)
  end;
  screenzoom;
  display
end;
Ja, es mag nicht besonders speicherschonend sein, direkt bei Open zehnmal das Original ins Array zu laden. Aber dies ist nun ziemlich fest im Programm verwurzelt und eigentlich kann dort auch nicht das Problem sein. Denn nach meiner Schreibweise müssten die ja auch komplett wieder freigegeben werden.

Trotzdem muss bei mir pro Bild 1% meines RAM dran glauben.

Jemand einen Tipp?

Medium 27. Sep 2011 22:38

AW: Erneutes Laden von Bitmaps: Speicherplatz freigeben
 
Das wichtigste zuerst: Niemals Destroy() direkt aufrufen, immer Free() oder gleich FreeAndNil() - die Predigt haben wir hier öfter ;). Das wird in diesem Fall gut gehen, es gibt aber andere, in denen das nicht so geht, von daher besser sofort abgewöhnen.
Dann noch eine Stilsache: Der Konstruktor heisst Create(), nicht Init()! Von "Create" abweichende Namen sollte man nur für statische Methoden nehmen, die Factory-artig arbeiten, und selbst da sollte es immer noch irgendwie "Create" drin vorkommen.

Du schlabberst dir bei einem 10MP Bild damit aber auch gleich mal (10*1000000*4)/(1024*1024) ~= 380 MB Speicher voll, das ist ja schon mal was, und nicht die "feine Englische Art". In Open() wird "jpg" nie freigegeben, und ich bin mir fast sicher, dass TJpegImage intern eine dekomprimierte Version vorhält, ggf. noch weitere Metadaten, vor allem aber GDI Resourcen belegt, die schnell mal knapp werden können. Eventuell schafft das schon Abhilfe.
Zudem ist es mir schon ab und an passiert, dass mir FastMM Lecks angezeigt hat, wenn ich Arrays nicht mit Finalize() explizit und "ganz" platt gemacht habe, das ggf. also auch noch rein. Ich habe aber eher GDI Resourcen im Verdacht, nicht blos das RAM, auch wenn du das ziemlich mies behandelst ;)
Was machen checksave, screenzoom und display? (Auch eher schlechte Namen übrigens, und sie riechen sehr deplatziert.)

var-king 27. Sep 2011 23:00

AW: Erneutes Laden von Bitmaps: Speicherplatz freigeben
 
Problem gelöst - das Freigeben vom jpg bei Open war's.
Danke @Medium!

Ich hatte ehrlich gesagt angenommen, dass sich die lokalen Variablen schon von selbst erledigen.

Und danke für die anderen formalen Tipps, ich bin halt nur Gelegenheitsprogrammierer. checksave, screenzoom und display haben dort allerdings durchaus ihren Sinn ;-) Fürs Protokoll: sie platzieren das Bild vor allem oberflächenfüllend in der Mitte des Programms!

himitsu 27. Sep 2011 23:16

AW: Erneutes Laden von Bitmaps: Speicherplatz freigeben
 
Und dann solltest du besser auf globale Variablen verzichten.

Wenn dein TBild das Form1 kennen soll, dann übergib es ihm im Constructor, über ein Property und speichere dieses in einem Feld,
oder als Parameter direkt an das Open, anstatt auf Form1 zuzugreifen.

Wobei ich alles ab application.title nicht in die Klasse reinmachen würde,
da man sich so eine untrennbare Verbindung einbaut.
Für sowas gibt es Callbacks/Events, wie z.B. das OnChange eines Edits ... man baut den seinen OnChange-Code ja auch nicht direkt in die TEdit-Klasse ein.
> wiederverwendbarer Code

PS: Du nutzt
Delphi-Quellcode:
with Form1 do
und greifst darin nochmal auf Form1 zu, obwohl du dich schon in dessen Scope/Gültigkeitsbereich befindest?
Ist nicht schlimm, aber "unschön". (abgesehn davon daß man auf sowas besser nicht direkt zugreifen sollte)



Zu der Bezeichnung "Init":
Diese ist auch noch syntaktisch falsch, denn dort erstellst und initialisierst du das Objekt, anstatt es nur zu initialisieren.

Abgesehn davon, daß sich Create als Constructor etabliert hat und jeder weiß was dieses macht, wenn man nur den Namen ließt ... das ist bei init nicht der Fall.
> selbsterklärender Code


Alle Zeitangaben in WEZ +1. Es ist jetzt 11:33 Uhr.
Seite 1 von 2  1 2      

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz