Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Multimedia (https://www.delphipraxis.net/16-multimedia/)
-   -   Delphi Schnell wiederholt in PaintBox zeichnen (Bitblt) (https://www.delphipraxis.net/142345-schnell-wiederholt-paintbox-zeichnen-bitblt.html)

BenjaminH 26. Okt 2009 20:31


Schnell wiederholt in PaintBox zeichnen (Bitblt)
 
Hallo,
ich zeichne bei meinem BeatDetector in ganz kurzen Intervallen das Spektrum.
Der Timer ist der aus MMSystems
Delphi-Quellcode:
timer := timeSetEvent(25, 25, @UpdateFreq, 0, TIME_PERIODIC);
Im Timer findet folgendes statt:
Delphi-Quellcode:
Spectrum:TBitmap;

BitBlt(MainForm.PaintBox1.Canvas.Handle, 0, 0, spectrum.width, Spectrum.height,spectrum.Canvas.Handle, 0, 0, SRCCOPY);
Jetzt passiert folgendes:
  • Nach einiger Zeit bleibt das Spektrum stehen(unter XP später, unter Win 7/Vista früher)
  • Blende ich jetzt die PaintBox aus und wieder ein, so funktioniert das Spektrum unter XP wieder.
    Unter Win 7/Vista dagegen ist es dann ganz verschwunden, die PaintBox ist also durchsichtig.

Allerdings habe ich durch zwischenzeitliches Speichern der Bitmap Spectrum erkannt, das diese durchgehend weitergezeichnet wird. Einzig das übertragen in die PaintBox scheint nicht zu funktionieren.

Kann irgendjeman dieses Verhalten erklären und mir einen Tipp geben, wie ich das beheben kann?
Vielen Dank!
Viele Grüße,
Benjamin

turboPASCAL 26. Okt 2009 20:37

Re: Schnell wiederholt in PaintBox zeichnen (Bitblt)
 
:gruebel:
Hm, schwer zu sagen das kann viele Ursachen haben.

Wie zeichnest du auf das Bitmap und oder erstellst du irgend etwas was du nicht frei gibst ?

PS.: Die Paintbox kann man auch weg lassen und direkt auhc den Canvas der Form zeichnen.

BenjaminH 26. Okt 2009 21:03

Re: Schnell wiederholt in PaintBox zeichnen (Bitblt)
 
Zeichen ich direkt auf die Form passiert dasselbe.
Ich habe mit FastMM geprüft, ob ich irgendwas nicht freigebe. Offensichtlich nicht.
Die einzigen Sachen die ich nutze sind FillRect und LineTo.

turboPASCAL 27. Okt 2009 11:09

Re: Schnell wiederholt in PaintBox zeichnen (Bitblt)
 
Kannst du die Zeichenrout. hier mal zeigen ?

Uwe Raabe 27. Okt 2009 11:27

Re: Schnell wiederholt in PaintBox zeichnen (Bitblt)
 
Wenn ich mich recht erinnere, muss das Zeichnen in die PaintBox im OnPaint-Event erfolgen, da die PaintBox keinen Speicher für den Zeicheninhalt bereitstellt.

In deinem Fall sollte im Timer-Ereignis lediglich PaintBox.Invalidate aufgerufen werden, während der BitBlt-Befehl im OnPaint der Paintbox stehen sollte.

BenjaminH 27. Okt 2009 14:09

Re: Schnell wiederholt in PaintBox zeichnen (Bitblt)
 
Zitat:

Zitat von Uwe Raabe
Wenn ich mich recht erinnere, muss das Zeichnen in die PaintBox im OnPaint-Event erfolgen, da die PaintBox keinen Speicher für den Zeicheninhalt bereitstellt.

Das ist zwar richtig, in dem Fall aber glaube ich leider irrelevant. Zwar "erinnert" sich die PaintBox nicht mehr an ihren Inhalt, wenn sie einmal geleert wurde, das ist aber egal, weil sie 40 mal pro Sekunde neu befüllt wird.

Die Zeichenroutine sieht so aus:
Delphi-Quellcode:
procedure paintSpectrum;
var
  i: Integer;
  y: Integer;
  sc:INteger; //Skalierung;
  b0,b1:Integer;
  rect:TRect;
  avg:Single;
begin
  //Fläche schwarz machen
  Rect.Left:=0;
  Rect.Top:=0;
  Rect.Right:=Spectrum.Width+1;
  Rect.Bottom:=Spectrum.Height+1;
  with Spectrum.Canvas do
  begin
    brush.Color:=clBlack;
    FillRect(Rect);
  end;
 
  //Jedes Band zeichnen
  b1:=1;
  for i := 0 to Bands - 1 do
  begin
    //Breite des Bandes
    b0:=b1-1;
    b1:=Trunc(Power(2, i * 10.0 / (BANDS - 1)));

    //Skalierungsfaktor
    sc:=10 + b1 - b0;

    //Wert des Bandes(skaliert)
    Y := Trunc((sqrt(MainForm.BandHistory[i,MainForm.Histposition-1] / log10(SC)) * 1.7 * Spectrum.Height) - 4);

    //Band zeichnen
    Rect.Left:=trunc(i*spectrum.width/bands);
    Rect.Top:=spectrum.Height-y;
    Rect.Right:=trunc((i+1)*spectrum.width/bands);
    Rect.Bottom:=spectrum.Height;
    with Spectrum.Canvas do
    begin
      //Ausgewähltes Band in Gelb, sonst grün
      if i=MainForm.SelectedBand then
        Brush.Color:=clYellow
      else
        Brush.Color:=clGreen;
      FillRect(Rect);
    end;
 
    //Durchschnitt des Bandes
    avg:=MainForm.AverageBandEnergy(MainForm.BandHistory[i]);
     
    //Durchschnittswert des Bandes einzeichnen
    Y := Trunc((sqrt(avg / log10(SC)) * 1.7 * Spectrum.Height) - 4);
    Spectrum.Canvas.Pen.Color:=clRed;
    Spectrum.Canvas.MoveTo(Rect.Left,Spectrum.Height-Y);
    Spectrum.Canvas.LineTo(Rect.Right,Spectrum.Height-Y);

    //Schwellwert einzeichnen
    Y :=Trunc((sqrt(avg*MainForm.DetectingFactor / log10(SC)) * 1.7 * Spectrum.Height) - 4);
    Spectrum.Canvas.Pen.Color:=clYellow;
    Spectrum.Canvas.MoveTo(Rect.Left,Spectrum.Height-Y);
    Spectrum.Canvas.LineTo(Rect.Right,Spectrum.Height-Y);
  end;
  //Auf Zeichenfläche übertragen
  BitBlt(MainForm.PaintBox1.Canvas.Handle, 0,0,spectrum.width,Spectrum.height,spectrum.Canvas.Handle, 0,0,SRCCOPY);
end;

Medium 27. Okt 2009 14:28

Re: Schnell wiederholt in PaintBox zeichnen (Bitblt)
 
Ich kann mir vorstellen, dass der Timer sich hier quasi selbst überholt, die Zeichenroutine also ab und an noch zugange ist wenn schon der nächste Callback kommt. Das staut sich dann zusammen. Du könntest also einen Skip-Frame-Mechanismus einbauen: Kling moppig, wäre aber einfach nur das Abschalten der Reaktion auf den Timer wenn gerade gezeichnet wird.

Ein anderer Ansatz wäre es das zeichnen ähnlich wie in Spielen zu machen: Im Application.Idle Event. Um dabei nicht 100% CPU Last reinzubraten kann man mittels eines FPS Counters die FPS quasi künstlich drosseln. Timer sind zum Zeichnen immer so eine Sache, und selten eine gute ;)

Edit: Ein dritter Weg wäre noch ein separater Zeichenthread der sich immer wieder mit Sleep(0) in der Dauerschleife "klein" hält. Auch hier wäre eine manuelle FPS-Begrenzung sinnvoll, und das letztliche Zeichnen auf den Zielcanvas sollte Synchronized gemacht werden.

BenjaminH 27. Okt 2009 14:48

Re: Schnell wiederholt in PaintBox zeichnen (Bitblt)
 
Zitat:

Zitat von Medium
Ich kann mir vorstellen, dass der Timer sich hier quasi selbst überholt, die Zeichenroutine also ab und an noch zugange ist wenn schon der nächste Callback kommt. Das staut sich dann zusammen. Du könntest also einen Skip-Frame-Mechanismus einbauen: Kling moppig, wäre aber einfach nur das Abschalten der Reaktion auf den Timer wenn gerade gezeichnet wird.

Das hab ich mal versucht. War aber nix.

Über die anderen Möglichkeiten muss ich mir mal Gedanken machen.
Was ist denn groß der Unterschied zwischen dem Zeichnen im Timer und dem im OnIdle Event/eigenem Thread? In beiden Fällen wird doch regelmäßig gezeichnet, nur dass das beim Timer eben in festen Intervallen ist, oder?

Uwe Raabe 27. Okt 2009 14:54

Re: Schnell wiederholt in PaintBox zeichnen (Bitblt)
 
Das mit dem Sich-Selbst-Überholen des Timers kannst du mit dem OnPaint-Ansatz ebenfalls lösen.

turboPASCAL 27. Okt 2009 15:03

Re: Schnell wiederholt in PaintBox zeichnen (Bitblt)
 
Liste der Anhänge anzeigen (Anzahl: 1)
Auch hier wieder ein Offscreenbitmap basteln und in einem Timer etc. auf dieses Bitmap zeichnen lassen.
Das/die Bitmaps dann in der Paintbox zeichnen.

Spectrum ist eine PaintBox ?

Ich pers. verwende keine PaintBox, ist mir zu "dumm". ;)


