[ImageMagick]
[sponsor]

The citizens of Oz were quite content with their benefactor, the all-powerful Wizard. They accepted his wisdom and benevolence without ever questioning the who, why, and where of his power. Like the citizens of Oz, if you feel comfortable that ImageMagick can help you convert, edit, and compose your images without knowing the the nitty-gritty details on hows its done, feel free to skip this section. However, if you want to know more about the software and algorithms behind ImageMagick, read on. To fully benefit from this discussion, you should be comfortable with image nomenclature and be familiar with computer programming.

Architecture Overview

An image typically consists of a rectangular region of pixels and metadata. To convert, edit, or compose an image in an efficient manner we need convenient access to any pixel anywhere within the region. And in the case of an image sequence, we need access to any pixel of any region of any image in the sequence. However, there are hundreds of image formats such JPEG, TIFF, PNG, GIF, etc., that makes it difficult to access pixels on demand. Within these formats we find differences in:

  • colorspace (e.g RGB, CMYK, YUV, Lab, etc.)
  • bit depth (.e.g 1, 4, 8, 12, 16, etc.)
  • storage format (e.g. unsigned, signed, float, double, etc.)
  • compression (e.g. uncompressed, RLE, Zip, BZip, etc.)
  • orientation (i.e. top-to-bottom, right-to-left, etc.),
  • layout (.e.g. raw, interspersed with opcodes, etc.)

In addition, some image pixels may require attenuation, some formats permit more than one image, and some formats contain vectors that must be first rasterized (converted from vector to pixels).

An efficient implementation of an image processing algorithm may require we get or put:

  • one pixel a time (e.g. pixel at location 10,3)
  • a single scanline (e.g. all pixels from row 4)
  • a few scanlines at once (e.g. pixel rows 4-7)
  • an arbitrary region of pixels from the image (e.g. pixels defined at 10,7 to 10,19)
  • a pixel in random order (e.g. pixel at 14,15 and 640,480)
  • pixels from two different images (e.g. pixel at 5,1 from image 1 and pixel at 5,1 from image 2
  • pixels outside the boundaries of the image (e.g. pixel at -1,-1)

In addition, some images include a clip mask that define which pixels are eligible to be updated. Pixels outside the area defined by the clip mask remain untouched.

Given the varied image formats and image processing requirements, we implemented the ImageMagick pixel cache to provide convenient access to any pixel on demand anywhere in the image region and from any image in a sequence. In addition, the pixel cache permits access to pixels outside the boundaries defined by the image (we call these virtual pixels).

In addition to pixels, images have a plethora of image attributes and profiles. Attributes include the well known items like width, height, depth, and colorspace. An image may have optional attributes which might include the image author, a comment, a create date, and others. Some images also include profiles for color management, or EXIF, IPTC, 8BIM, or XMP informational profiles. ImageMagick provides command line options and programming methods to get, set, or view image attributes or profiles or apply profiles.

ImageMagick has more than 275,000 lines of C code and optionally depends on over a million lines of code in dependent libraries (e.g. JPEG, PNG, TIFF libraries). Given that, one might expect a huge architecture document. However, a great majority of image processing is simply accessing pixels and its metadata and our simple and elegant implementation makes this easy for the ImageMagick user. We discuss the implementation of the pixel cache and getting and setting image attributes and profiles in the next few sections. Next, we discuss image coders to read or write a particular image format followed by a few words on creating your own image filters. In the final section, we discuss using ImageMagick within a thread of execution.

The Pixel Cache

The ImageMagick pixel cache is a repository for image pixels with up to 5 channels. The first 4 channels are stored first and an optional second area follows with 1 channel. The channels are at the depth specified when ImageMagick was built. The channel depths are 8 bits-per-pixel for the Q8 version of ImageMagick, 16 bits-per-pixel for the the Q16 version, and 32 bits-per-pixel for the Q32 version. The primary 4 channels can hold any value but typically contain red, green, blue, and alpha intensities or cyan, magenta, yellow, and alpha intensities. The optional fifth channel contains the colormap indexes for colormapped images or the black channel for CMYK images. The pixel cache storage may be anonymous memory mapped memory, heap memory, disk-backed memory mapped, or on disk. The pixel cache is referenced counted. Only the cache properties are copied when the cache is cloned. The cache pixels are only copied when you signal your intentions to update any of the pixels.

Create the Pixel Cache

The pixel cache is created when an image is created and you try to get or put pixels. Here are three methods to create a pixel cache associated with an image:

  • Create an image canvas initialized to the background color:
      image=AllocateImage(image_info);
      if (SetImageExtent(image,640,480) == MagickFalse)
        { /* an exception was thrown */ }
      (void) QueryMagickColor("red",&image->background_color,&image->exception);
      SetImageBackgroundColor(image);
    
  • Create an image from a JPEG image on disk:
      (void) strcpy(image_info->filename,"image.jpg"):
      image=ReadImage(image_info,exception);
    
  • Create an image from a memory based image:
      image=BlobToImage(blob_info,blob,extent,exception);
    

In our discussion of the pixel cache we use the MagickCore API to illustrate our points but the principals are the same for other program interfaces to ImageMagick.

When the pixel cache is created, the pixels are scaled from the whatever bit depth they originated from to that of the pixel cache. For example, a 1-channel 1-bit monochrome PBM image is scaled to a 4 channel 8-bit RGBA image assuming we are using the Q8 version of ImageMagick. You can determine which version you have with this command:

  -> identify -version
  Version: ImageMagick 6.2.7 04/30/06 Q16 http://www.imagemagick.org

As you can see, the convenience of the pixel cache sometimes comes with a tradeoff in storage and speed.

Access the Pixel Cache

Once the pixel cache is created you will want to get, update, or put pixels to it. Use these methods to get at the pixels in the cache:

Here is some typical MagickCore code for manipulating pixels in the pixel cache. In our example we copy pixels from the input image to the output image and decrease the intensity by 10%:

  long
    x,
    y;  

  const PixelPacket
    *p;

  PixelPacket
    *q;

  destination=CloneImage(source,0,0,MagickTrue,exception);
  if (destination  == (Image *) NULL)
    { /* an exception was thrown */ }
  for (y=0; y < (long) source->rows; y++)
  {
    p=AcquireImagePixels(source,0,y,source->columns,1);
    q=GetImagePixels(destination,0,y,destination->columns,1);
    if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL)
      break;
    for (x=0; x < (long) source->columns; x++)
    {
      q->red=90*p->red/100;
      q->green=90*p->green/100;
      q->blue=90*p->blue/100;
      q->opacity=90*p->opacity/100;
      p++; 
      q++; 
    }
    if (SyncImagePixels(destination) == MagickFalse)
      break;
  }
  if (y < (long) source->rows)
    { /* an exception was thrown */ }

