Tutorial

IM - Imaging Libray

Advanced Guide

Memory I/O and Others

For the majority of the formats, with the exception of the ones that use external SDKs, the I/O is done by the imBinFile module.

This module can be configured to access other types of media by implementing a driver. There are some predefined drivers see Reference / Utilities / Binary File Access.

One very useful is the Memory Buffer where you can read and write a file in memory. The activation is very simple, it needs to happen just before the imFileOpen/imFileNew functions. But the file name must be a pointer to an imBinMemoryFileName structure instead of a string. Se the example bellow:

int old_mode = imBinFileSetCurrentModule(IM_MEMFILE);

imBinMemoryFileName MemFileName; // This structure must exists while the file remains open.
MemFileName.buffer = NULL; // Let the library initializes the buffer,
                           // but it must be freed the the application, free(MemFileName.buffer) MemFileName.size = 1024; // The initial size
MemFileName.reallocate = 1.5; // The reallocation will increase 50% the buffer.
                              // This is used only when writing with a variable buffer.
                              // Use 0 to fix the buffer size.

int error;
imFile* ifile = imFileNew((const char*)&MemFileName, "GIF", &error);

imBinFileSetCurrentModule(old_mode); // The mode needs to be active only for the imFileOpen/imFileNew call.

if (error != IM_ERR_NONE) ....

Another driver interesting is the Subfile where you can read and write from a file that is already open. This is very important for formats that can have an embedded format inside. In this module the file_name is a pointer to an imBinFile  structure from any other module that uses the imBinFile functions. The imBinFileSize will return the full file size, but the imBinFileSeekTo and imBinFileTell functions will compensate the position when the subfile was open.

Using imBinFileSetCurrentModule(IM_SUBFILE) just like the example above will allow you to open a subfile using the imFileOpen/imFileNew functions.

New Operations

An operation complexity is directly affected by the number of data types it will operate.

If it is only one, than it is as simple as:

void DoProc(imbyte* data, int width, int height)
{
  for (int y = 0; y < height; y++)
  {
    for (int x = 0; x < width; x++)
    {
      // Do something
      int offset = y * width + x;

      data[offset] = 0;
    }
  }
}

void SampleProc(imImage* image)
{
  // a loop for all the color planes
  for (int d = 0; d < image->depth; d++)
  {
    // Notice that the same operation may be used to process each color component
    DoProc((imbyte*)image->data[d], image->width, image->height);
  }
}

Or if you want to use templates to allow a more number of types:

template <class T> 
void DoProc2(const T* src_data, T* dst_data, int count)
{
  for (int i = 0; i < count; i++)
  {
    src_data[i] = dst_data[i];
    
    // or a more low level approach
    
    *src_data++ = *dst_data++;
  }
}

// This is a sample that do not depends on the spatial distribution of the data.
// It uses data[0], the pointer where all depths depends on.

void SampleProc2(const imImage* src_image, imImage* dst_image)
{
  int total_count = src_image->count * src_image->depth; 
  switch(src_image->data_type)
  {
  case IM_BYTE:
    DoProc((imbyte*)src_image->data[0], (imbyte*)dst_image->data[0], total_count);
    break; 
  case IM_USHORT:
    DoProc((imushort*)src_image->data[0], (imushort*)dst_image->data[0], total_count);
    break; 
  case IM_INT: 
    DoProc((int*)src_image->data[0], (int*)dst_image->data[0], total_count);
    break; 
  case IM_FLOAT: 
    DoProc((float*)src_image->data[0], (float*)dst_image->data[0], total_count);
    break; 
  case IM_CFLOAT: 
    DoProc((imcfloat*)src_image->data[0], (imcfloat*)dst_image->data[0], total_count);
    break;
  }
}

The first sample can be implemented in C, but the second sample can not, it must be in C++. Check the manual and the source code for many operations already available.

New File Formats

Again the easiest way is to look at the source code of an already implemented format. The RAS, BMP, TGA and SGI formats are very simple to follow.

Basically you have to implement a class that inherits from imFormat and implement its virtual methods. You can use the imBinFile functions for I/O or use an external SDK.

For more information see Reference / Image Storage / File Format SDK.

Counters

To add support for the counter callback to a new operation is very simple. The following code shows how:

int counter = imCounterBegin("Process Test 1");
imCounterTotal(counter, count_steps, "Processing");

for (int i = 0; i < count_steps; i++)
{
  // Do something


  if (!imCounterInc(counter))
    return IM_ERR_COUNTER;
}

imCounterEnd(counter);

Every time you call imCounterTotal between a imCounterBegin/imCounterEnd for the same counter means that you are starting a count at that counter. So one operation can be composed by many sub-operations and still have a counter to display progress. For example, each call to the imFileReadImageData starts a new count for the same counter.

A nice thing to do when counting is not to display too small progress. To accomplish that in the implementation of the counter callback consider a minimum delay from one display to another.

See Reference / Utilities / Counter.

Names Convention

To improve the readability of the code we use a very simple naming convention:

  • Global Functions and Types - "im[Object][Action]" using first capitals (imFileOpen)
  • Local Functions and Types - "i[Object][Action]" using first capitals (iTIFFGetCompIndex)
  • Local Static Variables - same as local functions and types (iFormatCount)
  • Local Static Tables - same as local functions and types with "Table" suffix (iTIFFCompTable)
  • Variables and Members - no prefix, all lower case (width)
  • Defines and Enumerations - all capitals (IM_ERR_NONE)

C x C++ Usage

The library main API is in C. We adopt this because of the many C programmers out there. Some of the API is also available in C++ for those addicted to classes.

Internally C++ is used to implement the format driver base architecture. A virtual base class that every drivers inherits from. This made a lot of things easier to the driver development. But we keep it simple, no multiple inheritance, no exception handling, no complicated classes.

But because we need several data types C++ templates were inevitable used (since we do not like long macros everywhere). But they are used only for processing functions, not classes.