X
3705 Rate this article:
No rating

Using the Clipboard, Static Methods and ZLIB compression in IDL 8.3

Jim Pendleton

When the Exelis Professional Services Group is contracted to author "large" applications for clients, we often have the luxury of building full installers that we use to place a variety of resource files and directories onto disk, along with the main functionality of an application or toolset.

In this environment, the ROUTINE_FILEPATH  function is a huge help for bootstrapping the application so it can find its resource files relative to its installation directory.

For smaller projects, however, it is often more convenient if all the resources, such as bitmap button images, can be bundled into a single file, in particular a compiled IDL SAVE file. This reduces the complexity of installation and testing.

Various routines exist for turning, say, bitmap images into IDL source code. I will show a version below that employs the new IDL 8.3 function for lossless, in-memory ZLIB compression of the image data. It also uses the new Clipboard static class so you can paste the generated code into a text editor with a single button click.

Finally, we'll investigate a utility for automatically generating an entire class of static methods which store a directory of images, greatly simplifying their accessibility.

The routine listed below takes as input an image in the form of a BYTARR along with a string that will be applied as the name of a new IDL function. It generates PRO code and places it into the system's clipboard as ASCII text. You can then paste the generated code directly into an editor window, which you will compile and execute.

PRO ImageToCode, image, functionName
 d = SIZE(image, /DIMENSIONS)
 z = ZLIB_COMPRESS(image)
 b64 = IDL_BASE64(z)
 l = LIST()
 l.Add, 'FUNCTION ' + functionName
 dims = STRJOIN(STRTRIM(D, 2), ',')
 l.Add, 'lines = LIST()'
 FOR i = 0L, STRLEN(b64) - 1, 66 DO $
    l.Add, "lines.ADD, '" + STRMID(b64, I, 66) + "'"
 l.Add, 's = IDL_BASE64(STRJOIN(lines.ToArray(/NO_COPY), ""))'
 l.Add, 'd = ZLIB_UNCOMPRESS(s)'
 l.Add, 'RETURN, REFORM(d, ' + dims + ')'
 l.Add, 'END'
 Clipboard.Set, l.ToArray(/NO_COPY)
END

In order to make the output code for the image data we first call the ZLIB_COMPRESS function to create an in-memory version of the image bitmap, using lossless compression. To simplify the code we will make an initial assumption that the input image is a 24-bit (RGB) image in a BYTARR.

Because we want to create IDL source code from this, we convert the binary data produced by ZLIB_COMPRESS into an ASCII format with the IDL_BASE64 function. This is a useful trick for encoding any type of binary data that needs to be exchanged in an ASCII format, for example via standard JSON objects passed to and from an ENVI Services Engine instance.

In order to make the output PRO code more legible, the output string from IDL_BASE64 is broken up over multiple lines, each with a maximum length of 80 characters. 

The new function code we create performs the inverse operations, converting the string back to binary via IDL_BASE64, then regenerating the BYTARR binary data via the ZLIB_UNCOMPRESS function.  Notice that the REFORM function call is required because the ZLIB compression doesn't encode the dimensionality of the original input data.

Try this:

 

IDL> f = filepath('colorbar24.png', subdir=['resource','bitmaps'])
IDL> imagetocode, read_image(f), 'cbimage'

 

Simply open an editor window and paste the contents of the system clipboard to it.  The imagetocode routine has populated the clipboard with the text of the routine. 

The output should look like this:

FUNCTION cbimage
lines = LIST()
lines.ADD, 'eJz6//8/w/9RPGLwuct3/udUTf2PLDZ32Y7/IIwsBlIDUossZuNX9H8gzQfpAamhFK'
lines.ADD, 'O7D4Yb+lf9r+lZ9b+ic9X/kraV/wtaVv7PaVz5P6Nuxf/UmhX/EytX/I8tW/4/snj5'
lines.ADD, '/9DC5f8D85b/981e9t8zc9l/17Rl/x2Tl/63TVz63zJu6aj5o+aPmj9qPknmj2L6Yw'
lines.ADD, 'AAAAD//wMA32owCw=='
s = IDL_BASE64(STRJOIN(lines.ToArray(/NO_COPY), ""))
d = ZLIB_UNCOMPRESS(s)
RETURN, REFORM(d, 4,24,24)
END

