Virtual Developer Workshop: Containerized Development with Docker
Environment: VC++ 6.0/7.0, Windows 98 and later, GDI+.
Everything You Always Wanted to Know About Your Bitmaps
Bitmap pictures, especially those obtained with a digital photo camera, carry a lot of extra information. Apart from the obvious pixel values, they also have a bunch of so-called properties. Some of these properties are inherent, and tell something about the bitmap format, such as the pixel dimensions or the pixel format. Others, sometimes called 'attributes,' offer 'meta information' about the picture, such as the date and time it was made, or the type of exposure which was used.
Exif properties include detailed exposure information, such as camera make and model, shutter speed, F-stop, focal length, and focussing distance, which is supposed to be read-only. Other properties—such as picture description, author, and copyright—can be written. Cameras and software are allowed to, and almost always will, implement only a subset of the Exif standard. Some of the properties are clearly thought out for the future. Looking at the Exif properties, you can predict the next hot gadget: a camera which records the exact picture location and viewing direction, based on information of the GPS satellite system.
Apart from the Exif standard, some software vendors have developed their own properties, which are more or less also accepted as de facto standard. They may have impressive names such as 'photometric interpretation' or 'YCbCr subsampling.' Some of these seem to duplicate others, or to differ only in the way the information is stored. Sometimes, completely unknown properties pop up. In other words, the world of picture properties is a mess.
Nevertheless, GDI+, the new graphics API which was introduced with Windows XP, makes it relatively easy to retrieve these properties. I say 'relatively' easy, because the GDI+ calls you have to use are not exactly developer-friendly. If you want to display the picture properties in a dialog box, say, some nasty and rather boring code has to be written. Not to worry, because I already did so and packed it in a class that's easy usable in an MFC application.
The QImageProperties class makes it easy to retrieve or set the image properties. Its constructor takes a pointer to a GDI+ Image (almost always this will be a Bitmap, which is derived from Image):
QImageProperties(Image * pImage);
The properties are identified by a tag, an unsigned int, which in GDI+ is typedefed as a PROPID. The GDI+ header file GdiplusImaging.h has a large list of #defines for different property tags. QImageProperties has methods to get several types of properties:
bool GetProperty(PROPID tag, BYTE& d) const; bool GetProperty(PROPID tag, short& d) const; bool GetProperty(PROPID tag, UINT& d) const; bool GetProperty(PROPID tag, int& d) const; bool GetProperty(PROPID tag, REAL& d) const; bool GetProperty(PROPID tag, CString& s) const;
It also has counterparts to set different kinds of properties:
bool SetProperty(PROPID tag, BYTE d); bool SetProperty(PROPID tag, short d); bool SetProperty(PROPID tag, UINT d); bool SetProperty(PROPID tag, int d); bool SetProperty(PROPID tag, REAL d); bool SetProperty(PROPID tag, CString s);
These methods all return true if they succeed. Be prepared to receive a false return often. As stated earlier, bitmaps only carry a subset of the properties. Also, the property type often needs to be exactly correct (it is not possible to get or set an UINT value as an int, for instance).
The methods do not cover each and every possibility, I'm afraid. Specifically, they only work for singular properties. Though few, there are properties consisting of multiple values. QImageProperties does not support getting or setting these directly.
Two convenient methods return a CString:
CString PropertyString(PROPID tag) const; CString AllProperties(PROPID * pSkipList = NULL) const;
The first one returns a string consisting of a user-friendly (well, sort of) name of the property, followed by a textual representation of its value. If tag has the value of PropertyTagExifExposureTime, for instance, the returned CString might read: "Exif Exposure Time: 0.003125 (1/320)." A few properties return information in a custom format, most of them use a generalized format. If the property is not supported, or not present, the value part of the returned string is simply empty.
The second function concatenates all the property strings it can find. If you want certain properties to be excluded, put their tags in a zero-terminated (important!) list and let pSkipList point to it. See the demo project for an example.
Both functions have an extra default parameter (not shown here), defining the kind of colon between the property name and its value.
As an extra, I implemented a static method to build a matching string with file information (name, length, dates):
static CString FileString(LPCTSTR pFilePath);
Concatenate the return strings of AllProperties() and FilesString() and you have all the information you might ever wish about the bitmap—and probably more than that. The resulting string (which can be quite long) might be used to display in a dialog, as the demo project does.
Finally, the QImageProperties class has a member function to copy the properties of another instantation:
void Copy(const QImageProperties& source, PROPID * pSkipList = NULL, bool bSkipThumbnail = true);
If the boolean flag bSkipThumbnail has the default value of true, a range of properties having to do with an embedded thumbnail picture, including the thumbnail itself, is skipped in the copying process. This is useful in cases where the bitmap is modified and the thumbnail picture would become outdated. The pSkipList parameter may point to a zero-terminated list of property tags to skip, as in the AllProperties() method.
A ready-to-use dialog to display and edit picture properties is provided. It is called QImagePropertiesDlg. It displays the file information, and all the properties. On top of that, it lets you edit the properties Image Description, Author, and Copyright. You may fill the member variables yourself and use the trusted DoModal(), but there is also a method to set up and invoke the dialog in one go:
bool Show(Image * pSource, Image * pDest = NULL, LPCTSTR pFilePath = NULL);
In most cases, it will be sufficient to call it just with the pSource parameter, pointing to the source image. But, in some cases, you might want the updated properties stored in another image, pDest. The demo application is an example. If pFilePath is set, the file information is included in the property dialog.
Coding with QImageProperties
The most important aspect of using QImageProperties and QImagePropertiesDlg in your own code is that the classes need their own resources: a dialog template and a lot of strings. These resources are in the QImageProperties.rc resource script file.
This file should be included in the project's main resource file. In VC++ 6.0, choose 'Resource Includes' from the 'View' menu. In VC++ 7.0, go to the Resource View and choose the same from the context menu when right-clicking on the resource file. Enter the following under 'Compile-time Directives':
There is a potential possibility of conflicting resource IDs with this technique. QImageProperties only uses IDs in the 4000-5000 range, which is well out of the range MFC normally uses, but you should be on alert if you use it in a big application. The way resource IDs are used in Windows simply prevents strong encapsulating of code and resources. Personally, I find this one of the most annoying flaws in the Windows programming model.
As I'm sorry to say, QImageProperties is only partially usable under VC++ 6.0, the reason being that some methods are implemented as function templates, which VC++ 6.0 doesn't understand (it only knows about class templates). Look at the source for details. The demo project doesn't use the offending methods, compiles fine under VC++ 6.0, and is fully functional.
I used QImageProperties and QImagePropertiesDlg in the latest version of Tinter, my ever-maturing demo project with a growing number of bitmap goodies. The first version was written to demonstrate the possibilities of color matrices. Version 1.1 added convolution filters for sharpening and blurring pictures. This latest version has a new 'Properties' command in the 'View' menu, and a shortcut button on the toolbar.
Like almost everything else in Tinter, this command is handled by the view class, CTinterView. Most things regarding the image property classes are in the handler OnViewProperties(). Notice that there are some subtleties in the code: As long as the picture is unmodified, we take the properties from the disk-based picture, but as soon as something is changed to the picture, we grab them from the memory bitmap.
QImageProperties, the Copy() member function in particular, is also used in opening a file (handled in OnUpdate()), and in saving (member function Save()).
While I had my hands on a new version of Tinter to incorporate property viewing, I added a few other enhancements. In fact, I almost completely rewrote CTinterView. To summarize:
- A big improvement is that Tinter now has zooming capabilities. I used my own class QZoomView to accomplish that;
- It now supports Twain, for scanning and importing pictures from cameras. To implement that, I used Rajiv Ramachandran's excellent CTwain class;
- Yet another control to modify the picture was added. Now you can also make it negative. It is almost the simplest you can do to a bitmap, but I overlooked it in previous versions;
- I completely changed the handling of rotating and flipping because I wasn't satisfied with the implementation in Tinter 1.1.
Note: For this project, your system must support GDI+, which currently only XP does natively. However, other Windows versions can be upgraded. Also, VC++ 6.0 comes without the GDI+ headers. You may obtain them by downloading the Windows Platform SDK. The GDI+ headers are included with VC++ 7.0.
DownloadsDownload demo project and source - 159 Kb
Download demo project executable only - 49 Kb