vb@rchiv
VB Classic
VB.NET
ADO.NET
VBA
C#
TOP-Angebot: 17 bzw. 24 Entwickler-Vollversionen zum unschlagbaren Preis!  
 vb@rchiv Quick-Search: Suche startenErweiterte Suche starten   Impressum  | Datenschutz  | vb@rchiv CD Vol.6  | Shop Copyright ©2000-2024
 
zurück
Rubrik:    |   VB-Versionen: VB200801.02.10
JPEG-Datei: Ergänzende Bildinformationen verwalten

Ein Modul zum Lesen und Schreiben von ergänzenden Informationen (Metadaten) in JPEG-codierten Bilddateien unter Verwendung von Klassen aus dem Namespace "System.Windows.Media.Imaging"

Autor:  Manfred BohnBewertung:     [ Jetzt bewerten ]Views:  2.563 
ohne HomepageSystem:  Win2k, WinXP, Win7, Win8, Win10, Win11 Beispielprojekt 

Einige Bilddatei-Formate (z..B. TIFF, PNG oder JPEG) bieten die Möglichkeit, ergänzende Informationen zum Bild einzutragen. Diese Angaben werden im Bild nicht sichtbar, können aber durch spezielle Routinen verwaltet werden. Es handelt sich dabei z.B. um den Titel des Bildes, einen Kommentar, das Erstellungsdatum oder um einen Kennstring zum verwendeten Kameramodell. Auch ein kleines Vorschaubild ("Thumbnail") kann enthalten sein.

Praktisch sind solche Extra-Infos bei der Verwaltung der eigenen Fotografien, weil diese Angaben beim Umbenennen, Kopieren, Verschieben oder Versenden einer Bilddatei erhalten bleiben.

Das Net-Framework umfasst den Namespace "System.Windows.Media.Imaging", der Methoden zum Lesen und Schreiben der Extra-Infos für Bilddatei-Formate enthält - sog. Metadaten. Laut VB-Doku lesen und schreiben diese Routinen bei JPEGs nach dem Standard des “International Press Telecommunications Council” (IPTC). Allerdings verarbeiten nicht alle Bildbearbeitungsprogramme, die sich auf diesen Standard beziehen, alle Felder und auch nicht in gleicher Weise.
(VB 2008 Express Edition: Im Projekteigenschaften-Dialog zum Setzen eines Verweises verwendet man den Reiter "Browse" und verweist direkt auf die Bibliotheken Windowbase, PresentationCore und PresentationFramework, die gewöhnlich im Ordner: "C:\Programme\Reference Assemblies\Microsoft\Framework\v3.0" abgelegt sind. Danach stehen die Namespaces "Windows.Media...." wie gewohnt zur Verfügung).

Das Modul:
Das Modul "modJPEGExtraInfos" enthält eine Datenstruktur und Routinen für komfortables Lesen und Schreiben von Zusatz-Informationen in JPEG-codierten Bild-Dateien. (Andere Bild-Formate erfordern die Verwendung der entsprechenden Encoder/Decoder.)

Variablen der Struktur "strucJPEGExtraInfos" dienen zum Eintragen und Abfragen der ergänzenden Bild-Informationen und werden als Parameter der Funktionen "SetJPEGExtraInfos" und "GetJPEGExtraInfos" verwendet. Vor der Verwendung müssen die Strukturvariablen durch die Methode "Clear" initialisiert werden. Die Gesamtlänge (Anzahl Zeichen) der zusätzlichen Angaben in einem ITPC-Header sollte 8192 nicht übersteigen.

Falls im JPEG zu einem Feld keine Angabe enthalten ist, gibt die Parametervariable "ExtraInfos" beim Lesen mit "GetJPEGExtraInfos" einen Leerstring zurück bzw. bei "DateTaken" den Default-Wert und bei "Rating" den Wert 0. Die Angaben zum Autor und zu den Schlüsselwörtern werden als Listen verwaltet.

