This is an explanation of what the package Tools-Resource Library just published to the open repository does, and how it does that.
VisualWorks applications, at least most of those I've been working on, use images prepared outside of the VisualWorks environment. Some of the older icons I made, including those in the Launcher and the RB were created using CoolImage--but nice as it is compared to the default Image Editor, it is no Photoshop killer. And arguably, replicating Photoshop or Gimp in Smalltalk is a waste of time better used otherwise. This means that bringing images (and perhaps some other resources) prepared in external applications into the image is a task worth supporting.
Currently, the class ImageReader can import external images, creating resource methods in one of the icon libraries. Unfortunately, this workflow is hard to manage, as the association of resource methods and their source files is not preserved. It's hard to keep track of what resource methods need updating after some of the source files change. The only workaround is maintaining a master import utility method that reimports all of the files "wholesale".
Additionally, the IconLibrary facility itself is showing its age. The mechanism that maps icon names to resource methods is rather complicated, mostly because of its unused ability to support multiple icon sizes and because of caching in the library.
Tools-Resource Library addresses all of these deficiencies. As an introduction, here is how one would use it in an application.
Begin by creating a subclass of ResourceLibrary. For the sake of the example, let's assume the class name is FooResources.
For each image you want to import in the library, write a class-side resource method marked with a pragma identifying the resource type and source file name. For example:
productIcon
<opaqueImage: 'product.gif'>
The method (for now) needs no code in the body other than the pragma. The selector and the protocol are entirely up to you. We are assuming that product.gif file is present in the current directory.
Now, in a workspace near you evaluate the expression "FooLibrary regenerate". Now look at the two methods again. If the files mentioned in the pragmas were indeed accessible, regenerating replaced the code of the methods with something like (omitting some non-essential comments):
productIcon
<opaqueImage: 'product.gif' >
"Generated from: product.gif
timestamped: March 31, 2006 22:10:50.000
on: January 15, 2007 11:08:50.161"
| figure shape |
figure := CachedImage on: ...stuff... .
shape := CachedImage on: ...stuff... .
^OpaqueImage figure: figure shape: shape
This shows the first thing that is special about ResourceLibrary and its subclasses. They can maintain methods with data imported from external files, and update them as necessary. Each time the class is sent the #regenerate message, the timestamp of the imported file stored as a comment inside a resource method is checked to see if the method needs updating.
All that's necessary to get the imported image is evaluating the expression "FooLibrary productIcon", which will return an instance of OpaqueImage, just as the method code shows.
Now, what about efficiency? It seems that every time the method is executed a new OpaqueImage is created. This certainly is a waste of both cycles and storage since these images could be cached and shared, as IconLibrary already does. Let's look at the method closer. Inspecting it shows that its bytecode in fact looks as follows:
short AnnotatedMethod numArgs=0 numTemps=0 frameSize=0
literals: (an OpaqueImage )
1 <1C> push an OpaqueImage
2 <65> return
What we have here is essentially compile-time evaluation at the method level, performed by the special compiler assigned to resource libraries. The code in the method body was evaluated by the compiler and saved as a literal returned by the method. Or, to look at it in a different perspective, we do have caching, with each resource access method doubling as a cache entry.
Only resource methods (those marked with one of the predefined resource pragmas) on the class side of ResourceLibrary and its subclasses are compiled in this fashion. All other methods are compiled and work as usual. The can also be filed out or published to a Store repository.
The following resource types (pragma keywords) are supported:
opaqueImage: -- an image file is imported and stored as an OpaqueImage. Supported image formats are those supported by ImageReader. As of VW 7.5, .gif and .png are best bets. Note that OpaqueImage does not support alpha channels, so only .png files with full transparency/opacity can be meaningfully imported.
image: -- an image file is imported and stored as a CachedImage on an Image.
xmlDocument: -- an XML file is imported, parsed and stored as an XML Document.
binaryFileContents: -- the contents of an arbitrary file are imported and saved as a ByteArray.
A library class understands the following messages of notice:
#regenerate -- regenerate the source code and recompile all resource methods that are out of date. "Out of date" means either that there is no "timestamp" line in the method, which is interpreted as a method that has not yet been generated from its file, or the timestamp of the source file remembered in the method differs from the file on the disk. If the source file of a method is missing, the method is left untouched.
#regenerateAll -- regenerates all resource methods of the class, regardless of whether they are out of date or not.
#pragmasWithMissingSourceFiles -- returns Pragma instances pointing to the methods whose original files are missing.
By default, files mentioned in resource pragmas are looked up in the current directory. ResourceLibrary defines a class instance variable rootDirectory, which can be set in each subclass to point to a different directory to look in.