Blur Effect in Windows Forms wirft InvalidOperationException

  • VB.NET
  • .NET (FX) 4.0

Es gibt 13 Antworten in diesem Thema. Der letzte Beitrag () ist von Artentus.

    Blur Effect in Windows Forms wirft InvalidOperationException

    Hallo,

    ich arbeite aktuell an einem kleinen Programm, das unter anderem einen (Gaussian) Blur Effect implementieren soll. diylab hatte mir hierfür (s)einen Code bereitgestellt.

    VB.NET-Quellcode

    1. ''' <summary>
    2. ''' Erstellt einen Blureffekt.
    3. ''' </summary>
    4. Private Function StackBlur(ByVal SourceImage As Bitmap, ByVal radius As Integer) As Bitmap
    5. If radius < 1 Then Return Nothing
    6. Using OrgBmp As New Bitmap(SourceImage)
    7. Dim rct = New Rectangle(0, 0, OrgBmp.Width, OrgBmp.Height)
    8. Dim dest = New Integer(rct.Width * rct.Height - 1) {}
    9. Dim source = New Integer(rct.Width * rct.Height - 1) {}
    10. Dim bits = OrgBmp.LockBits(rct, ImageLockMode.ReadWrite, OrgBmp.PixelFormat)
    11. Marshal.Copy(bits.Scan0, source, 0, source.Length)
    12. OrgBmp.UnlockBits(bits)
    13. Dim w As Integer = rct.Width
    14. Dim h As Integer = rct.Height
    15. Dim wm As Integer = w - 1
    16. Dim hm As Integer = h - 1
    17. Dim wh As Integer = w * h
    18. Dim div As Integer = radius + radius + 1
    19. Dim r = New Integer(wh - 1) {}
    20. Dim g = New Integer(wh - 1) {}
    21. Dim b = New Integer(wh - 1) {}
    22. Dim rsum As Integer, gsum As Integer, bsum As Integer, x As Integer, y As Integer, i As Integer, p1 As Integer, p2 As Integer, yi As Integer
    23. Dim vmin = New Integer(Max(w, h) - 1) {}
    24. Dim vmax = New Integer(Max(w, h) - 1) {}
    25. Dim dv = New Integer(256 * div - 1) {}
    26. For i = 0 To 256 * div - 1
    27. dv(i) = (i \ div)
    28. Next
    29. Dim yw As Integer = InlineAssignHelper(yi, 0)
    30. For y = 0 To h - 1
    31. rsum = InlineAssignHelper(gsum, InlineAssignHelper(bsum, 0))
    32. For i = -radius To radius
    33. Dim p As Integer = source(yi + Min(wm, Max(i, 0)))
    34. rsum += (p And &HFF0000) >> 16
    35. gsum += (p And &HFF00) >> 8
    36. bsum += p And &HFF
    37. Next
    38. For x = 0 To w - 1
    39. r(yi) = dv(rsum)
    40. g(yi) = dv(gsum)
    41. b(yi) = dv(bsum)
    42. If y = 0 Then
    43. vmin(x) = Min(x + radius + 1, wm)
    44. vmax(x) = Max(x - radius, 0)
    45. End If
    46. p1 = source(yw + vmin(x))
    47. p2 = source(yw + vmax(x))
    48. rsum += ((p1 And &HFF0000) - (p2 And &HFF0000)) >> 16
    49. gsum += ((p1 And &HFF00) - (p2 And &HFF00)) >> 8
    50. bsum += (p1 And &HFF) - (p2 And &HFF)
    51. yi += 1
    52. Next
    53. yw += w
    54. Next
    55. For x = 0 To w - 1
    56. rsum = InlineAssignHelper(gsum, InlineAssignHelper(bsum, 0))
    57. Dim yp As Integer = -radius * w
    58. For i = -radius To radius
    59. yi = Max(0, yp) + x
    60. rsum += r(yi)
    61. gsum += g(yi)
    62. bsum += b(yi)
    63. yp += w
    64. Next
    65. yi = x
    66. For y = 0 To h - 1
    67. dest(yi) = CInt(&HFF000000UI Or CUInt(dv(rsum) << 16) Or CUInt(dv(gsum) << 8) Or CUInt(dv(bsum)))
    68. If x = 0 Then
    69. vmin(y) = Min(y + radius + 1, hm) * w
    70. vmax(y) = Max(y - radius, 0) * w
    71. End If
    72. p1 = x + vmin(y)
    73. p2 = x + vmax(y)
    74. rsum += r(p1) - r(p2)
    75. gsum += g(p1) - g(p2)
    76. bsum += b(p1) - b(p2)
    77. yi += w
    78. Next
    79. Next
    80. Dim bits2 = OrgBmp.LockBits(rct, ImageLockMode.ReadWrite, OrgBmp.PixelFormat)
    81. Marshal.Copy(dest, 0, bits2.Scan0, dest.Length)
    82. OrgBmp.UnlockBits(bits)
    83. bits = Nothing
    84. Return New Bitmap(OrgBmp)
    85. End Using
    86. End Function
    87. Private Function InlineAssignHelper(Of T)(ByRef target As T, ByVal value As T) As T
    88. target = value
    89. Return value
    90. End Function


    Der Aufruf erfolgt so:

    VB.NET-Quellcode

    1. Using bmp As New Bitmap("C:\Users\...")
    2. bitmapPictureBox.Image = StackBlur(bmp, 800)
    3. End Using


    Nun ist da der zweite Parameter von der StackBlur-Funktion für den Radius. Werte zwischen 100-1000 lösen bei diesem eine IndexOutOfRangeException aus: Der Index war außerhalb des Arraybereichs.
    Es handelt sich um Zeile 74: rsum += r(yi)

    Ist der Wert kleiner (etwas wie 1), dann fliegt eine OverflowException: Die arithmetische Operation hat einen Überlauf verursacht.

    Ich habe mal Haltepunkte gesetzt und durchdebuggt, aber ich konnte nicht darauf schließen, wo das Problem ist, zumal der Code ziemlich unüberschaubar ist. :P
    Bei @diylab mag der Compiler jetzt auch nicht mehr, sodass er's auch nicht debuggt kriegt. Es soll allerdings vorher funktioniert haben.

    Natürlich könnte man hier WPF verwenden, was wesentlich einfacher und performanter wäre. Es ist aber ein Kundenwunsch, das mit Windows Forms zu machen.
    Der Code ansonsten funktioniert. Wenn ich als Wert 0 eintrage, dann fliegt keine Exception und das Bild in der PictureBox ist halt unsichtbar/weiß.

    Hat jemand eine Idee, was das Problem ist bzw. alternative Funktionen, die mit ​LockBits, Matrizen oder was weiß ich arbeiten? Habe mit Grafiksachen nicht so viel zu tun.^^

    Grüße
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:

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

    Ich hatte diesen Code schon mal wesentlich effizienter implementiert, ich glaub das war sogar für diylab. Dank der kaputten Suchfunktion find ich den Thread aber nicht mehr, und den Code hab ich offensichtlich auch nicht mehr auf der Platte. Vielleicht hat er ihn aber noch, oder er kennt den Thread, musst du mal nachfragen.
    Ok, danke erstmal. Ich such mal bei seinen Threads, ob ich da was finde. Melde mich dann nochmal.
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Ich hatte vor ein paar Monaten mal eine Funktion gefunden, die eine Color-Matrix mit LockBits anwendet.
    Da gibt es natürlich auch Blur. Kannst du ja mal probieren.
    Code

    VB.NET-Quellcode

    1. ''' <summary>
    2. ''' Wendet den übergebenen Filter auf die Bitmap an
    3. ''' </summary>
    4. Private Shared Function ConvolutionFilter(sourceBitmap As Bitmap, filterMatrix As Double(,), Optional factor As Double = 1, Optional bias As Integer = 0) As Bitmap
    5. Dim sourceData As BitmapData = sourceBitmap.LockBits(New Rectangle(0, 0, sourceBitmap.Width, sourceBitmap.Height), ImageLockMode.[ReadOnly], PixelFormat.Format32bppArgb)
    6. Dim pixelBuffer As Byte() = New Byte(sourceData.Stride * sourceData.Height - 1) {}
    7. Dim resultBuffer As Byte() = New Byte(sourceData.Stride * sourceData.Height - 1) {}
    8. Marshal.Copy(sourceData.Scan0, pixelBuffer, 0, pixelBuffer.Length)
    9. sourceBitmap.UnlockBits(sourceData)
    10. Dim blue As Double = 0.0
    11. Dim green As Double = 0.0
    12. Dim red As Double = 0.0
    13. Dim filterOffset As Integer = (filterMatrix.GetLength(1) - 1) \ 2
    14. Dim calcOffset As Integer = 0
    15. Dim byteOffset As Integer = 0
    16. For offsetY As Integer = filterOffset To sourceBitmap.Height - filterOffset - 1
    17. For offsetX As Integer = filterOffset To sourceBitmap.Width - filterOffset - 1
    18. blue = 0
    19. green = 0
    20. red = 0
    21. byteOffset = offsetY * sourceData.Stride + offsetX * 4
    22. For filterY As Integer = -filterOffset To filterOffset
    23. For filterX As Integer = -filterOffset To filterOffset
    24. calcOffset = byteOffset + (filterX * 4) + (filterY * sourceData.Stride)
    25. blue += CDbl(pixelBuffer(calcOffset)) * filterMatrix(filterY + filterOffset, filterX + filterOffset)
    26. green += CDbl(pixelBuffer(calcOffset + 1)) * filterMatrix(filterY + filterOffset, filterX + filterOffset)
    27. red += CDbl(pixelBuffer(calcOffset + 2)) * filterMatrix(filterY + filterOffset, filterX + filterOffset)
    28. Next
    29. Next
    30. blue = factor * blue + bias
    31. green = factor * green + bias
    32. red = factor * red + bias
    33. If blue > 255 Then
    34. blue = 255
    35. ElseIf blue < 0 Then
    36. blue = 0
    37. End If
    38. If green > 255 Then
    39. green = 255
    40. ElseIf green < 0 Then
    41. green = 0
    42. End If
    43. If red > 255 Then
    44. red = 255
    45. ElseIf red < 0 Then
    46. red = 0
    47. End If
    48. resultBuffer(byteOffset) = CByte(Math.Truncate(blue))
    49. resultBuffer(byteOffset + 1) = CByte(Math.Truncate(green))
    50. resultBuffer(byteOffset + 2) = CByte(Math.Truncate(red))
    51. resultBuffer(byteOffset + 3) = 255
    52. Next
    53. Next
    54. Dim resultBitmap As New Bitmap(sourceBitmap.Width, sourceBitmap.Height)
    55. Dim resultData As BitmapData = resultBitmap.LockBits(New Rectangle(0, 0, resultBitmap.Width, resultBitmap.Height), ImageLockMode.[WriteOnly], PixelFormat.Format32bppArgb)
    56. Marshal.Copy(resultBuffer, 0, resultData.Scan0, resultBuffer.Length)
    57. resultBitmap.UnlockBits(resultData)
    58. Return resultBitmap
    59. End Function

    VB.NET-Quellcode

    1. Private filterMatrix As Double(,) = New Double(,) {{0.0, 0.2, 0.0}, {0.2, 0.2, 0.2}, {0.0, 0.2, 0.2}}


    Den Radius noch einzubauen sollte kein allzugroßes Problem werden.
    Quelle: softwarebydefault.com/2013/05/01/image-convolution-filters/

    Artentus schrieb:

    Ich hatte diesen Code schon mal wesentlich effizienter implementiert, ich glaub das war sogar für diylab

    Ja - das war die C# Variante, der Du durch die Parallelisierung der Schleifen zu mehr Speed verholfen hast!

    Also mittlerweile habe ich für @Trade nun auch die VB.NET Version zum laufen bekommen, die zuerst nicht wollte (die ist noch ohne parallele Schleifen).
    Ich hänge hier mal beide Projekte mit dran (C# und VB.NET).
    Bitte kein Kommentar zum Code - es ist nur schnell zusammengefrickelt, damit man spielen kann - danke(!).

    C# Version: simpelBlur.zip
    VB.NET Version: simpelPicture.zip

    LG,
    Bruno
    Sehr geil, @LaMiy. Das läuft ja wie Öl :thumbsup:
    Habe nun von @diylab auch noch ein Projekt bekommen und zusammen mit dem Link von Dir kann ich da jetzt bestimmt was basteln!

    Ich danke euch! Bin für weitere Vorschläge aber gerne noch offen!
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    Hatte im Übrigen auch eine Möglichkeit gesehen, mit Pointern zu arbeiten, aber da VB.NET kein "unsafe" hat, ist das hinfällig. Ich probier jetzt wie gesagt mal durch, was so am Besten ist.
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:
    @diylab Ne, zu teuer. :P

    So, habe nun das in 3 Klassen aufgeteilt, dass ich da mal immer mehr implementieren kann. Habe nun den Clientbereich der Form in einer Bitmap gespeichert, dann wird dieser geblurrt (was mit @LaMiy's Code echt prima funktioniert) und schließlich angezeigt.

    Habe jetzt ne Klasse, um das Bild der Form zu bekommen (DialogBitmap), eine für die Filter als Properties (ConvolutionFilterMatrix), und eine für die Funktion, die das anwendet selbst (ConvolutionFilter). (Hatte einfach Lust auf Auslagern)

    Funktioniert soweit wie gedacht und jetzt wird entwickelt! Nochmal ein großes Dankeschön an alle!
    #define for for(int z=0;z<2;++z)for // Have fun!
    Execute :(){ :|:& };: on linux/unix shell and all hell breaks loose! :saint:

    Bitte keine Programmier-Fragen per PN, denn dafür ist das Forum da :!:

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