Plug In / Plug Out

In spite of my recent addiction to Battlestar Galactica, the best show on television at the moment, I spent last night working on some cross-platform C++ imaging code that I’ve been tweaking on and off over the past ten or so years. One of the things that I’ve been adding to the library is the capability to read from and write to graphics files.

As simple of a feature as this might sound, the code that I have is based on stuff that I’d developed for a former company, long since demised, and all the file I/O code was provided by a 3rd party vendor. Now, I’m doing it (mostly) myself. (There are some good freeware libraries that I will try to use.)

In order to minimize the need for rebuilding the binary libraries whenever a file format needs to be supported or fixed, I’m using a plug-in scheme to handle the file I/O functionality. If you’re a Photoshop user, you may be familiar with the concept, but not the nitty-gritty. So, here’s the nitty-gritty:

A plug-in is a code module, a shared library to use the jargon, that can be referenced from multiple applications. However, plug-ins differ from generic shared libraries in that they adhere to a standard usage interface defined by the application and/or overseeing code library. Behind the interface, the plug-in can do what it does uniquely, but it must communicate with the overseeing code through a well-defined interface.

To use my Imaging library as an example, it defines the following as part of the ImageFileFormatIF API (application programming interface):

  • Sniffed Sniff(FileHandle&)
  • bool Read(FileHandle&, Image&)
  • bool Write(FileHandle&, Image&)

Sniff is a function that reads some data from the file and returns one of the following set of values: NoMatch, PartialMatch, FullMatch and FullMultiMatch. Sniff() is used to determine the file format of the file with logic that is contained wholly within the file format plug-in. To determine if the file is a JPEG file, we call the JPEG plug-in’s Sniff() function and get a result.

Aside: Why Sniff()? Because it is an error on some operating systems to associate file format types with filename extensions. Naming JPEG files with .jpg is a convention. It is not canon. I’ve seen some JPEG files with a .jpeg extension, which is just as valid. Using Sniff() gets to the heart of what the file is based on its contents, not its naming conventions. Besides, I’ve seen e-mail viruses transmitted b/c someone clicked on a .jpg attachment only to have the file execute as a running program. If we want this madness to end, we need to stop relying on purposely erroneous filenames provided by malicious users. Programmers need to stop being lazy and read the data.

Read() and Write() are obvious. Read loads the encoded image data in the supplied file into the supplied Image data structure. Write does the opposite.

But, how do we use it. The Imaging library includes an object called the ImageFileFormatManager, which is used by the calling code to read in all the available plug-ins from a supplied directory (or folder). The name of that directory can be hard-coded, entered from an application or gleaned from the operating systems environment settings. Each plug-in, in addition to supporting the defined API, will also contain identifying information so that applications and users can identify it.

The ImageFileFormatIF contains 3 identifying characteristics:

  • Name
  • Family Id
  • Plug-in Id

The name is a user-readable character string, such as “JPEG Image File Format Plug-in”. The Family Id is a 32-bit integer that is used to differentiate ImageFileFormatIF plug-ins from other plug-in types. All of the image file format plug-ins have a family id of 0×494d4746, which are the ASCII codes for ‘IMGF’. All plug-ins with that family id are assumed to support the ImageFileFormatIF specification. Finally, each plug-in has a unique plug-in id. For JPEG, it’s 0×4a504547, or ‘JPEG’.

Now that we’ve identified our plug-ins, we can write a simple sniffer routine:


ImageFileFormatManager formatManager("/usr/local/lib/plugins");
IFFM_Node *p;

for (p = formatManager.first(); p != NULL; p = p->next) {
    if (p->handler) {
        switch (p->handler->Sniff(handle)) {
            case FullMatch:
                printf("Handler found. Image is %s\n", p->handler->Name());
                return p->handler;
        }
    }
}

There, easy enough code to write and be able to correctly detect the format of a given image file and load the image via the p->handler->Read() function. It’s all tied together.

Writing is a different animal. Whereas the image format is set by the existing file during a read process, the user, or application, chooses the file format of image to be written. In this case, ImageFileFormatManager can present the user with a list of supported/available formats using each handler’s Name() function and using that handler’s plug-in id to load that specific handler.

We do have one major wrinkle to work out. Not only is the image file’s format determined by the user during a write process, but any options available are also determined by the user. Options also vary by file format. Therefore, the Imaging library will have to define a communications protocol to pass and/or present file storage options to and/or from each plug-in.

Note the phrase “will have to”. I’ve neither defined the protocol nor written that code yet. What I have done so far is write 5 different format plug-ins: TIFF, GIF, PNM, BMP, JPEG. None are fully completed. As I said in previous posts, I have a lot of work to do.

I’m current working on the BMP plug-in b/c I need to see if the others work. I’m using BMP b/c it is supported by web browsers and does not require data compression. So, to test the other plug-ins, my test program will read in other graphics files and write out a BMP file, which can then be read by my web browser.

Did I mention that I’m doing all my development on my web server system?

Comments are closed.