PDF Drucken E-Mail

Bildobjekte in VBA mit GDIPlus verwalten unter Office 2007 und 2010

Umgang mit Bildern ist nicht nur unter Access 2007 und 2010 gefragt, sondern ebenso in den Vorversionen.
Was also soll dieser Beitrag? Warum kein Modul, das sich generell verwenden ließe?

Dafür gibt es zwei Gründe:

  • Office 2007 und 2010 haben keine Menüleisten mehr, sondern Ribbons. Benutzerdefinierte Ribbons für die eigene Anwendung lassen sich leider nicht so leicht mit Icons versehen, wie Symbolleisten - ein Editor dafür fehlt.
    Dabei schreit das in seinen Abmessungen recht ausgedehnte und fix eingestellt Ribbon geradezu nach bebilderten Elementen. Selbst wenn man kein Fan von solchen verspielten Oberflächen ist, muss man zugestehen, dass ein reines Text-Ribbon ziemlich eigenartig aussieht.
  • Das hier vorgestellte Modul verwendet im Kern API-Funktionen von GDIPlus. Die Routinen ließen sich zwar genauso gut mit dem alten GDI32 erstellen, fielen dann aber erheblich aufwändiger aus. Effektfunktionen, wie etwa das Schärfen von Bildern, erforderten ziemlich umfangreiche Programmierung und wären im Endergebnis deutlich langsamer, weil VBA nunmal nicht besonders schnell ist.
    GDIPlus steht automatisch in Windows XP und Vista/Win7 zur Verfügung - allerdings mit unterschiedlichem Funktionsumfang. Gerade die Effektabteilung existiert in GDIPlus 1.0 (Windows XP) nicht, sondern nur in GDIPlus 1.1.
    Praktischerweise bringen aber Office 2007 und 2010 ihre eigenen GDIPlus-DLLs mit, die da zwar den Namen ogl.dll haben und sich im Verzeichnis c:\programme\common files\microsoft shared\office12 (oder 14) befinden, ansonsten jedoch funktionsidentisch mit den GDIPlus-Bibliotheken von Vista/Win7 sind. ogl.dll ist wohl eine Abkürzung für Office Graphics Library.
    Zudem laden Office 12 und 14 die Bibliothek normalerweise automatisch, so dass sie von unserem Modul mitverwendet werden kann.

    [UPDATE 2010:] Hinweis: Wie sich herausstellte, wird die Datei ogl.dll von Office 2010 NICHT installiert, wenn es Windows 7 oder Vista vorfindet!
    Office/Access verwenden dann stattdessen die systemeigene GDIPlus.dll, da die hier bereits in Version 1.1 vorliegt.
    In diesem Fall sind wieder die API-Deklaration mit Lib "gdiplus" zu verwenden - oder einfach die neuere Version des Moduls unten bei den Downloads.

Im Folgenden sind die Prozeduren des Moduls beschrieben. Es handelt sich nur um die wichtigsten für den Umgang mit Bildobjekten benötigten. Das Modul selbst können Sie weiter unten herunterladen.
Eine Beispieldatenbank demonstriert mögliche Anwendungen dieser Funktionen. Auch diese können Sie unten herunterladen.

Das Modul benötigt übrigens einen Verweis auf die Bibliothek OLE Automation (stdole).
Beim Neuanlegen einer Datenbank wird dieser Verweis häufig bereits gesetzt. Falls nicht vorhanden, dann wird der VBA-Compiler all jene Stellen des Moduls bemängeln, die auf ein StdPicture-Objekt verweisen.

Funktionen

GDIPlus ist eigentlich eine objektorientierte Bibliothek, die im NET-Framework in Gestalt der Klassen unter System.Drawing daherkommt.
Sie veröffentlicht aber auch ein sogenanntes Flat-API, das herkömmliche API-Funktionen bereitstellt. Unmittelbare Objekte fehlen diesem Flat-API jedoch.
Stattdessen werden unter VB(A)6 Zeigervariablen (Long-Werte) auf die Objekte verwendet, was den Umgang mit dem API nicht gerade erleichert und etwas unübersichtlich macht.

Eine der zentralen Variablen ist eine Referenz auf ein Hauptobjekt, die man bei GDIPlus zu Beginn anfordert. Erst nach der dadurch erfolgten Initialisierung stehen die API-Funktionen zur Verfügung.
Diese Initialisierung geschieht im Modul in der Prozedur InitGDIP:

Function InitGDIP() As Boolean

