GDI Problem

  • VB.NET
  • .NET (FX) 4.5–4.8

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

    GDI Problem

    Hi,

    Ich habe eine Routine, die wandelt jpg Files in eine Bitmap um, damit ich sie in einer Picture Box anzeigen kann.

    Das ist die Routine zum Umwandeln .jpg nach Bitmap.

    VB.NET-Quellcode

    1. Imports System.ComponentModel
    2. Public Class Form1
    3. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    4. Dim RealObject As String = "P:\+++mypath+++\problem.jpg"
    5. Dim bytData(-1) As Byte 'Allocate empty byte array
    6. Dim bitmap1 As Bitmap
    7. Try
    8. bytData = IO.File.ReadAllBytes(RealObject) 'Get object as byte array
    9. Dim converter As TypeConverter = TypeDescriptor.GetConverter(GetType(Bitmap))
    10. bitmap1 = DirectCast(converter.ConvertFrom(bytData), Bitmap) 'Convert byte array to bitmap
    11. MessageBox.Show("Bitmap OK")
    12. Catch ex As Exception
    13. MessageBox.Show(ex.Message)
    14. End Try
    15. End Sub
    16. End Class


    Die Routine habe ich mit hunderten von Files problemlos verwendet. Aber jetzt habe ich Probleme mit einem File "problem.jpg". (s.Anhang)

    Die bitmap1 = DirectCast .... Anweisung liefert eine "System.ArgumentException" in System.Drawing.dll. Die Exeption Message lautet "Parameter is not valid." Das ist leider nicht sehr hilfreich.

    Normalerweise würde ich jetzt annehmen, dass die .jpg Datei beschädigt ist. Aber mit einem Bildbetrachter kann ich das Dingens problemlos öffnen. Auch hier im Forum kann ich den Anhang problemlos anzeigen lassen.

    Wenn Windows das Ding anzeigen kann, sollte das .Net doch eigentlich auch können.

    Weiß jemand was hier falsch läuft. Und noch besser: wie kann ich das Problem handhaben.

    LG
    Peter

    *Topic verschoben*
    Dateien
    • problem.jpg

      (32,19 kB, 69 mal heruntergeladen, zuletzt: )

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Marcus Gräfe“ ()

    Jau ... das habe ich jetzt von dir gelernt, dass es da ein proprietäres Format von Google gibt ... Die Picture Viewer im Windows unterstützen dieses Format... meine .Net Routine hingegeben nicht.

    Die erste Frage ist jetzt: wie erkenne ich das WEBP Format. Das müsste man aus dem Header ablesen können Ich hab mal einen Hexdump des WEBP Files und eines JPEG Files angehängt. Da fallen Unterschiede auf ... aber man muss natürlich sicher wissen, was man abfragen muss.

    Die zweite Frage ist: wie kann ich so einen File mit .NET anzeigen ? Ich habe jetzt MSPAINT aufgerufen ... mit SAVE AS kann man dem Dingens ein anderes Format zuweisen und dann abspeichern. Aber das ist natürlich alles andere als elegant. Und vor allem wächst das Volumen dabei um den Faktor 2.

    MIt Google bin ich unter "WEBP nach Bitmap konvertieren" auch nicht so richtig fündig geworden Es wird eine Routine WebPFormat erwähnt, aber die habe ich nicht finden können.

    Weiß jemand, wie man WebP Files sicher erkennt und wie man solche Files mit .NET in eine Bitmap zum Anzeigen des Bildchens konvertieren kann ?

    LG
    Peter
    Bilder
    • WEBP Format.jpg

      31,53 kB, 913×110, 86 mal angesehen
    • JPG Format.jpg

      29,53 kB, 913×110, 96 mal angesehen
    @Peter329 Muss diese Datei von Deinem Programm konvertiert werden?
    Wenn Nein: Konvertiere sie im IrfanView (ggf. nach Update).
    Wenn Ja: Verwende WPF-Image-Klassen, die haben bei mir noch alles gelesen: BitmapFrame, BitmapDecoder.
    ===
    Nö, kanner (noch) nicht.
    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!
    Die Konvertierung klappt mit MSPAINT problemlos

    Aber ich will die Datei nach Möglichkeit so belassen wie sie ist. Ich möchte sie halt nur in eine Bitmap laden, damit ich sie anzeigen kann !

    LG
    Peter
    ad 1: jau, das mit dem HEADER ist genau die Sache, die ich gesucht hatte. So kann ich unabhängig von der Extension nachgucken, was das für ein File ist. Supi !

    ad2: Genau auf das Dingens von Jose Pineiro bin ich auch schon gestoßen. Aber ich bin einfach zu blöd, um zu verstehen wie ich das anstellen soll ?

    Muss ich da C# Code herunterladen und eine .dll kompilieren ?

    Oder soll da eine .dll Datei heruntergeladen werden, die Ich dann mit Imports meiner Form hinzufüge ?

    Ich habe offen gestanden nicht den geringsten Plan, was ich da tun soll. Wie schon gesagt, wahrscheinlich bin ich dafür einfach zu blöd. :)

    Na, vielleicht kann mir ja jemand nachsichtig auf die Sprünge helfen.

    LG
    Peter
    @Peter329 Ich hab mein Projekt unter Win10-64 mit dem 2017er Studio compiliert (Framework 4),
    es hat das Bild anstandslos geladen, das Format ist 32 BPP.
    Bei mir wird das allerdings nach 8 oder 16 BPP Mono konvertiert, wegen der Bildverarbeitung.


    Spoiler anzeigen

    C#-Quellcode

    1. using (Stream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
    2. {
    3. BitmapFrame bmp = null;
    4. try
    5. {
    6. BitmapDecoder decoder = null;
    7. decoder = BitmapDecoder.Create(stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None);
    8. bmp = decoder.Frames[0];
    9. }
    10. catch
    11. {
    12. // not supported format
    13. return false;
    14. }
    15. width = bmp.PixelWidth;
    16. height = bmp.PixelHeight;
    17. int stride = bmp.PixelWidth;
    18. // Speicher für das Bild
    19. values = new ushort[height * width];
    20. // bmp.Format ist kein enum
    21. if (bmp.Format == PixelFormats.Gray16)
    22. {
    23. bmp.CopyPixels(values, stride * 2, 0);
    24. }
    25. else if (bmp.Format == PixelFormats.Gray8 ||
    26. bmp.Format == PixelFormats.Indexed8)
    27. {
    28. // 8 BPP-Formate werden explizit als solche eingelesen
    29. byte[] pixels8 = new byte[bmp.PixelHeight * bmp.PixelWidth];
    30. bmp.CopyPixels(pixels8, stride, 0);
    31. // convert 8 BBP to 16 BBP
    32. for (int i = 0; i < values.Length; i++)
    33. {
    34. values[i] = pixels8[i];
    35. }
    36. }
    37. else
    38. {
    39. // 16 BPP *.bmp landet hier
    40. // Alle anderen Formate werden explizit nach 16 BPP Grau konvertiert
    41. FormatConvertedBitmap newImage = new FormatConvertedBitmap(bmp, PixelFormats.Gray16, null, 0.0);
    42. newImage.CopyPixels(values, stride * 2, 0);
    43. newImage = null;
    44. }
    45. return true;
    46. }
    Dazu

    C#-Quellcode

    1. using System.Windows;
    2. using System.Windows.Media;
    3. using System.Windows.Media.Imaging;
    und die entsprechenden DLLs:
    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!
    Erst mal Danke für dein Code Beispiel. Ich hab das mal versucht umzusetzen:

    VB.NET-Quellcode

    1. Dim Stream As New FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)
    2. Dim bmp As BitmapFrame = Nothing
    3. Dim bitmap1 As Bitmap
    4. Try
    5. Dim decoder As BitmapDecoder = Nothing
    6. decoder = BitmapDecoder.Create(Stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None)
    7. bmp = decoder.Frames(0)
    8. Dim converter As TypeConverter = TypeDescriptor.GetConverter(GetType(Bitmap))
    9. bitmap1 = DirectCast(converter.ConvertFrom(bmp), Bitmap)
    10. PictureBox1.Image = bitmap1
    11. Return True
    12. Catch ex As Exception
    13. 'Unsupported format
    14. MessageBox.Show(ex.Message)
    15. Return False
    16. End Try
    17. PictureBox1.Image = bitmap1
    18. Return True


    Das funzt leider nicht, weil die DirectCast Anweisung scheitert, weil bmp ein BitmapFRAME ist.

    Jetzt habe ich offen gestanden keine Ahnung was der Unterschied zwischen einer BitmapFrame und einer Bitmap ist. Und deshalb weiß ich schon gar nicht wie ich das beheben soll.

    Möglicherweise brauche ich da doch Teile von deiner GrauKonvertierung .... (die ich einfach weggelassen habe :) )

    LG
    Peter
    @Peter329 Bitmap kommt bei mir nicht vor.
    Du musst die Bytes aus dem BitmapFrame in das Pixel-Array Deiner korrekt (Größe, Pixelformat) formatierten Bitmap-Instanz kopieren,
    nutze dazu Bitmap.LockBits und
    System.Runtime.InteropServices.Marshal.Copy:
    docs.microsoft.com/de-de/dotne…its?view=netframework-4.8
    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!
    Also: ich habe mal versucht aus deinen Hinweisen schlau zu werden. Hier mein (etwas hilfloser) Versuch:

    VB.NET-Quellcode

    1. Dim Stream As New FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)
    2. Dim bmp As BitmapFrame = Nothing
    3. Try
    4. Dim decoder As BitmapDecoder = Nothing
    5. decoder = BitmapDecoder.Create(Stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None)
    6. bmp = decoder.Frames(0)
    7. 'Speicher für das Bild
    8. Dim myWidth As Integer = bmp.PixelWidth
    9. Dim myHeight As Integer = bmp.PixelHeight
    10. Dim myValues(myWidth * myHeight) As UShort
    11. Dim myStride As Integer = bmp.PixelWidth
    12. 'Rectangle over picture
    13. Dim rect As New Rectangle(0, 0, bmp.Width, bmp.Height)
    14. 'Lock the bitmap's bits.
    15. Dim bmpData As System.Drawing.Imaging.BitmapData =
    16. bmp.LockBits(Rect, Drawing.Imaging.ImageLockMode.ReadWrite, bmp.PixelFormat)
    17. 'Get the address of the first line.
    18. Dim ptr As IntPtr = bmpData.Scan0
    19. 'Declare an array to hold the bytes of the bitmap.
    20. 'This code is specific to a bitmap with 24 bits per pixels.
    21. Dim bytes As Integer = Math.Abs(bmpData.Stride) * bmp.Height
    22. Dim rgbValues(bytes - 1) As Byte
    23. ' Copy the RGB values into the array.
    24. System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes)


    Den Speicher und das Rectangle habe ich hoffentlich richtig definiert.

    Das Befüllen von BitmapData mit .LockBits klappt aber nicht. ("Lockbits" / "PixelFormat" ist kein Member von "BitmapFrame").

    Ganz allgemein fehlt mir der Background der Begriffe "Bitmap", "BitmapFrame" und "BitmapData".

    Das weitere Coding mit dem .Marshal.Copy habe ich aus der Microsoft Doku übernommen. Das ist zwar syntaktisch in Ordnung ... aber was da gemacht wird, verstehe ich nicht ...

    Schade ... irgendwie komme ich mit deinen gut gemeinten Ratschlägen nicht so richtig weiter ...

    LG
    Peter
    Mit der BmpBitmapEncoder-Klasse unter VS2019 probiert:

    VB.NET-Quellcode

    1. Imports System.Windows.Media.Imaging 'Verweis: WindowBase. PresentationCore
    2. Imports System.IO
    3. Public Class Form1
    4. Dim Pb As New PictureBox With {.Dock = DockStyle.Fill, .Parent = Me}
    5. Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    6. Me.ClientSize = New System.Drawing.Size(600, 400)
    7. Using fs As New FileStream("D:/VBGrafik/Test.webp", FileMode.Open, FileAccess.Read, FileShare.Read), ms As New MemoryStream
    8. Dim dec = BitmapDecoder.Create(fs, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None)
    9. Dim enc As New BmpBitmapEncoder
    10. enc.Frames.Add(BitmapFrame.Create(dec.Frames(0)))
    11. enc.Save(ms)
    12. Pb.Image = New Bitmap(ms)
    13. End Using
    14. End Sub
    15. End Class


    MfG GPM
    Wow ... das hat auf Anhieb funktioniert ! :)

    Ich habe zwar noch VS2015 .. aber das dürfte unerheblich sein, denn das ist ja nur das Level der IDE ... maßgeblich ist wohl eher das Level der .NET Umgebung.

    Auch wenn mein Problem damit behoben ist, würde ich jetzt doch noch gern das mit den "Frames" verstehen !

    Soweit meine ich das verstanden zu haben:

    Der BitmapDecoder dec decodiert den Filestream fs, der auf das Bildchen weist, mit bestimten Optionen.

    BitmapFrame.Create liest dec.Frame(0) (wieso Frame(0) ???) und fügt ein Element an das BmpBitmapEncoder Object enc an. (Wieso BmpBitmap ??? Bmp ist doch schon die Abkürzung von "Bitmap" ???? Und was sind "Frames" ????)

    Mit enc.Save wird dieses Element als Memory Stream ms abgespeichert.

    Bitmap.Create verwendet diesen Memory Stream, um eine Bitmap zu erzeugen, die dann in die PictureBox geladen wird.

    Vielleicht ist jemand so freundlich und füllt meine Wissenslücken ...

    LG
    Peter

    @RFG: An der Umsetzung deiner Idee hätte ich nach wie vor Interesse ...

    Peter329 schrieb:

    Umsetzung deiner Idee
    Nun, bei mir wird der Speicherinhalt in ein HShort-Array kopiert, in dem ich Bildverarbeitung mache, und wenn etwas dargestellt werden soll, kopiere ich den in eine Bitmap-Inatanz.
    Was genau interessiert Dich?
    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 schrieb:

    @Peter329 Bitmap kommt bei mir nicht vor.
    Du musst die Bytes aus dem BitmapFrame in das Pixel-Array Deiner korrekt (Größe, Pixelformat) formatierten Bitmap-Instanz kopieren,
    nutze dazu Bitmap.LockBits und
    System.Runtime.InteropServices.Marshal.Copy:
    docs.microsoft.com/de-de/dotne…its?view=netframework-4.8


    Diese beiden Anweisungen kommen aber in deinem Beispiel nicht vor !

    Meine Versuche, das nach diesem Vorschlag zu realisieren führen zu Laufzeitfehlern (s. Post #11). Und jetzt würde mich als erstes interessieren, was ich falsch gemacht habe.

    [quote]
    BitmapFrame.Create liest dec.Frame(0) (wieso Frame(0) ???)
    Wieso BmpBitmap ??? Bmp ist doch schon die Abkürzung von "Bitmap" ????
    Und was sind "Frames" ????)
    [/quote]

    Es gibt diverse BitmapEncoder. Siehe Link:
    [url]https://docs.microsoft.com/de-de/dotnet/api/system.windows.media.imaging.bmpbitmapencoder?view=netframework-4.8


    [/url]Zu Frames siehe: BitmapEncoder.Frames Eigenschaft
    Normalerweise gigt es nur ein Bild = Frame(0).
    Ein Image z.B .Gif, Tiff kann aber aus mehreren Frames bestehen: Siehe Hinweise bei Frames.

    MfG GPM


    Peter329 schrieb:

    kommen aber in deinem Beispiel nicht vor
    Klar.
    Deswegen schrieb ich, was Du statt dessen tun solltest.
    ====
    Du programmierst Strict Off :?: :?: :?:
    Ich mach mal ein VB-Testprogramm, aber nicht mehr heute.
    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!
    @Peter329 Dies läuft.
    Sinnigerweise sind im BitmapFrame Rot und Blau getauscht, deswegen tauschen wir sie in einer separaten Prozedur noch zurück.
    Natürlich ist die Lösung von @GPM wesentlich eleganter, aber für mein eigenes Problem leider nicht verwendbar.
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Imports System.IO
    2. Imports System.Windows.Media.Imaging
    3. Imports System.Drawing.Imaging
    4. Public Class Form1
    5. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    6. Dim path = "C:\Temp\problem.webp"
    7. Dim bmp = ReadImage(path)
    8. bmp = ChangeColor(bmp)
    9. PictureBox1.Image = bmp
    10. End Sub
    11. Private Function ReadImage(fileName As String) As Bitmap
    12. Using Stream = New FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)
    13. Dim decoder = BitmapDecoder.Create(Stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None)
    14. Dim bmpFrame As BitmapFrame = decoder.Frames(0)
    15. 'Rectangle over picture
    16. Dim rect As New Rectangle(0, 0, bmpFrame.PixelWidth, bmpFrame.PixelHeight)
    17. Dim bmp = New Bitmap(bmpFrame.PixelWidth, bmpFrame.PixelHeight)
    18. 'Lock the bitmap's bits.
    19. Dim bmpData As System.Drawing.Imaging.BitmapData = bmp.LockBits(rect, Drawing.Imaging.ImageLockMode.ReadWrite, bmp.PixelFormat)
    20. Dim bytes As Integer = Math.Abs(bmpData.Stride) * bmpFrame.PixelHeight
    21. Dim rgbValues(bytes - 1) As Byte
    22. bmpFrame.CopyPixels(rgbValues, bmpData.Stride, 0)
    23. 'Get the address of the first line.
    24. Dim ptr As IntPtr = bmpData.Scan0
    25. 'Declare an array to hold the bytes of the bitmap.
    26. 'This code is specific to a bitmap with 24 bits per pixels.
    27. ' Copy the RGB values into the array.
    28. System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes)
    29. bmp.UnlockBits(bmpData)
    30. Return bmp
    31. End Using
    32. End Function
    33. ''' <summary>
    34. ''' Tausch von Rot und Blau
    35. ''' </summary>
    36. ''' <param name="image"></param>
    37. ''' <returns></returns>
    38. Private Function ChangeColor(image As Bitmap) As Bitmap
    39. 'https://docs.microsoft.com/de-de/dotnet/framework/winforms/advanced/how-to-use-a-color-matrix-to-transform-a-single-color
    40. Dim colorMatrixElements As Single()() = { _
    41. New Single() {0, 0, 1, 0, 0}, _
    42. New Single() {0, 1, 0, 0, 0}, _
    43. New Single() {1, 0, 0, 0, 0}, _
    44. New Single() {0, 0, 0, 1, 0}, _
    45. New Single() {0, 0, 0, 0, 1}}
    46. Dim colorMatrix As New ColorMatrix(colorMatrixElements)
    47. Dim imageAttributes As New ImageAttributes()
    48. imageAttributes.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap)
    49. Dim bmp = New Bitmap(image)
    50. Using g = Graphics.FromImage(bmp)
    51. g.DrawImage( _
    52. image, _
    53. New Rectangle(0, 0, image.Width, image.Height), _
    54. 0, _
    55. 0, _
    56. image.Width, _
    57. image.Height, _
    58. GraphicsUnit.Pixel, _
    59. imageAttributes)
    60. End Using
    61. Return bmp
    62. End Function
    63. End Class

    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!
    Supi ... also, erst mal ganz herzlichen Dank an GPM und RFG Jetzt verstehe ich doch einiges sehr viel besser !

    Danke auch für den neuen "Spoiler" ... den werde ich natürlich dieses Wochenende ausrobieren. Allerdings habe ich jetzt erst noch ein anderes Problem ... zu dem ich aber einen neuen Thread aufmache !

    LG
    Peter
    So, dann ich will ich mal kurz über den Stand meiner Lösung Rückmeldung geben.

    Das mit dem neuen Thread war wohl keine so gute Idee ... der ist dann nämlich gleich verschoben worden. Aber sei es drum ... :)

    Ich habe die Lösung von GPM jetzt umgesetzt. Wenn man nur diese Imports und Verweise verwendet, dann gibt es auch keine Kollision mit anderen Namespaces. Und das ist sehr angenehm.

    Die Routine ist Teil meines handgestrickten FileBrowsers ... und deshalb hab ich mit einigen tausend Files testen können. Das Dingens ist absolut stabil und kommt mit allen Formaten, inbesondere dem WebP, problemlos zurecht.

    Mein Eindruck ist, dass diese Routine jetzt auch deutlich performanter als meine frühere Routine ist. Die Tile Vorschau von einigen hundert Bildchen geht im Handumdrehen.

    So mache ich das künftig ! :)

    Nochmal recht herzlichen Dank an die Ratgeber und einen schönen Sonntag !

    LG
    Peter