Man beachte, dass das Feld "DateTaken" im Datentyp "BitmapMetaData" zwar als String geführt ist, intern aber ein gültiges Zeit-Format erwartet wird ("Win32-FileTime"). Die Routine "SetJPEGExtraInfos" passt den Date-Wert an dieses Format an. Beim Lesen des Feldes kommt es gelegentlich zu einer Fehlermeldung weil manche Bildverarbeitungssoftware ein anderes Format in das Datums-Feld schreibt. Die Routine "GetJPEGExtraInfos" fängt ggf. die Ausnahme auf und registriert den Vorfall im Feld "ErrorMessage" (als Initialisierungs-, Überlauf- oder Bereichsfehler). Der Lesevorgang wird danach fortgesetzt.

Die Thumbnails werden in "strucJPEGExtraInfos" zur einfacheren Verwendung als Instanzen der Klasse Bitmap (System.Drawing) abgelegt. Diese Klasse ist nicht kompatibel zur Klasse "BitmapSource" (System.Windows.Media.Imaging). Das Modul enthält deshalb Methoden zur Umwandlung.

Die Routine "SetJPEGExtraInfos" überträgt die Bilddaten der Quell-Datei direkt (=verlustfrei) in die angegebene Zieldatei und fügt die Angaben im Parameter "ExtraInfos" im Datei-Header ein. Alle Felder in "strucJPEGExtraInfos" sind beim Schreiben mit "SetJPEGExtraInfos" optional. Wird zu einem Feld keine Angabe gemacht, bleibt die entsprechende Eintragung in der neu erstellten Bilddatei leer bzw. bereits vorhandene Angaben in der Quell-Bilddatei werden unverändert übernommen (sonst: ggf. überschrieben). Der Parameter "AllowOverwrite" legt fest, ob eine bereits vorhandene Zieldatei in den Papierkorb verschoben werden darf. Falls kein Vorschaubild angegeben worden ist, wird ein bereits vorhandenes Thumbnail aus der Quell-Datei übernommen bzw. neu erstellt. (Das direkte Eintragen von ergänzenden Informationen in eine bereits bestehende Bilddatei ist weniger empfehlenswert, weil bei der dabei erforderlichen Reorganisation der Kopfangaben Probleme auftreten können.)

Viele Digitalkameras speichern technische Details (z.B. Blende, Belichtungszeit, Brennweite, ISO, Blitz, Belichtungsverfahren, Korrektur-Einstellungen - bis zu 50 Infos). Auch Angaben zum Farbraum oder zu Pixeldimensionen können enthalten sein. Diese Daten werden gemäß dem "Exchangeable Image File Format” (Exif) direkt in die Bild-Datei geschrieben. Die Routine "ReadEXIF" liest alle vorhandenen Klartext-"Exif"-Angaben und gibt sie als Liste zurück.

Ergänzende Hinweise:
Das JPEG-Format unterstützt keine Multiframe-Bilddateien, keine globalen Metadaten oder Previews. Alle Angaben beziehen sich auf den Frame mit Index 0.

Gelegentlich kann eine JPEG-Datei nicht decodiert werden, obwohl andere Bildbearbeitungssoftware dazu eventuell in der Lage ist. Die Fehlertoleranz des JPEGDecoders ist vergleichsweise gering. Es kommt z.B. zur Fehler-Rückgabe: "Keine geeignete Imaging-Komponente gefunden". Die Meldung "Ungeeigneter Datenstromtyp" weist meist darauf hin, dass die angegebene Datei keine JPEG-Codierung enthält. In seltenen Fällen meldet "GetJPEGExtraInfos" den Fehler "beschädigter Metadaten-Header". Soweit ich beurteilen kann, ist die Ursache häufig keine tatsächliche Beschädigung, sondern ein nicht unterstütztes Metadaten-Format. Die Extra-Infos können in diesem Fall nicht gelesen werden.

Fehlerhafte oder ungültige Einträge können dazu führen, dass der Virenscanner ein "Exploit" entdeckt und die erstellte JPEG-Datei in die "Quarantäne" verschiebt oder sie löscht.