Sie müssen diese allerdings nicht selbst aufrufen. Dies geschieht automatisch in jeder der nachfolgenden Funktionen.
Das Pendant dazu ist die Prozedur ShutDownGDIP, die die Referenz auf GDIPlus wieder freigibt:

Sub ShutDownGDIP()

Das ist eine etwas heikle Angelegenheit. Wird die Referenz nicht freigegeben, dann kann es zu Speicherlecks Ihrer Anwendung kommen und schlimmstenfalls zu Abstürzen.
Sie sollten die Prozedur deshalb regelmäßig dann aufrufen, wenn Sie die Funktionen des Moduls nicht mehr benötigen - mindestens beim Beenden Ihrer Datenbank.
Da dieser Aufruf möglicherweise nicht immer sichergestellt ist, enthält das Modul eine Automatik, die auf Basis eines API-Timers das Shutdown regelmäßig nach jeweils 5 Sekunden betätigt.
Der Timer ruft dabei diese Prozedur auf:

Private Sub AutoShutDown()

Das Intervall können Sie natürlich Ihren Bedürfnissen anpassen und etwa verkleinern, was allerdings die Performance wegen der Anforderung dauernder Neuinitilisierungen geringfügig senkt.

Übrigens ist die Referenz auf das GDIPlus-Objekt in der modulweit gültigen Variablen lGDIP gespeichert.
Da unbehandelte Fehler diese Variable zurücksetzen würden, wodurch auch kein Shutdown mehr möglich wäre, wird eine Kopie des Zeigers auch noch in einer TempVar gespeichert. TempVars verleiren auch nach unbehandelten Fehlern nicht ihren Inhalt.

Bilddateien laden können Sie mit der Funktion LoadPictureGDIP:

Function LoadPictureGDIP(sFileName As String) As StdPicture

Sie erwartet als Parameter lediglich den Namen der Datei und gibt ein StdPicture-Objekt zurück. Damit ist sie kompatibel zur standardmäßig in Access vorhandenen Funktion LoadPicture oder der gleichartigen in der Bibliothek Ole Automation (stdole).
Während Letztere jedoch nur BMP-, GIF- und JPEG-Dateien laden können, kommt GDIPlus zusätzlich mit dem wichtigen Typ PNG klar und weiter mit TIFF, EMF, WMF.

Abspeichern können Sie ein Bildobjekt mit der Prozedur SavePicGDIPlus:

Function SavePicGDIPlus(ByVal Image As StdPicture, _
                        Byval sFile As String, _
                        PicType As PicFileType, _
                        Optional Quality As Long = 80) As Boolean

Das Bildobjekt wird als Stdpicture-Variable Image übergeben, die Zieldatei mit dem String-Parameter sFile.
Damit die Routine weiß, welche Art von Bilddatei erstellt werden soll, stellen Sie den Parameter PicType auf einen der folgenden Werte der Enumeration PicFileType ein, die so deklariert ist:

Public Enum PicFileType
    pictypeBMP = 1
    pictypeGIF = 2
    pictypePNG = 3
    pictypeJPG = 4
End Enum

Wir haben die anderen Typen, die GDIPlus ebenfalls erzeugen kann - TIFF etc. -, weggelassen. Hier handelt es sich um die wichtigsten.
Sollten Sie eine JPEG-Datei erzeugen wollen, dann geben Sie zusätzlich den optionalen Parameter Quality an, der bei den anderen Typen ignoriert wird.
Erlaubte Werte für ihn reichen von 1 bis 100. Er ist auf 80 voreingestellt, was den meisten Anforderungen genügen sollte.
Übrigens kann die analoge Methode SavePicture von OLE Automation ausschließlich BMP-Dateien erzeugen.

Haben Sie eine Bildvariable, dann können Sie die Abmessungen des enthaltenen Bildes in Pixel mit der Funktion GetDimensionsGDIP ermitteln:

Function GetDimensionsGDIP(ByVal Image As StdPicture) As TSize

Sie gibt einen Wert des Type TSize zurück, der Breite (X) und Höhe (Y) enthält:

Public Type TSize
    X As Double
    Y As Double
End Type

 

Mit ResampleGDIP können Sie ein Bild verkleinern oder vergrößern:

Function ResampleGDIP(ByVal Image As StdPicture, _
                      ByVal Width As Long, _
                      ByVal Height As Long, _
                      Optional bSharpen As Boolean = True) As StdPicture

