Bilder durchlaufen lassen - ruckelt aber :-(

  • VB.NET

Es gibt 8 Antworten in diesem Thema. Der letzte Beitrag () ist von FreakJNS.

    Bilder durchlaufen lassen - ruckelt aber :-(

    Servus,
    erstmal vorweg: Ich würde mich als recht guten progger bezeichnen, allerdings ist Grafik nun wirklich nicht mein ding ;)
    Nun soll ich eine kleine Promotion-App basteln, welche unterm strich folgendendes leisten soll:
    Oben in der mitte Firmenlogo -> ok, geschenkt
    jetzt sollen unten links einige logos undere Kunden sich erst nach oben bewegen und dann wenn sie komplett sichbar sind nach rechts rauslaufen.
    Das ganze habe ich mal mit einer "hübschen" Paint zeichnung verdeutlicht.


    Okisch, ich das ganze also per GDI direkt auf die Form gezeichnet und die ganzen verschiebungen im Paint-Event der Form per x/y-Koordinate berechnet.
    Nun geht ein Timer (3ms) hin und mach nix anderes als Invalidate() und Update() aufzurufen.
    Funktioniert auch alles subba :)
    Nur kommt es gelegentlich vor, dass der Ablauf kurz laaaaangsamer wird und danach normal weiterläuft.
    Natürlich nicht testbar unregelmäßig und sporadisch :(

    Das muss je eigentlich an meinem Konzept liegen, oder?
    VB kann Grafiktechnisch doch bedeutend mehr leisten, oder?

    Danke für eure Ideen
    3ms sind viel zu schnell. Das entspricht 333 Bildern pro Sekunde (Was mit GDI eigentlich so gut wie unmöglich ist). Ausreichend währen aber schon 24 um eine Flüssige bewegend hinzubekommen.
    Auch solltest du nicht immer Alles neu zeichnen sondern nur die Bereiche der Form die sich geändert haben. Dafür hat "Invalidate" eine Überladung der du einen Bereich übergeben kannst.

    Auch eine Möglichkeit wäre die Verwendung von WPF. Dort könntest du die Bilder über Keyframe Animationen bewegen lassen.

    Grüße Wulf

    spamme schrieb:

    VB kann Grafiktechnisch doch bedeutend mehr leisten, oder?

    Ja, aber nicht mit WinForms, Win32-Controls sind für schnelle Updatevorgänge hintereinander nicht geschaffen worden. Mit WPF wäre das ganze kein Problem und würde auch absolut Ruckelfrei laufen, und zwar mit deutlich weniger Aufwand (wenn du die Technologie erstmal kannst).

    Wulf schrieb:

    Dafür hat "Invalidate" eine Überladung der du einen Bereich übergeben kannst.

    Ich habs mal probiert. Dafür habe ich einen Timer genommen, der alle 40 Millisekunden Update() und Draw() aufruft, wobei im Draw() eine Region aus den 3 Rectangles der Bilder erstellt wird und damit dann das Panel invalidatet wird. Mehr als 22 FPS bekam ich allerdings nicht hin. Auch mit größerem oder kleinerem Timer-Intervall. Da muss man dann wohl statt einem Timer einen einfachen Loop nehmen, wenn man mehr will. Man kann sich auch mal bei denjenigen umschauen, die im Showroom GDI+ Spiele veröffentlicht haben.
    Übrigens haben die BIlder bei mir trotz DoubleBuffering noch viel geflackert: (das Bild habe ich wegen der Dateigröße auf 75% verkleinert)
    img694.imageshack.us/img694/808/k6t.gif

    Hier noch der Code:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Class Form1
    2. Dim logo1Pos, logo2Pos, logo3Pos As Point
    3. Dim lastUpdate As DateTime
    4. Private Sub Timer1_Tick(sender As System.Object, e As System.EventArgs) Handles Timer1.Tick
    5. UpdateLocations()
    6. DrawLogos()
    7. End Sub
    8. Private Sub UpdateLocations()
    9. If lastUpdate <> Nothing Then
    10. Label1.Text = CStr(Math.Round(1000 / (DateTime.Now - lastUpdate).TotalMilliseconds, 1)) & " FPS"
    11. End If
    12. lastUpdate = DateTime.Now
    13. If logo1Pos.Y > 0 Then
    14. logo1Pos = New Point(0, logo1Pos.Y - 5)
    15. Else
    16. logo1Pos = New Point(logo1Pos.X + 5, 0)
    17. End If
    18. If logo1Pos.Y < 50 Then
    19. If logo2Pos.Y > 0 Then
    20. logo2Pos = New Point(0, logo2Pos.Y - 5)
    21. Else
    22. logo2Pos = New Point(logo2Pos.X + 5, 0)
    23. End If
    24. End If
    25. If logo2Pos.Y < 50 Then
    26. If logo3Pos.Y > 0 Then
    27. logo3Pos = New Point(0, logo3Pos.Y - 5)
    28. Else
    29. logo3Pos = New Point(logo3Pos.X + 5, 0)
    30. End If
    31. End If
    32. If logo3Pos.X > Panel1.Width Then
    33. Timer1.Stop()
    34. End If
    35. End Sub
    36. Private Sub DrawLogos()
    37. Dim invalidated As New Region(New Rectangle(logo1Pos.X - 1, logo1Pos.Y, My.Resources.daermonism5_ohnebg.Size.Width + 2, My.Resources.daermonism5_ohnebg.Size.Height + 1))
    38. invalidated.Complement(New Rectangle(logo2Pos.X - 1, logo2Pos.Y, My.Resources.letter_icon.Size.Width + 2, My.Resources.letter_icon.Size.Height + 1))
    39. invalidated.Complement(New Rectangle(logo3Pos.X - 1, logo3Pos.Y, My.Resources.twitter_logo.Size.Width + 2, My.Resources.twitter_logo.Size.Height + 1))
    40. Panel1.Invalidate()
    41. End Sub
    42. Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
    43. logo1Pos = New Point(0, Panel1.Height)
    44. logo2Pos = New Point(0, Panel1.Height)
    45. logo3Pos = New Point(0, Panel1.Height)
    46. End Sub
    47. Private Sub Panel1_Paint(sender As Object, e As System.Windows.Forms.PaintEventArgs) Handles Panel1.Paint
    48. e.Graphics.DrawImage(My.Resources.daermonism5_ohnebg, logo1Pos)
    49. e.Graphics.DrawImage(My.Resources.letter_icon, logo2Pos)
    50. e.Graphics.DrawImage(My.Resources.twitter_logo, logo3Pos)
    51. 'Me.DoubleBuffered = True
    52. Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
    53. Me.SetStyle(ControlStyles.OptimizedDoubleBuffer, True)
    54. Me.SetStyle(ControlStyles.UserPaint, True)
    55. End Sub
    56. Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
    57. Timer1.Start()
    58. End Sub
    59. End Class

    Skybird schrieb:

    Das sind ja Ubisoftmethoden hier !

    @vb-checker
    Ich glaube du hast in Zeile 46 das wichtigste vergessen: den Bereich der .invalidate-Methode zu übergeben x

    edit: aber dann stimmts immernoch nicht ganz. Du musst den alten und den neuen Bereich des bewegten Objektes invalidieren. Die .complement-Methode kenn ich garnicht, bekomme es damit aber grade nicht zum laufen... Mehrere .invalidate-Aufrufe sollten aber auch nicht schlimm sein, weil sie afaik zusammengefasst werden.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „FreakJNS“ ()

    @FreakJNS: Oh wow, da hab ich durch zu viel Rückgängig machen echt einiges kaputt gemacht. Den alten Bereich invalidate ich schon, dafür sind die +1 und -1 ja da. Danach hab ich aber die Bewegungsschritte auf 5 gesetzt und es da vergessen. :cursing:
    Werd's nach ein paar Stunden Schlaf noch mal machen...

    Skybird schrieb:

    Das sind ja Ubisoftmethoden hier !

    @FreakJNS:
    habe die Fehler jetzt mal ausgebessert. (Unter anderme sind die SetStyle-Aufrufe einen Sub zu tieg gerutscht -.- )
    Jetzt wird gar nichts mehr gezeichnet. Das scheint am Complement() zu liegen. Welche Möglichkeit gibts da noch?

    Skybird schrieb:

    Das sind ja Ubisoftmethoden hier !

    Der ErfinderDesRades hat das sehr gut beschrieben: mycsharp.de/wbb2/thread.php?threadid=51920

    Die Knackpunkte sind, dass man eine Klasse Figur (=> das zu zeichnende Objekt) erstellt, die sich selbst zeichnen kann ( also eine Methode .draw(byval g as graphics) ) hat und weiß, welchen Bereich sie belegt (=> .Bounds). Damit hätte man schon die Grundlage gelegt.

    In der Update-Routine, und nur in der Update-Routine, werden neue Positionen/neue Objekte/etc gestzt. Es ist also ein leichtes me.Invalidate(ObjektXY.Bounds) aufzurufen (um den alten Bereich neuzuzeichnen), danach die Positionen/Rotationen/.. zu manipulieren und wieder me.Invalidate(ObjektXY.Bounds) aufzurufen (um den neuen Bereich neuzuzeichnen).
    Wichtigster Hinweis ist, dass ein Aufruf von .invalditate(..) nicht sofort ein onPaint-Aufruf auslöst - mehrere aufeinander folgende .invalidate-Aufrufe werden zusammengefasst.

    Falls das Probleme machen sollte kann man immernoch alle Bounds auf einen GraphicsPath (oder Region?) adden und diesen Buffer der .invalidate-Methode übergeben. Region hat ja eine entsprechende Konstruktor-Überladung die einen GraphicsPath haben will.




    Edit: habe hier mal ein kleines Beispiel gemacht, dass nur zeigt wie Leistungsstark GDI sein kann, wenn man nicht ständig ALLES komplett neuzeichnet. Oben genanntes System ist NICHT umgesetzt, nur dessen Auswirkung:

    VB.NET-Quellcode

    1. Dim bmp As New Bitmap(1000, 1000)
    2. Dim sw As New Stopwatch
    3. Using g As Graphics = Graphics.FromImage(bmp)
    4. sw.Start()
    5. g.Clear(Color.Red)
    6. g.SetClip(New Rectangle(50, 50, 50, 50)) 'Simuliert den neuzuzeichnenden Bereich, (testweise auskommentieren!)
    7. For i = 0 To 1000
    8. g.Clear(Color.Blue)
    9. Next
    10. sw.Stop()
    11. End Using
    12. PictureBox1.Image = bmp
    13. Me.Text = sw.ElapsedMilliseconds.ToString & " ms"


    Das läuft bei mir in ca. 10ms ab. Kommentiert man die Zeile aus, die den ungültigen Bereich simuliert dauert es über 2000ms, weil immer alles neugezeichnet wird.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „FreakJNS“ ()