Die VB-Dokumentation ist zu allen diesen Punkten sehr lückenhaft. Hilfreich war der Blog von Robert A. Wlodarczyk, der C#-Code enthält (Download unter:  http://rwlodarcmsdnblog.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=35304)

Beispiel:

Private Sub Demo()
 
  Dim picfile As String = "I:\daten\adriana.jpg"
  Dim picfile_new As String = "I:\daten\adriana_new.jpg"
 
  Dim extrainfos_write, extrainfos_read As New clsJPEGExtraInfos
  Dim errormessage As String = ""
 
  ' alle Angaben optional / 
  ' Thumbnail automatisch, falls nicht vorhanden
  With extrainfos_write
    .Title = "<Titel des Bildes>"
    .Authors.Add("<Autor>")
    .Authors.Add("<CoAutor>")
    .Keywords.Add("<Kategorie 1>")
    .Keywords.Add("<Kategorie 2>")
    .Comment = "<Bild-Kommentar>"
    .DateTaken = CDate("1.1.2010")
    .Subject = "<Motivangaben o.ä.>"
    .ApplicationName = "SetJPEGExtraInfos"
    .CameraModel = "<verwendete Kamera>"
  End With
 
  ' Informationen in die Bilddatei eintragen 
  If Not SetJPEGExtraInfos(picfile, picfile_new, extrainfos_write, , errormessage) Then
    MessageBox.Show(errormessage, "", MessageBoxButtons.OK, _
    MessageBoxIcon.Exclamation)
  Else
    If extrainfos_write.ErrorMessage <> String.Empty Then
      MessageBox.Show("Einige Datenfelder konnten nicht eingetragen werden" & _
      Environment.NewLine & extrainfos_write.ErrorMessage, "", _
      MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
    End If
 
    ' Geschriebene Informationen zur Kontrolle wieder einlesen 
    If Not GetJPEGExtraInfos(picfile_new, extrainfos_read, errormessage) Then
      MessageBox.Show(errormessage, "", MessageBoxButtons.OK, _
      MessageBoxIcon.Exclamation)
    End If
  End If
 
  ' Um Angaben zu ergänzen, liest man zunächst die vorhandenen
  ' Daten, hängt die neuen Einträge an die gelesenen Strings und
  ' schreibt die erweiterten Daten in eine neue Bilddatei
End Sub

Das Modul "modJPEGExtraInfos"

Option Strict On
Option Explicit On
Option Infer Off
 
' Erforderliche Verweise auf Framework-Bibliotheken:
' System, System.Core, System.Drawing
' PresentationCore, PresentationFramework, Windowbase
 
Imports System                          ' Exception, StringComparison, IntPtr
Imports System.IO                       ' FileStream
Imports Microsoft.VisualBasic.FileIO    ' UIOption, RecycleOption
 
Imports System.Windows.Media.Imaging    ' (JPEG)BitmapDecoder,  
'Bitmapframe, -source, -metadata, -createoptions, -sizeoptions, -cachoption
 
Imports System.Runtime.InteropServices  ' Marshall
Imports System.Drawing                  ' Bitmap. Rectangle, Imaging
Imports System.Collections.Generic      ' List(Of T)
Imports System.Collections.ObjectModel  ' ReadOnlyCollection(Of T)
 
''' <summary>
''' Verwaltung ergänzender Bildinformationen
''' </summary>
Module modJPEGExtraInfos
  Const cMaxStringLength As Integer = 256 'maximale Länge Stringeintrag 
  Const cThumbnailHeight As Integer = 200 'Höhe des Thumbnails (Pixel)
#Region "strucJPEGExtraInfos"
 
  ''' <summary>Datenstruktur für ergänzende Bildinformationen</summary>
  Public Class clsJPEGExtraInfos
 
    Friend Const DefaultDate As Date = #1/2/1601#
 
    ''' <summary>Anwendung für Bildverwaltung</summary>
    Public ApplicationName As String
    ''' <summary>Liste der Autoren</summary>
    Public Authors As List(Of String)
    ''' <summary>Hersteller der aufnehmenden Kamera</summary>
    Public CameraManufacturer As String
    ''' <summary>Modelbezeichnung der aufnehmenden Kamera</summary>
    Public CameraModel As String
    ''' <summary>Kommentar zum Bild</summary>
    Public Comment As String
    ''' <summary>Copyright-Vermerk</summary>
    Public Copyright As String
    ''' <summary>Aufnahme-/Erstellungs-Datum</summary>
    Public DateTaken As Date
    ''' <summary>Liste der Schlüsselworte zur Bildverwaltung</summary>
    Public Keywords As List(Of String)
    ''' <summary>positive Ganzzahl (Bild-Rating)</summary>
    Public Rating As UInteger
    ''' <summary>Thema/Verwendungszweck des Bildes</summary>
    Public Subject As String
    ''' <summary>Titel des Bildes</summary>
    Public Title As String
    ''' <summary>Thumbnail als Bitmap (oder Nothing)</summary>
    Public Thumbnail As Bitmap
    ''' <summary>Rückgabe: Fehler bei Bearbeitung (oder Leer)</summary>
    Public ErrorMessage As String
 
    ' Konstruktor (incl. Initialisierung)
    Public Sub New()
      Clear()
    End Sub
 
    ''' <summary>Initialisiert die Elemente der Struktur</summary>
    Public Sub Clear()
      Dim e As String = String.Empty
      Authors = New List(Of String) : Keywords = New List(Of String)
      ApplicationName = e
      Comment = e : Copyright = e
      ' DefaultDate = CDate("#1/2/1601#")
      DateTaken = DefaultDate : Rating = 0
      CameraManufacturer = e : CameraModel = e
      Subject = e : Title = e : ErrorMessage = e
      Thumbnail = Nothing
    End Sub
  End Class
 
#End Region
#Region "Öffentliche Methoden (Lesen und Schreiben von Metadaten)"
 
  ''' <summary>Ergänzen von Rahmeninformationen in einer Bilddatei</summary>
  ''' <param name="filename">Bilddatei (JPEG-codiert)</param>
  ''' <param name="savefilename">Ausgabedatei mit den Rahmeninformationen 
  ''' (wird ggf. gelöscht!)</param>
  ''' <param name="ExtraInfos">Rahmenangaben zur Bilddatei</param>
  ''' <param name="AllowOverwrite">Darf die Zieldatei - falls bereits vorhanden - 
  ''' in den Papierkorb geworfen werden?</param>
  ''' <param name="ErrorMessage">Fehlermeldung (oder Leer)</param>
  ''' <returns>Alles OK?</returns>
  Public Function SetJPEGExtraInfos(ByVal filename As String, _
                  ByVal savefilename As String, _
                  ByVal ExtraInfos As clsJPEGExtraInfos, _
      Optional ByVal AllowOverwrite As Boolean = False, _
      Optional ByRef ErrorMessage As String = "") As Boolean
 
    ' Rückgabe initialisieren
    ErrorMessage = String.Empty
    ExtraInfos.ErrorMessage = String.Empty
 
    ' Berechnung des erforderlichen Zusatzspeichers
    Dim PaddingAmount As UInteger = ComputePaddingAmount(ExtraInfos)
    If PaddingAmount > 8192 Then
      ErrorMessage = "Zu viele ExtraInfos" : Return False
    End If
 
    Dim source As BitmapDecoder            ' zum Lesen des Bildes
    Dim jpegenc As New JpegBitmapEncoder   ' zum Schreiben des Bildes
    Dim md As BitmapMetadata               ' Aktualisierung der ExtraInfos
 
    Dim CreateOptions As BitmapCreateOptions = _
    BitmapCreateOptions.PreservePixelFormat Or _
    BitmapCreateOptions.IgnoreColorProfile
 
    Dim str() As String
    Dim thumb As BitmapSource = Nothing
 
    Try
      With My.Computer.FileSystem
        If Not .FileExists(filename) Then
          ErrorMessage = "Bilddatei nicht vorhanden"
          Return False
        End If
 
        If .FileExists(savefilename) Then
          If Not AllowOverwrite Then
            ErrorMessage = "Die Ausgabedatei ist bereits vorhanden"
            Return False
          Else
            .DeleteFile(savefilename, _
            UIOption.OnlyErrorDialogs, RecycleOption.SendToRecycleBin)
          End If
        End If
      End With
    Catch ex As Exception
      ErrorMessage = ex.Message : Return False
    End Try
 
    Try
      Using picstream As New FileStream _
      (filename, FileMode.Open, FileAccess.Read)
        source = BitmapDecoder.Create _
        (picstream, CreateOptions, BitmapCacheOption.None)
 
        If Not source.CodecInfo.FileExtensions.ToLower.Contains("jpg") Then
          ErrorMessage = "Kein Decoder für das File vorhanden"
          Return False
        End If
 
        If source.Frames(0) IsNot Nothing And _
            source.Frames(0).Metadata IsNot Nothing Then
 
          ' Metadaten der Eingabedatei klonen
          md = CType(source.Frames(0).Metadata.Clone, BitmapMetadata)
 
          ' zusätzlich benötigten Speicher anfordern
          md.SetQuery("/app1/ifd/PaddingSchema:Padding", PaddingAmount)
          md.SetQuery("/app1/ifd/exif/PaddingSchema:Padding", PaddingAmount)
          md.SetQuery("/xmp/PaddingSchema:Padding", PaddingAmount)
 
          With ExtraInfos
            ' ExtraInfos kontrolliert in die Metadaten übertragen 
            Try
              SetString(.ApplicationName, md.ApplicationName)
              SetString(.CameraManufacturer, md.CameraManufacturer)
              SetString(.CameraModel, md.CameraModel)
              SetString(.Comment, md.Comment)
              SetString(.Copyright, md.Copyright)
              SetString(.Subject, md.Subject)
              SetString(.Title, md.Title)
            Catch ex As Exception
              .ErrorMessage &= _
              StringOrEmpty("String: " & ex.Message & "  ")
            End Try
 
            Try
              SetString(.DateTaken.ToString("G", _
              New System.Globalization.CultureInfo("en-US")), md.DateTaken)
            Catch ex As Exception
              .ErrorMessage &= _
              StringOrEmpty("Date: " & ex.Message & "  ")
            End Try
 
            ' negative Werte sind nicht zulässig
            md.Rating = CInt(.Rating)
 
            ' Auflistungen kontrolliert übertragen
            If .Authors IsNot Nothing _
                AndAlso .Authors.Count > 0 Then
              ReDim str(.Authors.Count - 1)
              For i As Integer = 0 To ExtraInfos.Authors.Count - 1
                str(i) = String.Empty
                If md.Author IsNot Nothing Then
                  If i < md.Author.Count Then str(i) = md.Author(i)
                End If
                SetString(.Authors(i), str(i))
              Next i
              md.Author = New ReadOnlyCollection(Of String)(str)
            End If
            If .Keywords IsNot Nothing _
                AndAlso .Keywords.Count > 0 Then
              ReDim str(.Keywords.Count - 1)
              For i As Integer = 0 To .Keywords.Count - 1
                str(i) = ""
                If md.Keywords IsNot Nothing Then
                  If i < md.Keywords.Count Then str(i) = md.Keywords(i)
                End If
                SetString(.Keywords(i), str(i))
              Next i
              md.Keywords = New ReadOnlyCollection(Of String)(str)
            End If
 
            Try
              If .Thumbnail IsNot Nothing Then
                ' explizit vorgegebene Thumbnail
                thumb = BitMapToBitmapSource _
                (CreateThumbnail(.Thumbnail))
              ElseIf source.Frames(0).Thumbnail IsNot Nothing Then
                ' T. bereits in Bilddatei enthalten
                thumb = source.Frames(0).Thumbnail
              Else
                ' T. aus der Bilddatei erstellen
                thumb = BitMapToBitmapSource _
                (CreateThumbnail(New Bitmap(filename)))
              End If
            Catch ex As Exception
              .ErrorMessage &= _
              StringOrEmpty("Thumbnail: " & ex.Message & "  ")
            End Try
          End With
 
          With source
            ' verlustfreie Übertragung des Input-Bildes 
            ' unter Hinzufügung der Metadaten in den Output-Frame
            jpegenc.Frames.Add(BitmapFrame.Create _
            (.Frames(0), thumb, md, .Frames(0).ColorContexts))
          End With
 
          Try
            ' Ausgabe-Bilddatei schreiben
            Using savefile As New FileStream(savefilename, _
                FileMode.Create, FileAccess.ReadWrite, FileShare.None)
              jpegenc.Save(savefile)
            End Using
          Catch ex As Exception
            ErrorMessage = ex.Message
          End Try
        End If
      End Using
    Catch ex As Exception
      ErrorMessage = ex.Message
    End Try
 
    Return (ErrorMessage = String.Empty)
 
  End Function
 
  ''' <summary>Abfrage der Extra-Infos einer Bilddatei</summary>
  ''' <param name="filename">Name der Bilddatei</param>
  ''' <param name="ExtraInfos">Struktur für die Extrainfos</param>
  ''' <param name="ErrorMessage">Fehlermeldung</param>
  ''' <returns>Alles OK?</returns>
  Public Function GetJPEGExtraInfos(ByVal filename As String, _
                  ByRef ExtraInfos As clsJPEGExtraInfos, _
         Optional ByRef ErrorMessage As String = "") As Boolean
 
    Dim picstream As FileStream
    Dim jpDecoder As JpegBitmapDecoder
    Dim md As BitmapMetadata
 
    ' Rückgabe initialisieren
    ErrorMessage = String.Empty
    ExtraInfos.Clear() ' Initialisierung
 
    ' Bilddatei zur Weiterverarbeitung öffnen
    Try
      picstream = New FileStream(filename, _
      FileMode.Open, FileAccess.Read, FileShare.None)
    Catch ex As Exception
      ErrorMessage = ex.Message : Return False
    End Try
 
    Try
      ' JPEG-Decoder für die geöffnete Bilddatei erstellen
      jpDecoder = New JpegBitmapDecoder _
      (picstream, BitmapCreateOptions.PreservePixelFormat, _
      System.Windows.Media.Imaging.BitmapCacheOption.Default)
      ' Rahmendaten abfragen
      md = CType(jpDecoder.Frames(0).Metadata, BitmapMetadata)
    Catch ex As Exception
      picstream.Close()
      ErrorMessage = ex.Message
 
      ExtraInfos.ErrorMessage &= _
      StringOrEmpty("Decode: " & ex.Message & "  ")
      Return False
    End Try
 
    ' Extra-Informationen in die Rückgabe-Struktur übertragen
    With ExtraInfos
      ' Kontrollierte Abfrage der String-Daten
      .ApplicationName = StringOrEmpty(md.ApplicationName)
      .CameraManufacturer = StringOrEmpty(md.CameraManufacturer)
      .CameraModel = StringOrEmpty(md.CameraModel)
      .Comment = StringOrEmpty(md.Comment)
      .Copyright = StringOrEmpty(md.Copyright)
      .Subject = StringOrEmpty(md.Subject)
      .Title = StringOrEmpty(md.Title)
 
      ' Feld mit Datumsangabe
      Try
        If Not String.IsNullOrEmpty(md.DateTaken) Then
          .DateTaken = CDate(md.DateTaken)
        Else
          .DateTaken = clsJPEGExtraInfos.DefaultDate
        End If
      Catch ex As Exception
        .DateTaken = clsJPEGExtraInfos.DefaultDate
        .ErrorMessage &= StringOrEmpty("Date: " & ex.Message & "  ")
      End Try
 
      .Rating = CUInt(md.Rating)
 
      ' kontrollierte Abfrage der Listen (Author, Keywords)
      .Authors.Clear()
      If md.Author IsNot Nothing AndAlso md.Author.Count > 0 Then
        For i As Integer = 0 To md.Author.Count - 1
          .Authors.Add(StringOrEmpty(md.Author(i)))
        Next i
      End If
 
      .Keywords.Clear()
      If md.Keywords IsNot Nothing AndAlso md.Keywords.Count > 0 Then
        For i As Integer = 0 To md.Keywords.Count - 1
          .Keywords.Add(StringOrEmpty(md.Keywords(i)))
        Next i
      End If
 
      Try
        ' Abfrage des Thumbnail
        If jpDecoder.Frames(0).Thumbnail IsNot Nothing Then
          .Thumbnail = BitMapSourceToBitmap(jpDecoder.Frames(0).Thumbnail)
        Else
          .Thumbnail = Nothing
        End If
      Catch ex As Exception
        ExtraInfos.ErrorMessage &= _
        StringOrEmpty("Thumbnail: " & ex.Message & "  ")
      End Try
    End With
 
    picstream.Close()
    ErrorMessage = "" : Return True
 
  End Function
 
  ''' <summary>EXIF-Einträge in Bilddatei ermitteln</summary>
  ''' <param name="filename">Pfad zu einer Bilddatei</param>
  ''' <returns>Liste der Exif-Einträge</returns>
  Public Function ReadEXIF(ByVal filename As String) As List(Of String)
 
    Dim exif_list As New List(Of String)
 
    Try
      ' Bilddatei lesen
      Dim text As String = My.Computer.FileSystem.ReadAllText(filename)
      Const exif As String = "<exif:"
      Const comp As StringComparison = StringComparison.InvariantCultureIgnoreCase
 
      Dim pos, pos1, pos2 As Integer
      Dim key, value As String
 
      ' Suche nach exif-Kennungen
      pos = 0
      While text.IndexOf(exif, pos, comp) >= 0
        ' Startposition ermitteln
        pos = text.IndexOf(exif, pos, comp)
        ' Ende der Exif-Kennung: Start des Exif-Value
        pos1 = text.IndexOf(">", pos, comp)
        If pos1 - pos > exif.Length + 1 Then
          ' Ende des Exif-Value
          pos2 = text.IndexOf("<", pos1, comp)
          If pos2 - pos1 > 1 Then
            ' Kennung und Wert der Kennung lesen
            key = text.Substring _
            (pos + exif.Length, pos1 - pos - exif.Length)
            value = text.Substring(pos1 + 1, pos2 - pos1 - 1)
            key = StringOrEmpty(key) : value = StringOrEmpty(value)
            If key <> String.Empty And value <> String.Empty Then
              ' nichtleere Exif-Angaben in Liste übertragen
              exif_list.Add(key & ": " & value)
            End If
          End If
        End If
        pos += exif.Length
      End While
 
      ' Rückgabe
      Return exif_list
    Catch
      Return Nothing
    End Try
 
  End Function
#End Region
#Region "Hilfsfunktionen (Strings, Bitmap-Konvertierung, Thumbnails)"
 
  Private Function StringOrEmpty(ByVal str As String) As String
 
    ' Hilfsfunktion: Nullstring filtern, Trimmen, 
    ' max. Länge überwachen, NL-Ersetzung
 
    If str Is Nothing Then Return String.Empty
 
    str = str.Trim
    If str <> String.Empty Then
      str = str.Replace(Environment.NewLine, " ")
    End If
    Dim rlength As Integer
    If str.Length > cMaxStringLength Then
      rlength = str.Length - cMaxStringLength
      str = str.Substring(0, cMaxStringLength) & "...&" & CStr(rlength)
    End If
 
    ' Rückgabe
    Return str.Trim
  End Function
 
  Private Sub SetString(ByVal quelle As String, ByRef ziel As String)
    ' Hilfsfunktion: kontrolliertes Überschreiben des Zielstrings, 
    ' falls ein nicht-leerer Quell-String gegeben worden ist 
    ziel = StringOrEmpty(ziel)
    Dim q As String = StringOrEmpty(quelle)
    If q <> String.Empty Then ziel = q
  End Sub
 
  Private Function ComputePaddingAmount _
  (ByVal Extrainfos As clsJPEGExtraInfos) As UInteger
 
    ' Hilfsfunktion: Abschätzung der Größe des zusätzlich 
    ' benötigten Speichers für die Extrainfos
 
    Dim l As Long = 0
    With Extrainfos
      l += StringOrEmpty(.ApplicationName).Length
      l += StringOrEmpty(.CameraManufacturer).Length
      l += StringOrEmpty(.CameraModel).Length
      l += StringOrEmpty(.Comment).Length
      l += StringOrEmpty(.Copyright).Length
      l += StringOrEmpty(.DateTaken.ToString).Length
      l += 4 ' Rating
      l += StringOrEmpty(.Subject).Length
      l += StringOrEmpty(.Title).Length
      If .Authors IsNot Nothing Then
        For i As Integer = 0 To .Authors.Count - 1
          l += StringOrEmpty(.Authors(i)).Length
        Next i
      End If
      If .Keywords IsNot Nothing Then
        For i As Integer = 0 To .Keywords.Count - 1
          l += StringOrEmpty(.Keywords(i)).Length
        Next i
      End If
    End With
    l = ((l + 1024) \ 1024) * 1024
    Return CUInt(l)
  End Function
 
  Private Function BitMapSourceToBitmap(ByVal bs As BitmapSource) As Bitmap
 
    ' Hilfsfunktion: Umwandlung einer BitmapSource (Media.Imaging)
    ' in eine Bitmap (Drawing)
 
    Dim stride As Integer
 
    ' derzeit nur 24-Bit-Bilder (Standard bei JPEGs)
    If bs.Format.BitsPerPixel <> 24 Then Return Nothing
 
    ' Skalierung der BitmapSource (??)
    Dim tBS As BitmapSource = New TransformedBitmap _
    (bs, New System.Windows.Media.ScaleTransform(1, 1))
 
    With tBS
      stride = .PixelWidth * 3
      While stride Mod 4 > 0 : stride += 1 : End While
 
      ' Bytearray mit den Bilddaten füllen
      Dim bytes(.PixelHeight * stride - 1) As Byte
      .CopyPixels(bytes, stride, 0)
 
      ' Bitmap in korrekter Größe erstellen 
      Dim bmp As Bitmap = New Bitmap _
      (.PixelWidth, .PixelHeight, Imaging.PixelFormat.Format24bppRgb)
      ' zur Sperre der gesamten Bitmap
      Dim bmp_rect As New Rectangle(0, 0, .PixelWidth, .PixelHeight)
      ' Bitmap sperren
      Dim bmp_data As Imaging.BitmapData = _
      bmp.LockBits(bmp_rect, Imaging.ImageLockMode.ReadWrite, bmp.PixelFormat)
      ' Bilddaten in die Bitmap übertragen
      Marshal.Copy(bytes, 0, bmp_data.Scan0, bytes.Length)
      ' Bitmap freigeben
      bmp.UnlockBits(bmp_data)
      ' Rückgabe
      Return bmp
    End With
 
  End Function
 
  Private Function BitMapToBitmapSource(ByVal Bmp As Bitmap) As BitmapSource
 
    ' Hilfsfunktion: Umwandlung einer Bitmap (Drawing) in eine
    ' BitmapSource (Media.Imaging)
 
    Dim hBitmap As IntPtr = Bmp.GetHbitmap()
    Return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap _
    (hBitmap, IntPtr.Zero, System.Windows.Int32Rect.Empty, _
     BitmapSizeOptions.FromEmptyOptions())
 
  End Function
 
  Private Function CreateThumbnail(ByVal bmp As Bitmap) As Bitmap
 
    ' Hilfsfunktion: Zu einer Bitmap wird ein Thumbnail erstellt
    If bmp Is Nothing Then Return Nothing
 
    Return CType(bmp.GetThumbnailImage(cThumbnailHeight, _
    CInt(cThumbnailHeight * bmp.Height / bmp.Width), Nothing, Nothing), Bitmap)
  End Function
 
#End Region
End Module