EXIF nach Grafikänderungen erhalten

  • VB.NET
  • .NET (FX) 4.0

Es gibt 21 Antworten in diesem Thema. Der letzte Beitrag () ist von drschef.

    EXIF nach Grafikänderungen erhalten

    Ich lese eine Bitmap B1 aus einem Foto. Ein Exif-Reader ermittelt daraus rund 50 vernünftig erscheinende Exif-Angaben.
    Um die Bindung an die Fotodatei aufzuheben (sonst allgemeiner GDI-Fehler) wird aus B1 eine Bitmap B2 erzeugt.
    B2 wird mit oder ohne Änderungen in die Datei zurückgeschrieben. Danach zeigt der Exif-Reader nur noch 2 Exif-Parameter an, während das Foto im einfachsten Fall ohne Änderungen ein- und ausgabeseitig übereinstimmt .

    VB.NET-Quellcode

    1. Dim fname As String = "XYZ.jpg"
    2. Dim B1 As Bitmap = New Bitmap(fname)
    3. Dim B2 As Bitmap = New Bitmap(B1)
    4. B1.Dispose()
    5. '.....Änderungen
    6. SaveCompJPEG(fname, B2, 95)


    Hinter der Funktion SaveCompJPEG verbirgt sich im Wesentlichen ein Image.Save.

    Es scheint, als ob durch das Duplizieren der Bitmap der Exif-Anhang verloren geht. Warum da bei unterschiedlichen Fotodateien genau 2 Parameter noch angezeigt werden, ist ebenso unklar. Wie können die Exif-Parameter für die Ausgabe erhalten bleiben. Ich bin bisher davon ausgegangen, dass die Exif-Parameter ein Anhang der Bitmap sind und beim Clonen und erst recht beim Zurückschreiben mitgenommen werden.

    drschef schrieb:

    Danach zeigt der Exif-Reader nur noch 2 Exif-Parameter an
    Welche?
    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!
    @drschef Ich hab mal Frau Google befragt.
    msdn.microsoft.com/en-us/libra…op/ms533832(v=vs.85).aspx
    0x5090 - PropertyTagLuminanceTable
    0x5091 - PropertyTagChrominanceTable
    msdn.microsoft.com/en-us/libra…op/ms534416(v=vs.85).aspx
    PropertyTagLuminanceTable
    Luminance table. The luminance table and the chrominance table are
    used to control JPEG quality. A valid luminance or chrominance table has
    64 entries of type PropertyTagTypeShort. If an image has either a
    luminance table or a chrominance table, then it must have both tables.
    Klingt in sich logisch.
    Eine Bitmap-Instanz merkt sich immer ihre Herkunft. Wenn Du eine Datei lädtst und ohne Formatspezifikation speicherst, kommt das ursprüngliche Format wieder raus, ggf. mit falscher Extension.
    Konvertiere mal das Bild in eine Windows-Bitmap (*.bmp), die müsstest Du völlig ent-formatieren können.
    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!
    Ich hab mal Frau Google befragt


    Nach Recherchen stellt sich die Angelegenheit nun so dar:

    VB.NET-Quellcode

    1. Dim fname As String = "XYZ.jpg"
    2. 'My.Computer.FileSystem.DeleteFile(fname)
    3. Dim BE As Bitmap = New Bitmap(fname)
    4. Dim BA As Bitmap = CType(BE.Clone, Bitmap)
    5. BE.Dispose()
    6. My.Computer.FileSystem.DeleteFile(fname)


    Die Datei fname lässt sich vor Erstellung der Bitmap löschen, aber nicht danach. Durch das Dispose löse ich ja die Bindung an die Datei. Genau dieses Prinzip hat auch an einer anderen Stelle im Programm funktioniert.

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

    drschef schrieb:

    Angelegenheit
    Du verwechselst System.Drawing.Bitmap mit einer Bilddatei im Windows-Bitmap-Format *.bmp".
    Öffne die Datei in Paint oder IrfanView und speichere sie als Windows-Bitmap *.bmp".
    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!
    Öffne die Datei in Paint


    Es handelt sich um mehrere Fotos im jpg.Format, die gelesen, ggf. geändert werden sollen. Sie wurden bereits in einem primären Schritt als Hochformat-Bilder aufgerichtet. Dabei wurde die Exif-Orientierung nicht mit geändert, was bei der Visualisierug bei manchen Programmen nicht stört, bei manchen aber doch, weil sie vermutlich die Orientierung nochmal auf das bereits gedrehte Bild anwenden und es praktisch überdrehen. Nun möchte ich die Orientierung für die bereits gedrehten Bilder nachträglich auf Standard setzen.

    Hier sinngemäß nochmal beide Schritte:

    1. Schritt

    VB.NET-Quellcode

    1. Dim fname As String = "XYZ.jpg"
    2. Dim BE As New Bitmap(fname)
    3. Dim BA As Bitmap = ImageRotate(BE, winkel)
    4. SaveCompJPEG(fname, BA, 95) '=> OK
    5. BE.Dispose()


    2. Schritt

    VB.NET-Quellcode

    1. Dim fname As String = "XYZ.jpg"
    2. Dim BE As New Bitmap(fname)
    3. Dim BA As Bitmap = CType(BE.Clone, Bitmap)
    4. 'SaveCompJPEG(fname, BA, 95) 'Allg. Fehler GDI+
    5. My.Computer.FileSystem.DeleteFile(fname) 'Prozess kan nicht auf Datei zugreifen
    6. BE.Dispose()


    Der 1. Schritt funktioniert, der zweite nicht. Beim 2. Schritt habe ich anstelle des SaveCompJPEG(-> allgemeiner Fehler GDI+) Löschen eingesetzt, um einzugrenzen, dass die Eingabedatei noch blockiert ist. Da kommt nämlich "Prozess kann nicht auf Datei zugreifen". Der primäre Dreh-Algorithmus funktioniert einwandfrei. Der sekundäre, der nur die Orientierung in den Exif-Daten nachreichen soll aber nicht. Es muss da einen kleinen Unterschied geben, für den ich irgendwie betriebsblind bin.

    drschef schrieb:

    VB.NET-Quellcode

    1. Dim BA As Bitmap = CType(BE.Clone, Bitmap)
    bringt 0 Punkte.
    Pack das ganze mal in einen Using-Block.
    Zum Verständnis:
    Du lädtst ein Bild, ordnest es und speicherst es wieder unter dem selben Namen?
    Probier mal dies:

    VB.NET-Quellcode

    1. Using bmp = New Bitmap("C:\Temp\Test.jpg")
    2. bmp.RotateFlip(RotateFlipType.Rotate90FlipNone)
    3. bmp.Save("C:\Temp\Test.jpg", Imaging.ImageFormat.Jpeg)
    4. End Using

    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!
    Pack das ganze mal in einen Using-Block.


    Der Using-Block war es wahrscheinlich nicht. Aber die Erklärung rückt scheinbar langsam näher. Ich habe Deinen Vierzeiler etwas an meinen Fall angepasst. Da sieht er so aus und funktioniert:

    VB.NET-Quellcode

    1. Using BMP = New Bitmap("XYZ.jpg")
    2. BMP.RotateFlip(RotateFlipType.Rotate90FlipNone)
    3. SetImageOrientation(BMP, ExifOrientations.TopLeft)
    4. BMP.Save(fname, Imaging.ImageFormat.Jpeg)
    5. End Using


    Leider wird das Bild aber dabei noch einmal gedreht, was in meinem Fall ja schon vorher gemacht wurde. Aber die Orientation wird wie gewollt auf 1 = TopLeft gesetzt.
    Sobald ich aber das Rotate raus lasse, kommt wieder "Allgemeiner Fehler in GDI+", wohinter sich wahrscheinlich auch wieder die nicht freigegeben Eingabedatei verbirgt. Die Frage ist, was das Rotate an der Bitmap ändert.

    drschef schrieb:

    Die Frage ist, was das Rotate an der Bitmap ändert.
    Dann probieren wir nun, das Bild zunächst zu entformatieren:

    VB.NET-Quellcode

    1. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    2. Using bmp = New Bitmap("C:\Temp\Test.jpg")
    3. bmp.Save("C:\Temp\Test.bmp", Imaging.ImageFormat.Bmp)
    4. Using bmp2 = New Bitmap("C:\Temp\Test.bmp")
    5. bmp2.RotateFlip(RotateFlipType.Rotate90FlipNone)
    6. bmp2.Save("C:\Temp\Test2.jpg", Imaging.ImageFormat.Jpeg)
    7. End Using
    8. End Using
    9. IO.File.Delete("C:\Temp\Test.bmp")
    10. IO.File.Delete("C:\Temp\Test.jpg")
    11. IO.File.Move("C:\Temp\Test2.jpg", "C:\Temp\Test.jpg")
    12. End Sub
    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!
    Bild zunächst zu entformatieren


    Ich habe die Befehlsfolge 1:1 übernommmen. Bei der vorletzten Anweisung Delete("C:\Temp\Test.jpg")
    hat es wieder gehakt: "Der Prozess kann nicht auf die Datei ... zugreifen, da sie von einem anderen Prozess verwendet wird".

    In der bereits erwähnten Form

    VB.NET-Quellcode

    1. Using BMP = New Bitmap("XYZ.jpg")
    2. BMP.RotateFlip(RotateFlipType.Rotate90FlipNone)
    3. SetImageOrientation(BMP, ExifOrientations.TopLeft)
    4. BMP.Save(fname, Imaging.ImageFormat.Jpeg)
    5. End Using


    hat es ja noch funktioniert. Erst mit Weglassung des RotateFlip trat der Fehler auf. Wie wäre es anstelle des Rotate mit einer anderen Grafikoperation ohne Wirkung. Vielleicht gibt es da so eine Nulllösung. Drehen mit Winkel 0 habe ich bereits ausprobiert. Das geht auch nicht.

    Nachtrag:
    Ich habe aber inzwischen noch folgenden Krampf erfolgreich getestet:

    VB.NET-Quellcode

    1. Using BMP = New Bitmap("XYZ.jpg")
    2. BMP.RotateFlip(RotateFlipType.Rotate180FlipNone)
    3. BMP.RotateFlip(RotateFlipType.Rotate180FlipNone)
    4. SetImageOrientation(BMP, ExifOrientations.TopLeft)
    5. BMP.Save(fname, Imaging.ImageFormat.Jpeg)
    6. End Using


    Da wird zweimal um 180 Grad gedreht also praktisch 0 Grad. Das funktioniert genauso wie gewollt, nämlich nicht drehen und die Orientation auf Standard setzen.

    Aber geht so etwas nicht auch einfacher und durchschaubarer ???

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

    drschef schrieb:

    Befehlsfolge
    Mein Code funktioniert.
    Wenn Du dort SetImageOrientation richtig einbaust, sollte der auch bei Dir funktionieren.
    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!

    Mein Code funktioniert.


    Natürlich funktioniert er. Aber das Problem ist gerade, dass Dein Code eine Drehung enthält. Es soll aber nicht mehr gedreht werden, weil das schon geschehen ist. Und wenn ich die Drehung raus lasse und nur das SetImageOrientation verwende, kommt unerbittlich der Fehler. Deshalb habe ich eine Drehung 2 mal mit 180 Grad eingebaut, damit eigentlich gar nicht gedreht wird. Sobald ich die beiden Drehbefehle herauslasse, hakt es.

    Was hat in diesem Block die Drehung für eine besondere Funktion, dass es nur mit ihr geht, die Orientierung zu ändern?

    drschef schrieb:

    dass Dein Code eine Drehung enthält
    Du musst den Algorithmus natürlich so gestalten wie Du ihn brauchst.
    Mein Hauptaugenmerk lag auf den Ent-Formatieren des Bildes. Danach kannst Du die Ausricdhtung und den Dreh-Zustand in Einklang bringen.
    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!
    Ich würde da einfach die rohen Daten kopieren:

    VB.NET-Quellcode

    1. Dim Path = "C:\Users\Niko\Desktop\450130.jpg"
    2. Dim BitmapWidth As Integer
    3. Dim BitmapHeight As Integer
    4. Dim BitmapData As Byte()
    5. Using SourceBitmap As New Bitmap(Path)
    6. BitmapWidth = SourceBitmap.Width
    7. BitmapHeight = SourceBitmap.Height
    8. Dim Bits = SourceBitmap.LockBits(New Rectangle(0, 0, SourceBitmap.Width, SourceBitmap.Height), Imaging.ImageLockMode.ReadOnly, Imaging.PixelFormat.Format32bppArgb)
    9. BitmapData = New Byte(Bits.Stride * Bits.Height - 1) {}
    10. System.Runtime.InteropServices.Marshal.Copy(Bits.Scan0, BitmapData, 0, BitmapData.Length)
    11. SourceBitmap.UnlockBits(Bits)
    12. End Using
    13. Using TargetBitmap As New Bitmap(BitmapWidth, BitmapHeight)
    14. Dim Bits = TargetBitmap.LockBits(New Rectangle(0, 0, TargetBitmap.Width, TargetBitmap.Height), Imaging.ImageLockMode.WriteOnly, Imaging.PixelFormat.Format32bppArgb)
    15. System.Runtime.InteropServices.Marshal.Copy(BitmapData, 0, Bits.Scan0, BitmapData.Length)
    16. TargetBitmap.UnlockBits(Bits)
    17. TargetBitmap.Save(Path, System.Drawing.Imaging.ImageFormat.Jpeg)
    18. End Using

    Da können keine EXIF-Informationen mitgenommen werden, denn die abgespeicherte Bitmap weiß nichts von der ursprünglichen.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    ​Da können keine EXIF-Informationen mitgenommen werden


    Es wäre aber schön, wenn sie erhalten blieben. Und mit der von mir angegebenen Befehlsfolge geschieht das. Nur das eben mit dem einfachen Drehen der Bitmap ein Widerspruch zwischen der Ausrichtung der Bitmap (Breite, Höhe) und dem Exif-Parameter Orientierung entsteht. Scheinbar reagieren darauf manche Programme sauer, indem sie die nicht mehr gültige Orientierung auf die gedrehte Bitmap anwenden und dadurch 'überdrehen'. Das wollte ich vermeiden, indem ich die Orientierung 1 (Standard) nachtrage.

    drschef schrieb:

    Es wäre aber schön
    wenn Du mal genau schriebest, was soll und was nicht soll.
    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!
    ​wenn Du mal genau schriebest, was soll und was nicht soll.


    Ich zitiere mich selber von gestern:

    Es handelt sich um mehrere Fotos im jpg.Format, die gelesen, ggf. geändert werden sollen. Sie wurden bereits in einem primären Schritt als Hochformat-Bilder aufgerichtet. Dabei wurde die Exif-Orientierung nicht mit geändert, was bei der Visualisierug bei manchen Programmen nicht stört, bei manchen aber doch, weil sie vermutlich die Orientierung nochmal auf das bereits gedrehte Bild anwenden und es praktisch überdrehen. Nun möchte ich die Orientierung für die bereits gedrehten Bilder nachträglich auf Standard setzen.


    Das heißt es geht nicht (mehr) um das Drehen, sondern um das nachträgliche Setzen der Exif-Orientierung. Das Verrückte ist, dass da scheinbar immer ein Drehbefehl (oder etwas Äquivalentes) gebraucht wird, weil es sonst Fehler gibt. Das Erscheinungsbild suggeriert, dass die Bindung an die Eingabedatei erst durch einen Drehbefehl aufgehoben wird. Warum? Da die Fotos aber bereits gedreht sind, habe ich die Scheindrehung um 2 x 180 Grad eingeführt. Die dreht ja in Wirklichkeit gar nicht, aber wenn ich diese Befehle heraus lasse, kommt der erwähnte Fehler. Notfalls kann ich mit der Doppeldrehung (360 Grad) leben. Aber es wäre schon gut, das zu verstehen oder zu vereinfachen.

    drschef schrieb:

    Ich zitiere mich selber
    Das war mir schon klar.
    Tun bzw. nicht tun:
    • Bild drehen und EXIF drehen
    • Bild drehen und EXIF nicht drehen
    • Bild nicht drehen und EXIF drehen
    • Bild nicht drehen und EXIF nicht drehen
    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!