Bitmap in ein beliebiges Viereck zeichnen

  • VB.NET

Es gibt 22 Antworten in diesem Thema. Der letzte Beitrag () ist von RodFromGermany.

    Bitmap in ein beliebiges Viereck zeichnen

    Hallo,

    gibt es eine einfache Möglichkeit, eine Bitmap vollständig in die Umrisse eines unregelmäßigen Vierecks zu zeichnen, z.B. für die perspektivische Verzerrung eines Fotos in der Schrägansicht ?
    So etwas wie "FillPolygon(bitmap,p1,p2,p3,p4)" ?
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    koning3 schrieb:

    gibt es eine einfache Möglichkeit, eine Bitmap vollständig in die Umrisse eines unregelmäßigen Vierecks zu zeichnen
    Jepp - System.Drawing.Drawing2D.Matrix heisst die Klasse, mit der man solch tut.
    Allerdings sind nur die Operationen Rotate, Translate, Scale und Shear benutzerfreundlich bereitgestellt.
    Shear verzerrt ein Rechteck zu einem Trapez - also noch nicht wirklich unregelmässiges Viereck.

    Vermutlich muss man sich was schlaues ausdenken, um die Matrix-Elemente so listig einzustellen, dasses auf ein beliebiges Viereck abbildet.
    Ich denke mit geeignetem mathematischen Wissen kriegt man das hin - vom Prinzip her kann eine Matrix sowas.

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

    Hi

    Es gibt da mehrere Möglichkeiten. Kann man selbst berechnen oder berechnen lassen. GDI+ bietet leider nur die Möglichkeit ein Bild über 3 Punkte zu verzerren. Hier ein 5 Jahre altes Bsp wie es über 4 Punkte geht. activevb.de/cgi-bin/upload/download.pl?id=3630 Ist zwar kein perfektes Bsp, zeigt aber wie man das ganze angehen könnte.

    Und weil ich da letztens was mit Direct2D hier hochgeladen hatte, fällt mir noch dieses ein. 3D perspective transform effect -> docs.microsoft.com/en-us/windo…/3d-perspective-transform
    Mfg -Franky-
    OK, danke für die vielen Tipps. Manche sind mir eigentlich zu aufwendig.

    Ich habe eine Lösung mit Lockbits und etwas Mathematik (eigentlich nur Strahlensatz) ausprobiert. In den meisten Fällen OK, aber dann wieder merkwürdige Ergebnisse.
    Anbei zwei Screens, einmal mit Fehler, einmal OK. Daher die Hoffnung, es möge einen Befehl wie FillPolygon(bitmap,p()) geben.
    Bilder
    • BA220208_222926.jpg

      117,97 kB, 1.450×1.448, 77 mal angesehen
    • BA220208_223143.jpg

      134,02 kB, 1.413×1.413, 72 mal angesehen

    koning3 schrieb:

    einmal mit Fehler,
    Wie ist denn da der Code?
    Ich denke, da ist ein algorithmischer Fehler drin.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @koning3

    koning3 schrieb:

    OK, danke für die vielen Tipps. Manche sind mir eigentlich zu aufwendig. Ich habe eine Lösung mit Lockbits und etwas Mathematik.....

    Naja. Aufwendig ist ja immer relativ. Wenn Du keinen Aufwand haben möchtest, dann müsstest auf eine fertige Komponente setzen. zB Direct2D oder eine andere Grafikbibliothek. Bild rein, Koordinaten angeben, Bild raus. Der einzige Aufwand ist ja hauptsächlich die Brechnung der Pixelpositionen und die Interpolation. Wenn man keine fertige Komponente verwenden möchte, dann bleibt einem ja nur, um noch relativ schnell zu sein, der Weg über LockBits/UnlockBits. k.A. ob Du Dir das Bsp 4PointDistortion angeschaut hast. Der Screenshot zeigt ein so verzertes Bild das an jeder Ecke des Bildes verschoben werden kann. Klar kann man das Bsp sicher noch verbessern damit dieses noch schneller wird. Das Bsp verwendet ebenfalls LockBits/UnlockBits.
    Bilder
    • 4PointDistortion.png

      1,02 MB, 1.237×510, 71 mal angesehen
    Mfg -Franky-
    @-Franky-
    Danke für den Tipp mit 4PointDistortion. Schaue ich mir gerne noch an. Im Augenblick bin ich schon recht nah am Ziel.

    Meine Idee war, in meinem selbst-entwickelten 3D-Programm zumindest die rechtwinkligen Polygonflächen optional mit Bitmaps (Fotos, Grafiken, etc) zu versehen.
    In der GDI+-Zeichenroutine verwende ich dann statt dem FillPolygon meine eigene Routine (mit Lockbits), übernehme aber die gleichen 2D-Koordinaten (Projektion) wie für das Polygon.

    Ich beschreibe mal, wie ich bisher vorgegangen bin (siehe beigefügte Skizze).
    1. In der Zielbitmap definiere ich einen rechtwinkligen Bereich, der die gezerrte Bitmap vollständig umfasst (für x- und y- Schleifen)
    2. Für die Ränder erstelle ich Geradengleichungen X0A (links), X0B (rechts), Y0A (oben) und Y0B (unten)
    3. Ich durchlaufe den rechtwinkligen Zielbereich (x/y) und bestimme die relative Position zwischen X0A und X0B bzw. Y0A und Y0B.
    4. Skaliert mit den Dimensionen der einzufügenden Bitmap finde ich so das korrespondierende Pixel in der einzufügenden Bitmap. Eine Interpolation erspare ich mir ...
    Falls das berechnete Pixel ausserhalb der Bitmap-Dimensionen liegt (was für alle x/y-Werte ausserhalb der gezerrten Fläche zutrifft) übergehe ich die Pixel-Operation.

    Das Verfahren funktoniert eigentlich recht gut, macht aber in manchen Situationen Probleme, die ich nicht erklären kann.
    Da ich die Bitmaps im Raum (Projektion: in der Fläche) drehen kann, müssen die Geradengleichungen für die Ränder dieses berücksichtigen.
    Punkt-1 ist daher immer der Punkt oben links, alle weiteren sind u.a abhängig von der Orientierung (Drehrichtung) des Polygons.

    Wie gesagt: in 95% aller Fälle funktioniert das Verfahren sehr gut und mit erstaunlicher Performance. Aber die restlichen 5% nerven.
    Könnte es sein dass die Lockbit-Methode intern mit Background-Prozessen (z.B. für Marshal.Copy) arbeitet und so Timing-Probleme der Grund sein könnten?

    Habe mal den Quellcode der Lockbit-Routine (als .txt) beigefügt. (Gibt es bessere Wege als .txt ?)
    Anm: Für die Lockbit-Methode habe ich eine eigene Infrastuktur mit Prozeduren für Lock/Unlock/GetPixel/SetPixel, getrennt nach Foreground und Background, entwickelt.
    Bilder
    • 20220209_083536~2.jpg

      1,18 MB, 2.099×3.272, 68 mal angesehen
    Dateien
    @koning3
    Ich habe mal das wichtigste aus der 4PointDistortion zusammengefasst. Was ich hier weggelassen habe ist eine vernünftige Interpolation. Das macht sich beim vergrößern bemerkbar (fehlende Pixel im Zielbitmap) bzw. wenn die DistortionPoints außerhalb der Dimensionen der Originalbitmap liegen. Vllt kannst ja damit was anfangen.
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Option Strict On
    2. Option Explicit On
    3. Imports System.Drawing.Imaging
    4. Imports System.Runtime.InteropServices
    5. Public Class Form1
    6. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    7. Dim BmpWidth As Integer
    8. Dim BmpHeight As Integer
    9. Dim BmpStride As Integer
    10. Dim bmpData As BitmapData
    11. Dim SrcData As Byte() = New Byte() {}
    12. Dim DstData As Byte() = New Byte() {}
    13. Dim UsePixelFormat As PixelFormat = PixelFormat.Format32bppArgb
    14. Dim DistortionPoints As Point() = New Point(3) {}
    15. Using SrcBmp As New Bitmap("D:\Programming\VBC\GDI+\city001.jpg")
    16. bmpData = SrcBmp.LockBits(New Rectangle(0, 0, SrcBmp.Width, SrcBmp.Height),
    17. ImageLockMode.ReadOnly, UsePixelFormat)
    18. BmpWidth = bmpData.Width
    19. BmpHeight = bmpData.Height
    20. BmpStride = bmpData.Stride
    21. Array.Resize(SrcData, BmpStride * BmpHeight)
    22. Array.Resize(DstData, BmpStride * BmpHeight)
    23. Marshal.Copy(bmpData.Scan0, SrcData, 0, SrcData.Length)
    24. SrcBmp.UnlockBits(bmpData)
    25. End Using
    26. DistortionPoints(0) = New Point(0, 0) 'Ecke oben links
    27. DistortionPoints(1) = New Point(BmpWidth, 0) 'Ecke oben rechts
    28. DistortionPoints(2) = New Point(0, BmpHeight) 'Ecke unten links
    29. DistortionPoints(3) = New Point(BmpWidth, BmpHeight) 'Ecke unten rechts
    30. Dim DstBmp As New Bitmap(BmpWidth, BmpHeight, UsePixelFormat)
    31. For intY As Integer = 0 To BmpHeight - 1
    32. For intX As Integer = 0 To BmpWidth - 1
    33. Dim DistortionPoint As Point = GetDistortionPoint(intX, intY, BmpWidth, BmpHeight, DistortionPoints)
    34. Dim SrcDataIndex As Integer = (intY * BmpStride) + (intX * 4)
    35. Dim DstDataIndex As Integer = (DistortionPoint.Y * BmpStride) + (DistortionPoint.X * 4)
    36. If DistortionPoint.Y >= 0 AndAlso DistortionPoint.Y <= BmpHeight - 1 Then
    37. If DistortionPoint.X >= 0 AndAlso DistortionPoint.X <= BmpWidth - 1 Then
    38. DstData(DstDataIndex + 3) = SrcData(SrcDataIndex + 3) 'A
    39. DstData(DstDataIndex + 2) = SrcData(SrcDataIndex + 2) 'R
    40. DstData(DstDataIndex + 1) = SrcData(SrcDataIndex + 1) 'G
    41. DstData(DstDataIndex + 0) = SrcData(SrcDataIndex + 0) 'B
    42. End If
    43. End If
    44. Next
    45. Next
    46. bmpData = DstBmp.LockBits(New Rectangle(0, 0, DstBmp.Width, DstBmp.Height),
    47. ImageLockMode.WriteOnly, UsePixelFormat)
    48. Marshal.Copy(DstData, 0, bmpData.Scan0, DstData.Length)
    49. DstBmp.UnlockBits(bmpData)
    50. PictureBox1.Image = DstBmp
    51. End Sub
    52. Private Function GetDistortionPoint(X As Double, Y As Double, BmpWidth As Double,
    53. BmpHeight As Double, Points As Point()) As Point
    54. Dim X1 As Double = (Points(1).X - Points(0).X) / BmpWidth
    55. Dim X2 As Double = (Points(2).X - Points(0).X) / BmpHeight
    56. Dim X3 As Double = Points(0).X
    57. Dim X4 As Double = (Points(3).X - BmpHeight * X2 - X3 - BmpWidth * X1) / (BmpWidth * BmpHeight)
    58. Dim Y1 As Double = (Points(2).Y - Points(0).Y) / BmpHeight
    59. Dim Y2 As Double = (Points(1).Y - Points(0).Y) / BmpWidth
    60. Dim Y3 As Double = Points(0).Y
    61. Dim Y4 As Double = (Points(3).Y - BmpHeight * Y1 - BmpWidth * Y2 - Y3) / (BmpHeight * BmpWidth)
    62. Dim X5 As Double = X * (Y * X4 + X1) + Y * X2 + X3
    63. Dim Y5 As Double = Y * (X * Y4 + Y1) + X * Y2 + Y3
    64. Return New Point(CInt(X5), CInt(Y5))
    65. End Function
    66. End Class

    Mfg -Franky-

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

    @-Franky-
    Soweit ich den Code verstanden habe, wird das Bild verzerrt, behält aber seine Orientierung (Oben/unten, etc) bei.
    Ausprobiert habe ich ihn aber (noch) nicht. Für mich interessant ist, wie unterschiedlich Code für gleiche Aufgaben (z.B. für Bitlock) aussehen kann.
    Bei meinem Algorithmus kann das Bild bis max. 45 Grad gedreht werden, was den Eindruck einer perspektivischen Darstellung unterstützt. Danach "springen" die Koordinaten-Zuordnungen (X/Y). Hier muss ich noch etwas "feilen", damit der Wechsel reibungsloser verläuft.
    Danke für die Tipps.
    @koning3
    Was mir in diesem ganzen Zusammenhang noch einfällt wäre folgendes. Die Dimensionen der DstBmp mit dem verzerrten Bild ist in meinem Code noch genauso groß wie SrcBmp. Ist vllt nicht immer gewünscht. Man könnte daher noch das verzerrte Bild aus der DstBmp entsprechend ausschneiden und in ein weiteres Bitmap zeichnen. Die Dimensionen dieser neuen Bitmap bzw. wo das Bild aus der DstBmp ausgeschnitten werden muss, lässt sich ja aus den DistortionPoints berechnen.

    koning3 schrieb:

    Soweit ich den Code verstanden habe, wird das Bild verzerrt, behält aber seine Orientierung (Oben/unten, etc) bei. Bei meinem Algorithmus kann das Bild bis max. 45 Grad gedreht werden,

    Bei meinem Code kannst Du jeden Eckpunkt frei verschieben. Das kann man am besten mit dem ersten Downloadlink aus dem Post4 ausprobieren. Einfach mit der Maus in der rechten PictureBox auf eine Ecke des Bildes gehen. Dann wird Dir ja ein rotes Rechteck mit einem roten Punkt angezeigt. Klick auf den Punkt und verschiebe diesen wo Du ihn hinhaben möchtest. Das funktioniert mit allen 4 Ecken des Bildes. Bei genauer Betrachtung ist das ganze eine 4x2 Matrix mit den Defaultwerten {(0,0),(1,0),(0,1),(1,1)} für die entsprechenden Eckpunkt eines Bitmaps ohne Verzerrung.
    Mfg -Franky-

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

    @-Franky- Ich hab mal Deinen Code angesehen.
    Folgende Anmerkung:
    Du gehst durch das Source-Bild und holst zu den Source-Koordinaten die verzerrten Koordinaten, um dort das Source-Pixel hin zu malen.
    Wenn das Ziel-Bild die 4-fache Flache des Ausgangsbildes hat (*2, *2), bekommst Du nur jedes vierte Pixel gemalt und die Fläche ist zu klein (Faktor 4).
    Drehe Deine Logik um:
    Gehe durch die (Um-)Pixel des Ziel-Bildes und hole Dir die Koordinaten des Source-Bildes und male sie da hin.
    So bekommst Du ein Pixel-dicht gefülltes Bild der richtigen Größe. 8o
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @RodFromGermany
    Da gebe ich Dir vollkommen recht. Kann man so auch machen. Wenn Du Dir die 4PointDistortion aus dem Post 4 (erster Link) herunter lädst und im Projekt "Private InvertDistortion As Boolean = False" auf True umstellst, wird das genauso gemacht wie Du es vorgeschlagen hast. Nur in die andere Richtung. Die Dimensionen von Src und Dst bleiben hier aber auch gleich. Man kann es auch so lassen und man muss dann die fehlenden Pixel interpolieren. Dazu habe ich zusätzliche For/Next Schleifen im Projekt 4PointDistortion -> "Mit diesen beiden Schleifen schummeln wir ein wenig" :) Ist jetzt keine richtige Interpolation, aber im groben funktioniert es bis zu einer bestimmten Größe. Hier müsste man die Schleifen an die Vergrößerung entsprechend anpassen. Macht das ganze aber auch wieder etwas langsamer.

    Möglichkeiten zum Verbessern gibt es ja immer. Thx für Deinen Vorschlag. :thumbup: Hau Deinen Code hier mit drunter. Dann ist was im Archiv falls mal wieder jemand nach diesem Thema sucht. :)
    Mfg -Franky-
    @-Franky-
    Ich kann die genannte Projektmappe leider nicht öffnen, da ich noch mit VisualBasic 8 Express Edition arbeite (reichte mir bisher).
    Habe mir aber mal den Code angeschaut. Die wesentliche Leistung des Algorithmus steckt doch in der Function Distortion.
    Das diese nicht kommentiert ist, würde es mich freuen, wenn mir jemand die dahinter liegende Funktionsweise erklären würde.
    Ich könnte den Code zwar in angepasster Form "kopieren", aber schöner ist wenn man versteht was da passiert ;)
    @-Franky-
    Danke für den Link. Duplizität der Fälle! Das ist genau mein Algorithmus. Selbst die Skizze sieht meiner verblüffend ähnlich.
    Nur dass ich (wie auch Rod vorgeschlagen hat) die Schleifen über die Koordinaten der Zielbitmap laufen lasse.

    Da der Algorithmus auf der Proportionalität zu den X- und Y-Koordinaten der Eckpunkte basiert , "kippt" das Ganze, wenn man die Struktur um mehr als 45 Grad (X <-> Y) dreht. Solange man nur einen oder zwei Punkte verschiebt ist alles OK.

    koning3 schrieb:

    "kippt" das Ganze, wenn man die Struktur um mehr als 45 Grad (X <-> Y) dreht
    Mit dem Programm aus dem Link nicht:
    Hier ein Bild, das manuell in mehreren Schritten um 180° gedreht wurde:
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @koning3
    Ich vermute das Du irgendwo in deiner Distortion-Funktion einen Fehler hast. Frag nicht wie lange ich gebraucht habe diese Formeln aus dem Link zu verstehen und irgendwie auf .Net umzusetzen. Jedenfalls hat es dann irgendwann funktioniert. Ich glaube zumindest das ich die Formeln seiner Zeit so richtig umgesetzt habe. Wie auch immer, in dem Link wird ja beschrieben wie das ganze funktionieren soll. Ich würde mir da jetzt nicht so viele Gedanken machen wo In Deiner Distortion-Funktion der Fehler steckt wenn denn eine vorhandene Distortion-Funktion funktioniert.
    Mfg -Franky-

    -Franky- schrieb:

    Frag nicht wie lange ich gebraucht habe diese Formeln aus dem Link zu verstehen und irgendwie auf .Net umzusetzen.
    Was soll das?
    Dein Link aus Post #4 activevb.de/cgi-bin/upload/download.pl?id=3630 ist ein sofort lauffähiges VB.NET-Projekt.
    @koning3 Hole Dir dieses Projekt und feddich.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @RodFromGermany

    RodFromGermany schrieb:

    Was soll das?

    Vllt ist das Projekt auf AVB auch mein Projekt? :P Na was ich sagen wollte ist, bevor ich das Projekt auf AVB hochgeladen habe, habe ich ... ach hatte ich ja schon geschrieben, lange gebraucht usw. :D

    koning3 sagte ja schon das er das Projekt nicht laden kann da er wohl noch mit VS2008 arbeitet.
    Mfg -Franky-