Of course the translation routine could have been designed to write its output to a file directly, but where would the fun be in that?

Save and compile the new file, cbimage.pro.

Display the output by executing the function in the context of the IMAGE function.

IDL> image(cbimage())

Now for some additional fun, the algorithm can be adapted easily to create an entire class of static methods.

For example, I may want to extend this functionality to create a single static class that contains all the bitmaps in the IDL distribution's resource\bitmaps directory. Using this mechanism, I would no longer need to include the files in that directory in my application distribution. I would simply include my object class, whether in source code or in the form of compiled code in a SAVE file.  I can simply reference each bitmap via the static class method name.

PRO ImagesToStaticClass, directory, className
files = FILE_SEARCH(FILEPATH('*', ROOT = directory))
l = LIST()
FOREACH file, files DO BEGIN
  CATCH, errorNumber
  IF (errorNumber NE 0) THEN BEGIN
    CATCH, /CANCEL
    CONTINUE
  ENDIF
  image = READ_IMAGE(file, r, g, b)

  CATCH, /CANCEL
  IF (N_ELEMENTS(image) LT 2) THEN CONTINUE
  s = SIZE(image, /STRUCTURE)
  IF (s.N_DIMENSIONS eq 2) THEN BEGIN
    newImage = BYTARR(3, s.Dimensions[0], s.Dimensions[1])
    newImage[0, *, *] = r[image]
    newImage[1, *, *] = g[image]
    newimage[2, *, *] = b[image]
    image = TEMPORARY(newimage)
  ENDIF

  image = TRANSPOSE(TEMPORARY(image), [1, 2, 0])
  d = SIZE(image, /DIMENSIONS)
  baseName = FILE_BASENAME(file)
  methodName = IDL_VALIDNAME(STRMID(baseName, 0, $
    STRPOS(baseName, '.', /REVERSE_SEARCH)))
  IF (methodName eq '') THEN CONTINUE
  z = IDL_BASE64(ZLIB_COMPRESS(image))
  l.Add, 'FUNCTION ' + className + '::' + methodName
  l.Add, 'COMPILE_OPT STATIC'
  dims = STRJOIN(STRTRIM(D, 2), ',')
  l.Add, 'lines = LIST()'
  FOR i = 0L, STRLEN(z) - 1, 66 DO $
    l.Add, "lines.ADD, '" + STRMID(Z, I, 66) + "'"
  l.Add, 's = IDL_BASE64(STRJOIN(lines.ToArray(/NO_COPY), ""))'
  l.Add, 'd = ZLIB_UNCOMPRESS(s)'
  l.Add, 'RETURN, REFORM(d, ' + dims + ')'
  l.Add, 'END'
ENDFOREACH
l.Add, 'PRO ' + className + '__DEFINE'
l.Add, '!null = {' + className + ', INHERITS IDL_Object}'
l.Add, 'END'
Clipboard.Set, l.ToArray(/NO_COPY)
END

Copy, save, and compile the source shown above.

Execute the routine giving it the name of a directory containing image files and the name of the object class to generate.  For example,

IDL> imagestostaticclass, FILEPATH('', SUBDIR=['resource','bitmaps']), 'IDLBitmaps'

Paste the routine's output to an editor file and save it as idlbitmaps__define.pro, then compile it.

To create an "open folder" bitmap widget button, for example, I can simply specify a call to the static method whose name corresponds to the original image file name, modulo a call to IDL_VALIDNAME.

IDL> tlb = WIDGET_BASE()
IDL> b = WIDGET_BUTTON(tlb, VALUE=IDLBitmaps.Open(), /BITMAP)
IDL> WIDGET_CONTROL, tlb, /REALIZE

Many of the bitmap files in IDL's resource directory are palletized, single plane images each with an associated color look-up table. For the sake of expediency and code simplicity at the expense of code size in ASCII characters, they're converted by the utility from single-plane images to RGB format before compressing them.

Notice that the utility skips any files which cannot be read successfully by the READ_IMAGE function. Other than that there is no robust error checking, an exercise left for the reader.

The routine returns the images dimensioned [x,y,3], the format required by WIDGET_BUTTON.