Übergeben müssen Sie der Prozedur neben der Bildvariablen noch die Breite (Width) und Höhe (Height) des Zielbildes.
Wird der optionale Parameter bSharpen auf True gesetzt, dann wird das erzeugte Bild zusätzlich dezent nachgeschärft, was gerade beim Verkleinern Sinn macht.
Zurück bekommen Sie von der Funktion ein neues Bildobjekt.
Der Skalierungsalgorithmis von GDIPlus ist bei dieser Funktion ürigens bikubische Interpolation.

 

Sie können aus einem Bild auch einen Ausschnitt heraustrennen, wenn Sie die Funktion CropImage aufrufen:

Function CropImage(ByVal Image As StdPicture, _
                   X As Long, Y As Long, _
                   Width As Long, Height As Long) As StdPicture

X und Y sind dabei die Koordinaten der linken oberen Ecke des Zielrechtecks, Width und Height dessen Breite und Höhe.
Zurück bekommen Sie auch hier ein neues Bildobjekt.
Die Routine unternimmt keine Prüfung, ob X, Y, Width oder Height plausible Werte enthalten. Falls dies nicht der Fall sein sollte, dann erhalten Sie möglicherweise als Ergebnis Nothing.

 

Vielleicht möchten Sie ein Bildobjekt, das Sie zum Beispiel mit der LoadPictureGDIP-Methode erhielten, nach Weiterverarbeitung gar nicht als Datei abspeichern, sondern in das OLE-Feld einer Tabelle?
Dann hilft die Funktion ArrayFromPicture weiter:

Function ArrayFromPicture(ByVal Image As Object, _
                          PicType As PicFileType, _
                          Optional Quality As Long = 80) As Byte()

Die Funktion wandelt dabei das Bildobjekt in eine Speicherdatei eines wählbaren Typs um und gibt den Speicher als Byte-Array zurück.
Deshalb muss bei dieser Methode, wie beim Dateispeichern mit SavePicGDIPlus, angegeben werden, welches Format (BMP, PNG, etc.) das Zielbild haben soll, was mit dem Parameter PicFileType geschieht.
Für das Ziel JPEG können Sie zusätzlich die Qualitäts- bzw. Komprimierungsstufe im Parameter Quality (1...100) angeben.

 

Ist das Bild in einem OLE-Tabellenfeld gespeichert, dann möchten Sie daraus später natürlich wieder ein Bild erzeugen.
Das übernimmt die Methode ArrayToPicture:

Function ArrayToPicture(ByRef PicBin() As Byte) As StdPicture

Sie übergeben ihr ein Byte-Array, das Sie durch einfache Zuweisung aus einem Recordset-Feld erhalten (arrByte() = rst!OLEFeld), und bekommen von ihr ein StdPicture-Objekt.

 

Warum ein OLE-Feld, wenn doch Access 2007 ff. den neuen Datentyp Anlage unterstützen?
Ein OLE-Feld hat zwar bei der Performance die Nase vorn, dafür aber kann man in das Anlage-Tabellenfeld über dessen Anlage-Dialog Bilddateien direkt einspeichern, ohne eine einzige Zeile VBA bemühen zu müssen.
Deshalb gibt es im Modul auch noch eine analoge Anlagefeld-Funktion AttachmentToPicture:

Function AttachmentToPicture(strTable As String, _
                             strAttachmentField As String, _
                             strImage As String) As StdPicture

Die Methode will den Namen der Tabelle (strTable), den Namen des Anlagefelds darin (strAttachmentField) und schließlich die Bezeichnung einer Bilddatei (strImage).
Die Funktion geht davon aus, dass Sie jeweils nur eine Bilddatei in der Anlage gespeichert haben. Weitere Dateien ignoriert sie.
Den Namen der Bilddatei finden Sie übrigen, wenn Sie auf ein Anlagefeld doppelklicken, oder alle Dateinamen in der Tabelle mit einer Abfrage, wie dieser:

SELECT Anlagefeld.FileName As Dateiname FROM tblAnlagen

 

Damit Sie den Komfort auch bei in OLE-Feldern gespeicherten Bildern haben, gibt's noch eine ähnliche Funktion OLEFieldToPicture:

Function OLEFieldToPicture(strTable As String, _
                           strNameField As String, _
                           strName As String, _
                           strOLEField As String) As StdPicture

