Re: Grayscale Image Objects

Bob Friesenhahn <[email protected]> Sun, 4 Jan 2009 17:35:11 -0600 (CST)
Newsgroups gmane.comp.video.graphicsmagick.apis
Message-ID <[email protected]>
On Wed, 31 Dec 2008, [email protected] wrote:

> I am using GM 1.2.6 on Windows through the Magic++ API.  In several
> instances I have to construct an Image object from my own internal
> buffers.  I setup the type, colorspace, etc. without issue and populate
> the pixel (and possibly colormap) data.  The problem comes down to the
> internal flags for the image type.  Specifically when I create a grayscale
> image the "is_grayscale" member never gets set to True which causes
> problems for some of the imaging functions and encoders.  Since
> is_grayscale is not true for instance the JPEG encoder writes out a three
> channel JPEG file instead of a single channel file.  This is much more
> computationally and file size expensive than it should be.

Not good.

The is_grayscale and is_monochrome private members provide hints to 
help the software go faster.  Internally, the image is always stored 
as RGBA.  The problem is that if gray pixels are stored as RGBA, then 
all of the pixels need to be inspected to see if they are gray when 
the file is about to be written.  The optimization works by 
automatically clearing these flags whenever a pixel is updated.  The 
algorithm doing the updates then decides if the pixels should still be 
gray (or are gray) and then resets the flags appropriately.  If the 
algorithm does not maintain this information, then the image is 
treated as RGB until proven otherwise.

A workaround to automatically produce a more compact image is to use 
the image type method to set the image type to OptimizeType:

   image.type(OptimizeType);

and this forces the pixels to be inspected (as required) when the 
image is saved.  The annoyance is that if the image is gray, then all 
of the pixels need to be checked in order to prove it.

> There appears to be a method internally (IsGrayImage) that is called some
> a few functions that sets the is_grayscale member properly however I have
> not found any way to have this function execute via the Magic++ interface.
> I believe that the is_monochrome member suffers from the same problem.

All of the C functions are in the MagickLib namespace.  This means 
that you can execute IsGrayImage() using an incantation like this:

   image.modifyImage();
   MagickLib::IsGrayImage(image.image());

You are also able to access raw structure members using this image 
accessor to the underlying Image structure. Of course bypassing 
Magick++ could cause your code to not work with a newer version in the 
future.

> While I am here I might also ask if there is a recommended combination
> (most efficient) form of grayscale storage for GM?  I ask since they can
> be DirectColor or PseudoColor, and even GrayscaleType and RGBColorspace.

There are two storage models, and they can exist simultaneously.  One 
is the DirectClass model where the pixels are like an array of RGBA 
pixel structures (of type PixelPacket).  The other is a PsuedoClass 
model, where the pixels are like an array of colormap indexes (of type 
IndexPacket).  If the image storage class is set to PseudoClass, then 
indexes exist, and they refer to a colormap.  If the image storage 
class is set to DirectClass, then only the RGBA pixels are valid. 
Even if the image is set to PseudoClass, each algorithm assures that 
the DirectClass representation is also up to date before it returns. 
It is ok for the representations to diverge for a while but they 
should be brought up to date before being passed to the next 
algorithm, or saved.

It often helps to load grayscale images as PsuedoClass using a linear 
ramped grayscale colormap (which AllocateImageColormap() produces by 
default).  In fact (with only a few exceptions) this is how grayscale 
images are read into GraphicsMagick.  It can be very time consumptive 
to produce a new colormap given direct pixels so it helps a lot to 
start with a colormap.  The only exception that I am aware of at the 
moment is for the TIFF format which supports both native 
bilevel/grayscale and colormapped in the format, and so it is useful 
to remember the original representation.  Most TIFF users are not 
happy if they started with a true gray representation and they ended 
up with a colormapped representation.

The PsuedoClass representation is actually even less memory efficient 
than the DirectClass representation since the DirectClass 
representation always exists. From a performance standpoint, 
DirectClass will be faster unless a colormap is needed, and even then 
there is a fast transformation of a gray image to colormapped.

Bob
======================================
Bob Friesenhahn
[email protected], http://www.simplesystems.org/users/bfriesen/
GraphicsMagick Maintainer,    http://www.GraphicsMagick.org/


------------------------------------------------------------------------------