When we first create the destination image by cloning the source image, the pixel cache pixels are not copied. They are only copied when we signal our intentions of updating the pixel cache by calling GetImagePixels() or SetImagePixels(). The SyncImagePixels() method does the actual pixel updates to the cache.

Recall we mentioned that the indexes of a colormapped image or the black channel of a CMYK image are stored separately. We use GetIndexes() to gain access to this channel. For example, to print the colormap indexes, use:

  IndexPacket
    *indexes;

  for (y=0; y < (long) source->rows; y++)
  {
    p=AcquireImagePixels(source,0,y,source->columns,1);
    if (p == (const PixelPacket *) NULL)
      break;
    indexes=GetIndexes(source);
    for (x=0; x < (long) source->columns; x++)
      (void) printf("%d\n",indexes[x];
  }
  if (y < (long) source->rows)
    /* an exception was thrown */

The pixel cache decides whether to give you direct access to the image pixels. In most cases the pixels are staged to an intermediate buffer-- and that is why you must call SyncImagePixels() to ensure this buffer is pushed out to the pixel cache to guarantee the corresponding pixels in the cache are updated. For this reason we recommend that you only acquire, get, or set a scanline or a few scanlines of pixels at a time. However, you can get any rectangular region of pixels you want. For GetImagePixels(), you must get a region within the bounds of the image area. For a 640 by 480 image, you can get a scanline of 640 pixels but if you ask for 641 pixels an error is returned. AcquireImagePixels() does not have this constraint. For example,

  p=AcquireImagePixels(source,-3,3,source->columns+7,7);

gives you the pixels you asked for without complaint.

Virtual Pixels

We call any pixels outside the image region virtual pixels. Their value is defined by the SetImageVirtualPixelMethod() from the MagickCore API or -virtual-method option from the command line. The methods include:

  background:  The area surrounding the image is the background color.
  edge:        Extend the edge pixel toward infinity (default).
  mirror:      Mirror the image.
  tile:        Tile the image.
  transparent: The area surrounding the image is transparent blackness.

A fair number of image processing algorithms require a neighborhood of pixels about the pixel of interest. There is typically a caveat concerning how to handle pixels around the image boundaries, known as edge pixels. With virtual pixels, you do not need to concern yourself about special edge processing other than choosing which virtual pixel method is most appropriate for your algorithm.

Cache Storage and Resource Requirements

We mentioned previously that this simple and elegant design of the ImageMagick pixel cache comes at a price in terms of storage and processing speed. The pixel cache storage requirements scales with the area of the image and the bit depth of the pixel components. For example, if we have a 640 by 480 image and we're using the Q16 version of ImageMagick, the pixel cache consumes image width times height times bit depth divided by 8 times the number of channels bytes or approximately 2.3 megabytes (i.e. 640 * 480 * 2 * 4). Not too bad, but what if your image is 25000 by 25000 pixels. We now need approximately 4.7 gigabytes of storage. Ouch. ImageMagick accounts for possible huge storage requirements by caching large images to disk rather than memory. By default, the pixel cache is stored in memory using anonymous memory mapping, if that fails we try heap memory, if that fails we create the pixel cache on disk and attempt to memory-map it, if that fails we simply use standard disk I/O. Disk is cheap but its also very slow, upwards of 1000 times slower than memory. We can get some speed improvements, up to 5 times, if we use memory mapping to the disk-based cache. These decisions about storage are made automatically by ImageMagick negotiating with the operating system. However, you can influence how ImageMagick allocates the pixel cache with cache resource limits. The limits include:

    files
    maximum number of open pixel cache files. When this limit is exceeded, any subsequent pixels cached to disk are closed and reopened on demand. This behavior permits a large number of images to be accessed simultaneously on disk, but with a speed penalty due to repeated open/close calls.
    area
    maximum width * height of an image that can reside in the pixel cache memory.
    memory
    maximum amount of memory in megabytes to allocate for the pixel cache from the anonymous memory mapping or the heap.
    map
    maximum amount of memory map in megabytes to allocate for the pixel cache.
    disk
    maximum amount of disk space in gigabytes permitted for use by the pixel cache. When this limit is exceeded, cache.

To determine the current setting of these limits, use this command:

  -> identify -list resource
  File       Area     Memory        Map       Disk
  ------------------------------------------------
   960   5.8711gb   7.8281gb   15.656gb       16eb

You can set these limits either with environment variables or with the SetMagickResourceLimit() MagickCore API method. As an example, our online web interface to ImageMagick, ImageMagick Studio, has a memory limit of 32 megabytes and a map limit of 64 megabytes and a disk limit of 1 gigabytes. Since we process multiple users at once we don't want any one user consuming all the available memory on our system. Instead large images are cached to disk which stops these users from consuming too much process time as well (instead they consume I/O time). If the image they upload is too large, the pixel cache disk limit is exceeded and the program exits.

Note, the cache limits are global, meaning if you create several images, the combined resource requirements are compared to the limit to determine the pixel cache storage disposition.

Cache Views

AcquireImagePixels(), GetImagePixels(), SetImagePixels(), and SyncImagePixels() can only deal with one pixel cache area at a time. Suppose you want to access the first and last scanline from the same image at the same time? The solution is to use a cache view. A cache view permits you to access as many areas simultaneously in the pixel cache as you require. The cache view methods behave like the previous methods except you must first open a view and close it when you are finished with it. Here is a snippet of MagickCore that gives us two areas of an image:

  ViewInfo
    *view_1,
    *view_2;

  view_1=OpenCacheView(source);
  view_2=OpenCacheView(source);
  for (y=0; y < (long) source->rows; y++)
  {
    u=AcquireCacheView(source,0,y,source->columns,1);
    v=AcquireCacheView(source,0,source->rows-y,source->columns,1);
    if ((u == (const PixelPacket *) NULL) || (v == (const PixelPacket *) NULL))
      break;
    for (x=0; x < (long) source->columns; x++)
    {
      /* do something with u & v here */
    }
  }
  view_1=CloseCacheView(view_1);
  view_2=CloseCacheView(view_2);
  if (y < (long) source->rows)
    { /* an exception was thrown */ }
Magick Persistent Cache Format

Recall that each image format is decoded by ImageMagick and the pixels are placed in the pixel cache. If you write an image, the pixels are read from the pixel cache and encoded as required by the format you are writing (e.g. GIF, PNG, etc.). The Magick Persistent Cache (MPC) format is designed to eliminate the overhead of decoding and encoding pixels to and from an image format. MPC writes two files. One, with the extension .mpc, retains all the attributes associated with the image or image sequence (e.g. width, height, colorspace, etc.) and the second, with the extension .cache, is the pixel cache in the native format. When reading an MPC image file, ImageMagick reads the image attributes and memory maps the pixel cache on disk eliminating the need for decoding the image pixels. The tradeoff is in disk space. MPC is generally larger in file size than most other image formats.

Best Practices

Although you can request any pixel from the pixel cache, any block of pixels, any scanline, multiple scanlines, any row, or multiple rows with the AcquireImagePixels(), GetImagePixels(), SetImagePixels, AcquireCacheView(), GetCacheView(), and SetCacheView() methods, ImageMagick is optimized to return a few pixels or a few pixels rows at at time. There is additional optimizations if you request a single scanline or a few scanlines at a time. These methods also permit random access to the pixel cache, however, ImageMagick is optimized for sequential access.

If you are updating pixels obtained with GetImagePixels() or GetCacheView() don't forget to call SyncImageCache() or SyncCacheView() after you update the pixels to ensure any updates get pushed to the pixel cache.

Use SetImagePixels() or SetCacheView() if you setting an initial pixel value. The GetImagePixels() or GetCacheView() method reads pixels from the cache and if you are setting an initial pixel value this read is unnecessary. Don't forget to call SyncImagePixels() to push your updates to the pixel cache.

You can request pixels outside the bounds of the image with AcquireImagePixels() or AcquireCacheView(), however, it is more efficient to request pixels inside the image region.

Although you can force the pixel cache to disk using appropriate resource limits, disk access can be upwards of 1000 times slower than memory access. For fast, efficient, access to the pixel cache, try to keep the pixel cache in anonymous memory mapped or heap memory.

The ImageMagick Q16 version of ImageMagick permits you to read and write 16 bit images without scaling but the pixel cache consumes twice as much memory as the Q8 version. If your system has constrained memory resources, you might want to use the Q8 version of ImageMagick

If you are dealing with large images, make sure the pixel cache is written to a disk area with plenty of free space. Under Unix, this is typically /tmp and for Windows, c:/temp. You can tell ImageMagick to write the pixel cache to an alternate location with the MAGICK_TMPDIR environment variable. For example,

  export MAGICK_TMPDIR=/data/magick

If you plan on processing the same image many times, consider the MPC format. Reading a MPC image has near-zero overhead because its in the native pixel cache format eliminating the need for decoding the image pixels. Here is an example:

  convert bigassimage.tif bigassimage.mpc
  convert bigassimage.mpc -crop 100x100+0+0 1.png
  convert bigassimage.mpc -crop 100x100+100+0 1.png
  convert bigassimage.mpc -crop 100x100+200+0 1.png
  ...

MPC is ideal for web sites. It reduces the overhead of reading and writing an image. We use it exclusively at our online image studio.

Image Attributes and Profiles

Images have metadata associated with them in the form of attributes (e.g. width, height, description, etc.) and profiles (e.g. EXIF, IPTC, color management). ImageMagick provides convenient methods to get, set, or update image attributes and get, set, update, or apply profiles. Some of the more popular image attributes are associated with the Image structure in the MagickCore API. For example:

  (void) printf("image width: %lu, height: %lu\n",image->columns,image->rows);

For a great majority of image attributes, such as an image comment or description, we use the GetImageAttribute() and SetImageAttribute() methods. Here we set an attribute and fetch it right back:

  const ImageAttribute
    *attribute;

  (void) SetImageAttribute(image,"comment","This space for rent");
  attribute=GetImageAttribute(image,"comment");
  if ((attribute == (const ImageAttribute *) NULL) &&
      (attribute->value != (const char *) NULL))
    (void) printf("Image comment: %s\n",attribute->value);

Image profiles are handled with GetImageProfile(), SetImageProfile(), and ProfileImage() methods. Here we set a profile and fetch it right back:

  StringInfo
    *profile;

  profile=AcquireStringInfo(length);
  SetStringInfoDatum(profile,my_exif_profile);
  (void) SetImageProfile(image,"EXIF,profile);
  DestroyStringInfo(profile);
  profile=GetImageProfile(image,"EXIF");
  if (profile != (StringInfo *) NULL)
    (void) PrintStringInfo(stdout,"EXIF",profile);

Image Coders

An image coder is responsible for registering, optionally classifying, optionally reading, optionally writing, and unregistering one image format (e.g. PNG, GIF, JPEG, etc.). Registering alerts ImageMagick a particular format is available to read or write. While unregistering tells ImageMagick the format is no longer available. The classifying method looks at the first few bytes of an image and decides if the image is in the expected format. The reader sets the image size, colorspace, and other attributes and loads the pixel cache with the pixels. The reader returns a null image and an exception if an error occurs or returns a single image or an image sequence if the format supports multiple images per file. The writer does the reverse. It takes the image attributes and unloads the pixel cache and writes them as required by the image format.

The modular design of the image coder makes it relatively easy to support new formats. Here is a complete image coder with the exception of the classification method:

  /*
    Include declarations.
  */
  #include "magick/studio.h"
  #include "magick/blob.h"
  #include "magick/blob-private.h"
  #include "magick/colorspace.h"
  #include "magick/exception.h"
  #include "magick/exception-private.h"
  #include "magick/image.h"
  #include "magick/image-private.h"
  #include "magick/list.h"
  #include "magick/magick.h"
  #include "magick/memory_.h"
  #include "magick/monitor.h"
  #include "magick/quantum.h"
  #include "magick/static.h"
  #include "magick/string_.h"
  
  /*
    Forward declarations.
  */
  static MagickBooleanType
    WriteAVSImage(const ImageInfo *,Image *);

  /*
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %                                                                             %
  %                                                                             %
  %                                                                             %
  %   R e a d A V S I m a g e                                                   %
  %                                                                             %
  %                                                                             %
  %                                                                             %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %
  %  ReadAVSImage() reads an AVS X image file and returns it.  It
  %  allocates the memory necessary for the new Image structure and returns a
  %  pointer to the new image.
  %
  %  The format of the ReadAVSImage method is:
  %
  %      Image *ReadAVSImage(const ImageInfo *image_info,ExceptionInfo *exception)
  %
  %  A description of each parameter follows:
  %
  %    o image_info: The image info.
  %
  %    o exception: return any errors or warnings in this structure.
  %
  */
  
  static inline size_t CheckOverflowException(const Image *image,
    const size_t extend)
  {
    if ((extend*image->columns) < (size_t) image->columns)
      ThrowMagickFatalException(ResourceLimitFatalError,"MemoryAllocationFailed",
        image->filename);
    return(extend*image->columns);
  }
  
  static Image *ReadAVSImage(const ImageInfo *image_info,ExceptionInfo *exception)
  {
    Image
      *image;
  
    long
      y;
  
    MagickBooleanType
      status;
  
    register long
      x;
  
    register PixelPacket
      *q;
  
    register unsigned char
      *p;
  
    ssize_t
      count;
  
    size_t
      length;
  
    unsigned char
      *pixels;
  
    unsigned long
      height,
      width;
  
    /*
      Open image file.
    */
    assert(image_info != (const ImageInfo *) NULL);
    assert(image_info->signature == MagickSignature);
    if (image_info->debug != MagickFalse)
      (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
        image_info->filename);
    assert(exception != (ExceptionInfo *) NULL);
    assert(exception->signature == MagickSignature);
    image=AllocateImage(image_info);
    status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
    if (status == MagickFalse)
      {
        image=DestroyImageList(image);
        return((Image *) NULL);
      }
    /*
      Read AVS X image.
    */
    width=ReadBlobMSBLong(image);
    height=ReadBlobMSBLong(image);
    if ((width == ~0UL) || (height == ~0UL))
      ThrowReaderException(CorruptImageError,"ImproperImageHeader");
    do
    {
      /*
        Convert AVS raster image to pixel packets.
      */
      image->columns=width;
      image->rows=height;
      image->depth=8;
      if ((image_info->ping != MagickFalse) && (image_info->number_scenes != 0))
        if (image->scene >= (image_info->scene+image_info->number_scenes-1))
          break;
      length=CheckOverflowException(image,4);
      pixels=(unsigned char *) AcquireMagickMemory(length);
      if (pixels == (unsigned char *) NULL) 
        ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
      for (y=0; y < (long) image->rows; y++)
      {
        count=ReadBlob(image,length,pixels);
        if ((size_t) count != length)
          ThrowReaderException(CorruptImageError,"UnableToReadImageData");
        p=pixels;
        q=SetImagePixels(image,0,y,image->columns,1);
        if (q == (PixelPacket *) NULL)
          break;
        for (x=0; x < (long) image->columns; x++)
        {
          q->opacity=(Quantum) (QuantumRange-ScaleCharToQuantum(*p++));
          q->red=ScaleCharToQuantum(*p++);
          q->green=ScaleCharToQuantum(*p++);
          q->blue=ScaleCharToQuantum(*p++);
          if (q->opacity != OpaqueOpacity)
            image->matte=MagickTrue;
          q++;
        }
        if (SyncImagePixels(image) == MagickFalse)
          break;
        if (image->previous == (Image *) NULL)
          if ((image->progress_monitor != (MagickProgressMonitor) NULL) &&
              (QuantumTick(y,image->rows) != MagickFalse))
            {
              status=image->progress_monitor(LoadImageTag,y,image->rows,
                image->client_data);
              if (status == MagickFalse)
                break;
            }
      }
      pixels=(unsigned char *) RelinquishMagickMemory(pixels);
      if (EOFBlob(image) != MagickFalse)
        {
          ThrowFileException(exception,CorruptImageError,"UnexpectedEndOfFile",
            image->filename);
          break;
        }
      /*
        Proceed to next image.
      */
      if (image_info->number_scenes != 0)
        if (image->scene >= (image_info->scene+image_info->number_scenes-1))
          break;
      width=ReadBlobMSBLong(image);
      height=ReadBlobMSBLong(image);
      if ((width != ~0UL) && (height != ~0UL))
        {
          /*
            Allocate next image structure.
          */
          AllocateNextImage(image_info,image);
          if (GetNextImageInList(image) == (Image *) NULL)
            {
              image=DestroyImageList(image);
              return((Image *) NULL);
            }
          image=SyncNextImageInList(image);
          if (image->progress_monitor != (MagickProgressMonitor) NULL)
            {
              status=image->progress_monitor(LoadImagesTag,TellBlob(image),
                GetBlobSize(image),image->client_data);
              if (status == MagickFalse)
                break;
            }
        }
    } while ((width != ~0UL) && (height != ~0UL));
    CloseBlob(image);
    return(GetFirstImageInList(image));
  }

  /*
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %                                                                             %
  %                                                                             %
  %                                                                             %
  %   R e g i s t e r A V S I m a g e                                           %
  %                                                                             %
  %                                                                             %
  %                                                                             %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %
  %  RegisterAVSImage() adds attributes for the AVS X image format to the list
  %  of supported formats.  The attributes include the image format tag, a
  %  method to read and/or write the format, whether the format supports the
  %  saving of more than one frame to the same file or blob, whether the format
  %  supports native in-memory I/O, and a brief description of the format.
  %
  %  The format of the RegisterAVSImage method is:
  %
  %      RegisterAVSImage(void)
  %
  */
  ModuleExport void RegisterAVSImage(void)
  {
    MagickInfo
      *entry;
  
    entry=SetMagickInfo("AVS");
    entry->decoder=(DecoderHandler *) ReadAVSImage;
    entry->encoder=(EncoderHandler *) WriteAVSImage;
    entry->description=ConstantString("AVS X image");
    entry->module=ConstantString("AVS");
    (void) RegisterMagickInfo(entry);
  }

  /*
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %                                                                             %
  %                                                                             %
  %                                                                             %
  %   U n r e g i s t e r A V S I m a g e                                       %
  %                                                                             %
  %                                                                             %
  %                                                                             %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %
  %  UnregisterAVSImage() removes format registrations made by the
  %  AVS module from the list of supported formats.
  %
  %  The format of the UnregisterAVSImage method is:
  %
  %      UnregisterAVSImage(void)
  %
  */
  ModuleExport void UnregisterAVSImage(void)
  {
    (void) UnregisterMagickInfo("AVS");
  }

  /*
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %                                                                             %
  %                                                                             %
  %                                                                             %
  %   W r i t e A V S I m a g e                                                 %
  %                                                                             %
  %                                                                             %
  %                                                                             %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %
  %  WriteAVSImage() writes an image to a file in AVS X image format.
  %
  %  The format of the WriteAVSImage method is:
  %
  %      MagickBooleanType WriteAVSImage(const ImageInfo *image_info,Image *image)
  %
  %  A description of each parameter follows.
  %
  %    o image_info: The image info.
  %
  %    o image:  The image.
  % 
  */
  static MagickBooleanType WriteAVSImage(const ImageInfo *image_info,Image *image)
  
   MagickBooleanType
     status;
  
   MagickOffsetType
     scene;
  
   register const PixelPacket
     *p;
  
   register long
     x,
     y;
  
   register unsigned char
     *q;
  
   unsigned char
     *pixels;
  
   /*
     Open output image file.
   */
   assert(image_info != (const ImageInfo *) NULL);
   assert(image_info->signature == MagickSignature);
   assert(image != (Image *) NULL);
   assert(image->signature == MagickSignature);
   if (image->debug != MagickFalse)
     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
   status=OpenBlob(image_info,image,WriteBinaryBlobMode,&image->exception);
   if (status == MagickFalse)
     return(status);
   scene=0;
   do
   {
     /*
       Write AVS header.
     */
     if (image_info->colorspace == UndefinedColorspace)
       (void) SetImageColorspace(image,RGBColorspace);
     (void) WriteBlobMSBLong(image,image->columns);
     (void) WriteBlobMSBLong(image,image->rows);
     /*
       Allocate memory for pixels.
     */
     pixels=(unsigned char *)
       AcquireMagickMemory((size_t) image->columns*sizeof(PixelPacket));
     if (pixels == (unsigned char *) NULL)
       ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
     /*
       Convert MIFF to AVS raster pixels.
     */
     for (y=0; y < (long) image->rows; y++)
     {
       p=AcquireImagePixels(image,0,y,image->columns,1,&image->exception);
       if (p == (PixelPacket *) NULL)
         break;
       q=pixels;
       for (x=0; x < (long) image->columns; x++)
       {
         *q++=ScaleQuantumToChar(QuantumRange-(image->matte != MagickFalse ?
           p->opacity : OpaqueOpacity));
         *q++=ScaleQuantumToChar(p->red);
         *q++=ScaleQuantumToChar(p->green);
         *q++=ScaleQuantumToChar(p->blue);
         p++;
       }
       (void) WriteBlob(image,(size_t) (q-pixels),pixels);
       if (image->previous == (Image *) NULL)
         if ((image->progress_monitor != (MagickProgressMonitor) NULL) &&
             (QuantumTick(y,image->rows) != MagickFalse))
           {
             status=image->progress_monitor(SaveImageTag,y,image->rows,
               image->client_data);
             if (status == MagickFalse)
               break;
           }
     }
     pixels=(unsigned char *) RelinquishMagickMemory(pixels);
     if (GetNextImageInList(image) == (Image *) NULL)
       break;
     image=SyncNextImageInList(image);
     if (image->progress_monitor != (MagickProgressMonitor) NULL)
       {
         status=image->progress_monitor(SaveImagesTag,scene,
           GetImageListLength(image),image->client_data);
         if (status == MagickFalse)
           break;
       }
     scene++;
   } while (image_info->adjoin != MagickFalse);
   CloseBlob(image);
   return(MagickTrue);

Custom Image Filters

ImageMagick provides a convenient mechanism for adding your own custom image processing algorithms. They are invoked from the command line with the –process option or from the MagickCore API method ExecuteModuleProcess().

ImageMagick includes a sample custom filter listed here. It computes a few statistics such as the brightness and saturation mean and standard-deviation.

  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <time.h>
  #include <assert.h>
  #include <math.h>
  #include "magick/MagickCore.h"
  
  /*
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %                                                                             %
  %                                                                             %
  %                                                                             %
  %   A n a l y z e I m a g e                                                   %
  %                                                                             %
  %                                                                             %
  %                                                                             %
  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  %
  %  AnalyzeImage() computes the brightness and saturation mean and standard
  %  deviation and stores these values as attributes of the image.
  %
  %  The format of the AnalyzeImage method is:
  %
  %      MagickBooleanType AnalyzeImage(Image *image)
  %
  %  A description of each parameter follows:
  %
  %    o image: The address of a structure of type Image.
  %
  */
  ModuleExport MagickBooleanType AnalyzeImage(Image **image,const int argc,
    char **argv)
  {
  #define PRECISION "%.0f"
  
    char
      text[MaxTextExtent];
  
    double
      bsumX = 0.0,
      bsumX2 = 0.0,
      brightness_mean = 0.0,
      brightness_stdev = 0.0,
      ssumX = 0.0,
      ssumX2 = 0.0,
      saturation_mean = 0.0,
      saturation_stdev = 0.0,
      total_pixels = 0.0;
  
    double
      brightness,
      hue,
      saturation;
  
    long
      y;
  
    register const PixelPacket
      *p;
  
    register long
      x;
  
    assert(image != (Image **) NULL);
    assert(*image != (Image *) NULL);
    for (y=0; y < (int) (*image)->rows; y++)
    {
      p=AcquireImagePixels((*image),0,y,(*image)->columns,1,&(*image)->exception);
      if (p == (const PixelPacket *) NULL)
        break;
      for (x=0; x < (long) (*image)->columns; x++)
      {
        TransformHSB(p->red,p->green,p->blue,&hue,&saturation,&brightness);
        brightness*=QuantumRange;
        bsumX+=brightness;
        bsumX2+=brightness*brightness;
        saturation*=QuantumRange;
        ssumX+=saturation;
        ssumX2+=saturation*saturation;
        total_pixels++;
        p++;
      }
    }
    if (total_pixels <= 0.0)
      return(MagickFalse);
    brightness_mean=bsumX/total_pixels;
    (void) FormatMagickString(text,MaxTextExtent,PRECISION,brightness_mean);
    (void) SetImageAttribute((*image),"BrightnessMean",text);
    brightness_stdev=sqrt(bsumX2/total_pixels-(bsumX/total_pixels*bsumX/
      total_pixels));
    (void) FormatMagickString(text,MaxTextExtent,PRECISION,brightness_stdev);
    (void) SetImageAttribute((*image),"BrightnessStddev",text);
    saturation_mean=ssumX/total_pixels;
    (void) FormatMagickString(text,MaxTextExtent,PRECISION,saturation_mean);
    (void) SetImageAttribute((*image),"SaturationMean",text);
    saturation_stdev=sqrt(ssumX2/total_pixels-(ssumX/total_pixels*ssumX/
      total_pixels));
    (void) FormatMagickString(text,MaxTextExtent,PRECISION,saturation_stdev);
    (void) SetImageAttribute((*image),"SaturationStddev",text);
    return(MagickTrue);
  }

To invoke the custom filter from the command line, use this command:

  -> convert logo: -process Analyze -verbose info:
  Image: logo:
    Format: LOGO (ImageMagick Logo)
    Class: PseudoClass
    Geometry: 640x480
    ...
    BrightnessMean: 59192
    BrightnessStddev: 15209
    SaturationMean: 6156
    SaturationStddev: 16952

Threads of Execution

ImageMagick is thread safe with the exception of the MagickCore AcquireImagePixels(), GetImagePixels(), SetImagePixels(), and SyncImagePixels() pixel cache methods. If a thread wants to access the pixel cache, simply use a cache view. We do this for the CompositeImage() method, for example. Suppose we want to composite a single image over a different image in each thread of execution. If we use AcquireImagePixels(), the results could be corrupt since multiple threads might be asking for different areas of the pixel cache simultaneously. Instead we use AcquireCacheView() which creates a unique view for each thread of execution ensuring our program behaves properly regardless of how many threads are invoked. The other program interfaces, such as the MagickWand API, are thread safe so there are no special precautions for threads of execution.

 
© 1999-2006 ImageMagick Studio LLC