Beispiel:
Delphi-Quellcode:
implementation

{$R *.dfm}

const
  VisXOffset = 50;
  VisYOffset = 40;

procedure TForm1.FormCreate(Sender: TObject);
begin
  bmp := TBitmap.Create;
  bmp.Width := 200;
  bmp.Height := 80;
  bmp.Canvas.Brush.Color := clBlack;

  RenderVisDone := True;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  if RenderVisDone and ASSIGNED(bmp) then
  begin
    RenderVis;
    bitblt(Canvas.Handle, VisXOffset, VisYOffset, bmp.Width, bmp.Height,
      bmp.Canvas.Handle, 0, 0, SRCCOPY);
  end;
end;

procedure TForm1.RenderVis;
var
  i: integer;
  gt: Cardinal;
begin
  RenderVisDone := False;
  bmp.Canvas.FillRect(bmp.Canvas.ClipRect);

  for i := 0 to bmp.Width div 4 - 1 do
  begin
    gt := GetTickCount;
    Windows.SetPixel(bmp.Canvas.Handle, i*4, round(bmp.Height div 2 + sin(gt/250+i)*15), RGB(255, 0, 0));
    Windows.SetPixel(bmp.Canvas.Handle, i*4, round(bmp.Height div 2 + sin(gt/500-i)*20), RGB(128, 128, 255));
  end;

  RenderVisDone := True;
end;

end.
Man kann sich das auch in eine Classe bauen bzw. formen.


//Edit: Delphicodehighlightdingenbumensfehlfunktionskorrek tur ..

BenjaminH 1. Nov 2009 16:35

Re: Schnell wiederholt in PaintBox zeichnen (Bitblt)
 
Ich hab mein Projekt mal damit verglichen. Meiner Meinung nach, wird alles identisch aufgerufen.
Spectrum ist vom Typ TBitmap.

Allerdings habe ich jetzt folgendes festgestellt:
Wenn ich die Bitmap sobald es hängen geblieben ist freigebe und wieder erstelle, dann funktioniert es wieder(für kurze Zeit). Es scheint als ob die Bitmap irgendwie "kaputt" ginge.
Momentan habe ich mir damit beholfen die Bitmap für jedes mal zeichnen neu zu erzeugen.

Hat jemand eine Idee, wodurch ich mir die Bitmap ausversehen "zerschießen" könnte?
Viele Grüße,
Benjamin


Alle Zeitangaben in WEZ +1. Es ist jetzt 09:08 Uhr.

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