Auch hier wird der Tabellenname übergeben (strTable), der Name des OLE-Felds darin (strOLEField), aber zusätzlich der Name einer weiteren Text-Spalte, die einen Datensatz eindeutig identifizieren kann (strNameField).
Während in Anlagefeldern der Datename ja bereits im Unterfeld Anlage.FileName vorhanden ist, kann man aus einem OLE-Feld derlei nicht auslesen. Darum die zusätzliche Spalte.
Der korrekte Datensatz wird darin über den Inhalt des Parameters strName herausgefiltert.

 

Beispieldatenbank

In der Demo kommen fast alle der oben erläuterten Funktionen zum praktischen Einsatz.
In den beiden frmImage-Formularen werden Bilder aus OLE- bzw. Anlagefeldern erzeugt. Access-Bildsteuerelemente werden dabei nicht verwendet, weil diese mit StdPicture-Objekten nichts anfangen können.
Stattdessen kommen Image-ActiveX-Steuerlemente der MSForms-Bibliothek zum Einsatz, die mit jedem Office (inkl. Runtimes) installiert werden.

Außerdem werden die Icons der Schaltflächen im Formular ebenfalls dynamisch über Datensätze erhalten:

Demo-Formular zum OGL-Modul

Das linke Bild ist jeweils in Originalgröße dargestellt, das rechte ist das Ergebnis einer Resample-Operation.

Wenn Sie über die Navigationschaltflächen der Formulare zu einem neuen Datensatz gelangen, dann wechselt das linke Bild seinen Hintergrund in Blau.
Was Sie dann sehen, ist allerdings nicht mehr ein Bildsteuerelement, sondern ein Listview der mscomctl.ocx-Bibliothek, die ebenfalls immer mit Office (>=2003) installiert wird.
Das Listview unterstützt nämlich Drag&Drop-Operationen: Ziehen Sie auf das blaue Feld eine Bilddatei aus dem Explorer - schon wird sie als neuer Datensatz in der zugrundeliegenden Tabelle aufgenommen und im Steuerelement dargestellt.

Und da eingangs von den benutzerdefinierten Icons in Ribbons die Rede war, fehlen auch Bilder für Ribbon-Schaltflächen in der Demo nicht.
Sie werden aus Anlagefeldern beim Laden des Ribbon über die Methode AttachmentToPicture, aufgerufen in der entsprechenden CallBack-Funktion, erzeugt:

Button-Images aus dem OGL-Modul

Von eingehenderen Erklärungen über die Funktionsweise der Module in der Demo möchte ich Sie an dieser Stelle verschonen.
Schauen Sie sich doch einfach die Prozeduren an - der Umfang hält sich in Grenzen!


Viele weitere Beispiele für Bilder in Ribbons finden Sie übrigens auf Gunter Avenius Referenzwerk accessribbon.de.
Auch er setzt unser Modul für diesem Zweck ein.


Downloads

Es ist nicht ausgeschlossen, dass das OGL-Modul noch Weiterentwicklung erfährt, wie das in der Vergangenheit bereits der Fall war.
Schauen Sie hin und wieder hier herein, um sich über Änderungen zu informieren, oder die neueste Version herunterzuladen.

Download Beispieldatenbank (accdb)

Download GDIPlus-Modul (mdlOGL0710), Stand 08/2009

Download GDIPlus-Modul (mdlOGL0710), Stand 08/2010

 

 
Kommentare (3)
GDIPlus
3 Freitag, 23. Mai 2014 um 19:29 Uhr
Peter Ennis
I found that using the vi command
s/ogl/gdiplus/g
in module mdlOGL0710 of the demo made it work in 2013.
I will try you suggestion also.

Thanks,

Peter
Re: GDIPlus
2 Freitag, 23. Mai 2014 um 13:33 Uhr
Sascha Trowitzsch
Hi Peter,

Office 2013 Setup distributes neither ogl.dll nor gdiplus.dll. The prerequisites for O2013 are that you install it on a system already containing gdiplus 1.1. So there's no need to include it.
To workaround it use the latest version of the module and alter just one line in procedure GetGDIPVersion:
Change
Case "11.0" 'A2003
to
Case "11.0", "15.0" 'A2003, A2013

I did not update the module for use with O2013, so there is no corresponding branch in the Select..Case block.
GDIPlus
1 Freitag, 23. Mai 2014 um 03:57 Uhr
Peter Ennis
Office 2013, Windows 8.1
Run-time error '53':
File not found: ogl

Where do I send the error report captures?
I would like to fix this up for Access 2013
and include English translation.

Thanks,

Peter

Ihren Kommentar hinzufügen

Ihr Name:
Betreff:
Kommentar:
  Bild, welches den Sicherheitscode enthält
Sicherheitscode: