[GDI+] _GDIplus_BitmapMove

  • Moin,

    Es ist wieder nur eine sehr kleine Funktion für GDI+ von der ich nicht weiß ob ich sie in dieser Form schonmal im Forum gesehen habe. Der Übersicht halber hat sie jetzt einen eigenen Thread, falls sie jemand sucht. Das Prinzip der Funktion ist bekannt, aber ehe Irgendwer Workarounds fabriziert, lieber den einfachen Weg wählen :)

    Was kann die Funktion:
    - Das berühmte "Links rein, Rechts raus" Prinzip auf Bitmaps anwenden.
    - Das Bild "verschmierfrei" behandeln. (Ist 2x abgesichert. Wer das nicht will nimmt Round heraus und ändert die Zeichenfunktion von int zu float.)
    - mehr nicht.

    Im Anhang ist eine Zip versehen mit der Funktion selbst und einem Anwendungsbeispiel.


    lg
    M

  • Hi,
    funktioniert einwandfrei!
    Allerdings habe ich (egal ob round() oder int() ) einen Effekt, der Tearing ziemlich nahe kommt. Wahrscheinlich hat das aber eher mit der Beispielfunktion zu tun.

  • Das Tearing liegt am fehlenden VSync.
    Hab dir mal kurz eine D2D Version gebaut, da ist es nur noch wegen den Integern abgehackt, aber 0% Tearing ;)

    Edit1: Dabei ist mir aufgefallen, dass D2D keine negativen Koordinaten mag. Daher hab ich es kurzerhand einfach mit Max(..., 0) gelöst. Das Ergebnis ist nun exakt gleich mit dem von GDI+

    Edit2: Im Prinzip ist die Variante mit Max sogar wesentlich besser. Dort wird nämlich nur der benötigte Bildausschnitt gezeichnet, und wie ich GDI+ kenne wird da nicht intern geschaut welches Rechteck vom Quellbild man benötigt, sondern Pixel für Pixel jedes Mal eine IsInRect Routine (die selbstverständlich ein eigenes Handle im Kernel bekommt, sonst wäre das ja eine schnelle Sache) aufgerufen. ggf ist also noch etwas Beschleunigung von GDI+ drin, leider ist es Spät und ich will schlafen...^^

    lg
    M

  • Seeeehr geschmeidig!
    Tearing und ruckeln ist vollständig weg, Geschwindigkeit vorher 10-12ms, jetzt 4-5ms, also ca. Faktor 2.5 schneller als GDI. Aber das wäre mir ziemlich egal, Hauptsache das Geruckel ist jetzt weg!

  • Ich bewundere immer wieder, wie butterweich die Animationen in JS, D2D, etc. sind und warum das nicht in GDI+ möglich ist. Wenn man GDI+ mit GDI verheiratet, dann kommt man in die Richtung...

    Auch am Arsch geht ein Weg vorbei...

    ¯\_(ツ)_/¯

  • Wenn du es richtig geschmeidig haben willst musst du die Float-Positionen bestmöglich durch Int-Positionen approximieren. Also wenn ich z.B. 10x um 0.5 Pixel bewege nimmt man 0 1 0 1 0 1 0 1 0 1 und nicht (wie aktuell mit einfachem Abrunden) 0 0 0 0 0 0 0 0 0 0.

    Klingt weeeesentlich schwieriger als es ist. Einfach einen Positionspuffer erzeugen in dem der "Rest hinter dem Komma" aufgehoben wird.

    AutoIt
    Global $aFloatPos = [0, 0]
    
    
    $aFloatPos[0] += 5 * Sin(TimerDiff($t) / 1000) + 3 * Cos(TimerDiff($t) / 3000)
    $aFloatPos[1] += 5 * Cos(TimerDiff($t) / 1000) + 3 * Sin(TimerDiff($t) / 7000)
    $iTimer = TimerInit()
    _ED2D_BitmapMove($hIMG, $hIMG2, Int($aFloatPos[0]), Int($aFloatPos[1]))
    $iTime = (TimerDiff($iTimer) + $iTime * 19) / 20
    $aFloatPos[0] -= Int($aFloatPos[0])
    $aFloatPos[1] -= Int($aFloatPos[1])


    Zu GDI+:
    Das einzige was hier fehlt ist der VSync. Gäbe es eine Funktion (wie in D2D) die passend zur Bildfrequenz zeichnet (Es gibt leider keine mit bekannte Methode das in GDI+ zu fabrizieren), hätte man 90% Des Ruckelns abgeschafft, da man als Programmierer etwas aufpassen kann, dass das Skript die 60 oder 30 FPS schafft. (Bei niedrigeren Frameraten ist das Tearing eher Nebensache).
    Man kann mit D2D (manche, viele, alle, keine ?) Bitmaps aus GDI+ (via Scan0 geht "meistens", da man hier das RAM kopieren kann... können sollte.... hust.... Pixelformatfehler obwohl nur Integer im RAM liegen !?) kopieren und dann als D2D Bitmap ins Fenster beamen, das habe ich an einigen Stellen bereits ausprobiert, eine allumfassend zufriedenstellende Lösung gibt es (von mir jedenfalls) noch nicht.

    Edit: Das Problem ist nichtmal neu.... Das hab ich mir vor 3 Jahren schon gestellt. Inzwischen ist aber (dank eukalyptus und seinem D2D-Elan) wenigstens ein Ziel in Sicht :D

    lg
    M

  • Hier eine GDI+/GDI Version, die für diesen Zweck sehr schnell ist, aber immer noch ruckelt, aber nicht mehr so stark.

    Ich sollte auch mehr in D2D machen, weil einfach besser als GDI+...

    Auch am Arsch geht ein Weg vorbei...

    ¯\_(ツ)_/¯

    2 Mal editiert, zuletzt von UEZ (28. April 2016 um 14:23)

  • Ich vermute, das Geruckel der GDI-Version hat genau mit der von Mars bereits festgestellten Round() / int() - Problematik zu tun, an den FPS kann es eigentlich nicht liegen. Das Geruckel tritt bei GDI hauptsächlich an den "engen" Wendepunkten der Bewegung auf, bei annähernd gradliniger Bewegung geben sich beide Varianten imho nichts!
    Ich habe mal beide Scripte parallel laufen lassen, der Unterschied an den Wendepunkten ist wirklich frappierend. Nicht zu vernachlässigen, dass D2D nicht einige Prozent schneller ist, sondern im vorliegenden Beispiel Faktor 3-5!

  • Halt Stop, ich glaube hier wird etwas verwechselt:

    Mars Variante: Sowohl die GDI+ als auch die D2D Methode bewegen die Bitmap jeweils in 1px Schritten. Falls man naiv an die Sache herangeht (und jeden einzelnen Schritt abrundet) kommt es so zu "sprüngen" in der Bewegungsgeschwindigkeit. Bei 60FPS immerhin von 0px/s auf 60px/s und 120px/s. Das kann man weitestgehend umgehen, indem man einen Puffer für die Kommastellen benutzt (siehe Post 6). Das Tearing tritt nur bei GDI+ auf, da der Sync nicht stimmt.

    UEZ Variante: Ein wunderbares Beispiel für "Viele Wege führen nach Rom". Hier wird die Bitmap als Textur genutzt und anschließend verschoben abgebildet. Dabei ist ein sehr großer Unterschied zur Mars Variante, 1. Die Bitmap bleibt unangetastet, 2. Die Bewegungsschritte sind als float möglich (Da die Bitmap unverändert bleibt "verschmiert" diese auch nicht mit der Zeit). In der UEZ Variante gibt es also keine Sprünge von 0px/s auf 60px/s oder ähnliches, es gibt nur das GDI+ Typische Tearing. Hier liegt also kein Problem mit Round/Int vor.

    Beweis:

    AutoIt
    DllCall($__g_hGDIPDll, 	'int', 'GdipTranslateTextureTransform', 'handle', $hTXT, _
    							'float', 0.2 * Sin(TimerDiff($t) / 1000) + 0.2 * Cos(TimerDiff($t) / 3000), _
    							'float', 0.2 * Cos(TimerDiff($t) / 1000) + 0.2 * Sin(TimerDiff($t) / 7000), _
    							'int', 0)

    Hier wird das Bild in jedem Schritt um weniger als 1px bewegt, es kann also keine Round/Int Probleme geben. Da wir keinen besseren Buhmann haben: Tearing ist Schuld :D

    Edit: Sei froh, dass du kein D2D betreibst. Ich platze schon wieder vor Wut. Wollte ein DC-Kompatibles Render-Target herstellen. Keine Chance, egal was ich versuche: "irgendeiner der Parameter hat einen Fehler". Man weiß einfach nicht was die von einem wollen. Es gibt keine anständigen Errors mit denen man was anfangen kann und irgendwie funktioniert alles nur als Spezialfall. Bei GDI+ ist es einfach: GFX -> Darauf mal ich herum, Bitmap -> Selbsterklärend. D2D ist so nach dem Motto: Egal was du tust, es ist falsch oder es ist noch falscher, oder es ist ganz falsch. Und in keinem der 3 Fälle funktioniert irgendwas.

    lg
    M

  • Nicht zu vernachlässigen, dass D2D nicht einige Prozent schneller ist, sondern im vorliegenden Beispiel Faktor 3-5!

    Das sehe ich nicht so, denn man muss fair bleiben und alle Calls in die Berechnung einbeziehen.

    D2D:


    GDI+:

    GDI/GDI+ Textur:

    Meine Resultate:
    D2D: ~16.6ms
    GDI+: ~12ms
    GDI/GDI+: ~7.6ms

    Auch am Arsch geht ein Weg vorbei...

    ¯\_(ツ)_/¯

  • Ein wunderbares Beispiel für "Viele Wege führen nach Rom". Hier wird die Bitmap als Textur genutzt und anschließend verschoben abgebildet. Dabei ist ein sehr großer Unterschied zur Mars Variante

    Ein Vorteil hat diese Textur Version noch: du kannst ganz einfach das Bild spiegeln! Dazu den WrapMode Parameter in der Funktion _GDIPlus_TextureCreate angeben.

    Default ist 0.

    Auch am Arsch geht ein Weg vorbei...

    ¯\_(ツ)_/¯