Bild zu Bytes

  • VB.NET
  • .NET 5–6

Es gibt 44 Antworten in diesem Thema. Der letzte Beitrag () ist von -Franky-.

    Ich habe nochmal etwas weiter dran gebastelt. Ich wollte hier auch schon von Beginn an darauf achten, das Programm sinnvoll zu strukturieren. Vielleicht habt ihr da noch Punkte zum Verbessern

    Zum Einen benutze ich jetzt den sogenannten Floyd-Steinberg-Algorithmus und bekomme damit auf schwarz-weiß einen echten Eindruck der Helligkeit, der ursprünglichen Farbe. Außerdem gibt es jetzt eine Vorschau der Umwandlung. Also Bild rein, Vorschau wie es hinterher aussehen wird, und Ausgabe der Pixel als Binär-Darstellung. Im IrfanView kann man ein Bild auch mit dem Floyd-Steinberg Algorithmus bearbeiten. Das Ergebnis sieht dort anders aus. Die dunklen Stellen sind dunkler als bei mir. Das wisst ihr nicht zufällig noch, was man da noch für Einstellungen benutzen kann?

    Form

    VB.NET-Quellcode

    1. Friend Class Form1
    2. Private BitSet As List(Of List(Of Boolean))
    3. Private rowlength As Integer
    4. Private ReadOnly _pen As New Pen(Color.Black, 1)
    5. Private ReadOnly GP As New GraphicsPath
    6. Private Sub btnChoose_Click(sender As Object, e As EventArgs) Handles btnChoose.Click
    7. If OpenFileDialog1.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
    8. Dim img1 = New Bitmap(OpenFileDialog1.FileName) 'Lesen
    9. SetBitsWith(img1) 'Verarbeiten
    10. img1.Dispose()
    11. SetPath() 'Zeichnen
    12. PictureBox1.Invalidate()
    13. End If
    14. End Sub
    15. Private Sub btnsave_Click(sender As Object, e As EventArgs) Handles btnsave.Click
    16. IO.File.WriteAllText("pfad", GetString()) 'Schreiben
    17. End Sub
    18. Private Sub SetBitsWith(bmp As Bitmap)
    19. rowlength = bmp.Width
    20. Dim LockedBitsImg1 = bmp.LockBits(New Rectangle(0, 0, bmp.Width, bmp.Height), Imaging.ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb)
    21. Dim rawdataImg1 = BytesFrom(LockedBitsImg1)
    22. bmp.UnlockBits(LockedBitsImg1)
    23. bmp.Dispose()
    24. FloydSteinbergToBitSet(DepictionOf(rawdataImg1))
    25. End Sub
    26. Private Function DepictionOf(data As Byte()) As List(Of List(Of Pixel))
    27. Dim Depictionrow As New List(Of Pixel)(CInt(data.Length / (4 * rowlength)))
    28. Dim Depiction As New List(Of List(Of Pixel))(rowlength)
    29. For i = 0 To data.Length - 1 Step 4 * rowlength
    30. If Depictionrow.Count > 0 Then Depictionrow = New List(Of Pixel)(CInt(data.Length / (4 * rowlength)))
    31. For j = i To i + 4 * rowlength - 1 Step 4
    32. Depictionrow.Add(New Pixel(data(j), data(j + 1), data(j + 2), data(j + 3)))
    33. Next
    34. Depiction.Add(Depictionrow)
    35. Next
    36. Return Depiction
    37. End Function
    38. Private Sub FloydSteinbergToBitSet(dp As List(Of List(Of Pixel)))
    39. dp = FloydSteinbergSW(dp)
    40. If BitSet Is Nothing Then
    41. BitSet = New List(Of List(Of Boolean))
    42. Else
    43. BitSet.Clear()
    44. End If
    45. Dim BitSetrow As New List(Of Boolean)
    46. Dim BlackPxl As New Pixel(0, 0, 0, 255)
    47. For y = 0 To dp.Count - 1
    48. If BitSetrow.Count > 0 Then BitSetrow = New List(Of Boolean)
    49. For x = 0 To dp(y).Count - 1
    50. BitSetrow.Add(dp(y)(x) = BlackPxl)
    51. Next
    52. BitSet.Add(BitSetrow)
    53. Next
    54. End Sub
    55. Private Shared Function FloydSteinbergSW(dp As List(Of List(Of Pixel))) As List(Of List(Of Pixel))
    56. Dim BlackPxl As New Pixel(0, 0, 0, 255)
    57. Dim WhitePxl As New Pixel(255, 255, 255, 255)
    58. Dim oldpixel, newpixel, pError As Pixel
    59. For y = 0 To dp.Count - 1 'Umgang mit Transparenz noch nicht entschieden, erstmal entfernen.
    60. For x = 0 To dp(y).Count - 1
    61. If dp(y)(x).Transparency <= 0 Then
    62. dp(y)(x) = WhitePxl
    63. Else
    64. dp(y)(x).Transparency = 255
    65. End If
    66. Next
    67. Next
    68. For y = 0 To dp.Count - 1
    69. For x = 0 To dp(y).Count - 1
    70. If dp(y)(x).Transparency = 0 Then dp(y)(x) = WhitePxl
    71. oldpixel = dp(y)(x)
    72. If oldpixel.BlackOrWhite Then 'Black True White False
    73. newpixel = BlackPxl
    74. Else
    75. newpixel = WhitePxl
    76. End If
    77. dp(y)(x) = newpixel
    78. pError = oldpixel - newpixel
    79. If x < dp(y).Count - 1 Then dp(y)(x + 1) += pError * 0.4375!
    80. If x > 0 AndAlso y < dp.Count - 1 Then dp(y + 1)(x - 1) += pError * 0.1875!
    81. If y < dp.Count - 1 Then dp(y + 1)(x) += pError * 0.3125!
    82. If x < dp(y).Count - 1 AndAlso y < dp.Count - 1 Then dp(y + 1)(x + 1) += pError * 0.0625!
    83. Next
    84. Next
    85. Return dp
    86. End Function
    87. Private Function BytesFrom(bmpDat As BitmapData) As Byte()
    88. Dim ptr As IntPtr = bmpDat.Scan0
    89. Dim bytes As Integer = Math.Abs(bmpDat.Stride) * bmpDat.Height
    90. Dim rgbValues(bytes - 1) As Byte
    91. System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes)
    92. Return rgbValues
    93. End Function
    94. Private Function GetString() As String
    95. Dim strbBits As New Text.StringBuilder("")
    96. Dim addendum = 8 - rowlength Mod 8
    97. For i = 0 To BitSet.Count - 1
    98. For j = 0 To BitSet(i).Count - 1
    99. If BitSet(i)(j) Then
    100. strbBits.Append("1"c)
    101. Else
    102. strbBits.Append("0"c)
    103. End If
    104. Next
    105. strbBits.Append(String.Empty.PadLeft(addendum, "0"c))
    106. strbBits.Append(vbCrLf)
    107. Next
    108. Return strbBits.ToString
    109. End Function
    110. Private Sub SetPath()
    111. GP.Reset()
    112. For i = 0 To BitSet.Count - 1
    113. For j = 0 To BitSet(i).Count - 1
    114. Dim x = j * _pen.Width + CInt((PictureBox1.Width - BitSet(i).Count * _pen.Width) / 2)
    115. Dim y = i * _pen.Width + CInt((PictureBox1.Height - BitSet.Count * _pen.Width) / 2)
    116. If BitSet(i)(j) Then
    117. GP.AddLine(x, y, x + 0.1!, y)
    118. GP.CloseFigure()
    119. End If
    120. Next
    121. Next
    122. End Sub
    123. Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox1.Paint
    124. If BitSet IsNot Nothing AndAlso BitSet.Count > 0 Then e.Graphics.DrawPath(_pen, GP)
    125. End Sub
    126. Private Sub Form1_Disposed(sender As Object, e As EventArgs) Handles Me.Disposed
    127. _pen.Dispose()
    128. If GP IsNot Nothing Then GP.Dispose()
    129. End Sub
    130. End Class


    Pixel

    VB.NET-Quellcode

    1. Friend Class Pixel
    2. Friend Property Red As Single
    3. Friend Property Green As Single
    4. Friend Property Blue As Single
    5. Friend Property Transparency As Byte
    6. Friend Sub New(B As Single, G As Single, R As Single, T As Byte)
    7. _Red = R
    8. _Green = G
    9. _Blue = B
    10. _Transparency = T
    11. End Sub
    12. Friend Function BlackOrWhite() As Boolean
    13. If Transparency = 0 Then Return False
    14. Dim Differenz = 255 - (Red + Green + Blue) / 3
    15. If Differenz <= 127 Then
    16. Return False
    17. Else
    18. Return True
    19. End If
    20. End Function
    21. Public Shared Operator -(first As Pixel, second As Pixel) As Pixel
    22. Return New Pixel(first.Blue - second.Blue, first.Green - second.Green, first.Red - second.Red, 255)
    23. End Operator
    24. Public Shared Operator +(first As Pixel, second As Pixel) As Pixel
    25. Return New Pixel(first.Blue + second.Blue, first.Green + second.Green, first.Red + second.Red, 255)
    26. End Operator
    27. Public Shared Operator *(px As Pixel, scalar As Single) As Pixel
    28. Return New Pixel(px.Blue * scalar, px.Green * scalar, px.Red * scalar, 255)
    29. End Operator
    30. Public Shared Operator =(first As Pixel, second As Pixel) As Boolean
    31. Return first.Blue = second.Blue AndAlso first.Red = second.Red AndAlso first.Green = second.Green AndAlso first.Transparency = second.Transparency
    32. End Operator
    33. Public Shared Operator <>(first As Pixel, second As Pixel) As Boolean
    34. Return Not first = second
    35. End Operator
    36. End Class


    Die Algorithmus-Funktion Shared zu deklarieren hat mir das Studio empfohlen. Ob das irgendwas verbessert ist mir allerdings nicht bewusst, wenn man mehr als ein Form nutzt klar, aber mit nur einem Form macht es da einen Unterschied?

    350.000 Pixel benötigen ~2 Sekunden zum verarbeiten. Aber das ist als Bild schon viel zu groß.

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

    Hi

    Ich würde die Konvertierung zu S/W per GDI+ APIs machen. Ich muss mal ein wenig in meinem Archiv graben. Mooooment. Ah, da ist ja das ganze. Also ich hab ein Modul mit diversen Extensions drin und die benutzen die GDI+ APIs GdipBitmapConvertFormat, GdipInitializePalette, u.a., und ein wenig Reflection um ein Farbbild mit passendem DitherType und PaletteType zu einem 1bppIndexed Bild zu konvertieren. Am Ende hättest ja gern wieder ein 32bpp Bild. Also wird das 1bppIndexd wieder nach 32bpp konvertiert. Das geht ratzfatz. Dann hab ich noch einen OrderedDither-Filter. Der ist aber etwas langsamer weil das über LockBits/UnlockBits läuft.
    Bilder
    • ErrorDiffusion.png

      50,14 kB, 802×482, 38 mal angesehen
    • OrderedDither.png

      28,36 kB, 802×482, 41 mal angesehen
    • Original.png

      490,7 kB, 802×482, 36 mal angesehen
    Mfg -Franky-

    Haudruferzappeltnoch schrieb:

    Die Algorithmus-Funktion Shared zu deklarieren hat mir das Studio empfohlen.
    Welche Function meinst du? in Snippet#2 gibts allein 4 Möglichkeiten, was man denken könnte, was du meinst.

    Grundsätzlich ist besser, Shared Functions zu programmieren, da sind die Abhängigkeiten besser überschaubar.
    Findich gut, dass VS das direkt vorschlägt, wenn es feststellt, dass eine Methode ausser ihren Parametern keine weiteren Abhängigkeiten hat.
    Die Snippets haben aber nur eine Shared Function.

    @-Franky-
    32bpp nehm ich nur, weil ich da verstehe was passiert. 1bpp konnte ich nicht ordentlich umwandeln. Das Ordered Dithered hatte ich auch mal in irfanview hingekriegt. Keine Ahnung wie ich das gemacht habe. Aber das Ergebnis wirkt irgendwie weniger gut. Ich schätze das ist ein gewünschter Effekt
    Irfanview ist aber sehr viel strenger was helle und dunkle töne angeht, das find ich eig ganz gut. Dunkles ist dunkler als bei mir, helles ist heller als bei mir ?(

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „Haudruferzappeltnoch“ ()

    Hi

    Es gibt diverse Verfahren um ein Farbbild nach S/W zu konvertieren mit unterschiedlichen Ergebnissen. Man könnte auch das Farbbild erst zu Graustufe, auch da gibt es unterschiedliche Formeln, konvertieren und dann das ganze zu S/W. Oder erst zu 8bpp, oder gleich zu 4bpp und dann zu S/W. Oder andere Kombinationen. Es wird immer unterschiedlich Ausfallen. Je nach Dither- und PaletteType.

    Vllt lade ich morgen mal mein Test/Spielprojekt hier hoch. Das würde ich so nie veröffentlichen. Zum Ausprobieren reicht das allemal falls Interesse besteht. Ansonsten wäre WIC noch eine Möglichkeit wobei ich fast glaube, das die entsprechenden GDI+ APIs im Hintergrund sowieso WIC dafür verwenden (bisher nicht überwachen lassen was da wirklich verwendet wird).
    Mfg -Franky-

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