How to convert a 32 bit per pixel (bpp) image to an 8 bpp image

How to convert a 32 bit per pixel (bpp) image to an 8 bpp image

Post by Nicholas Paldino [.NET MVP » Sat, 23 Mar 2002 22:37:27


    I have been using the .NET framework to get icons for files/directories
and am trying to save the icons as transparent gifs.  Now I know that most
of the graphics routines in .NET are built on top of GDI/GDI+, so that is
why I am asking the question in these forums.

    I can get a handle to an icon just fine.  I can even convert it to a
transparent bitmap just fine.  However, this bitmap is 32 bits per pixel.
When I want to save this as a transparent gif, I cannot, because the
specification calls for a max of 8bpp.

    So, given that, how can I convert or create a new bitmap that I can save
as a transparent gif, but reducing the resolution to 8bpp?

    Thanks in advance for any help, it is greatly appreciated.

--
               - Nicholas Paldino [.NET MVP]


 
 
 

How to convert a 32 bit per pixel (bpp) image to an 8 bpp image

Post by James DeBroeck [M » Sun, 24 Mar 2002 09:24:59


Hi Nicholas,

This article has not yet been published. Soon I hope. But here is a
pre-published version. It may help you quite a bit. Please monitor the
Knowledge Base for the Q# below and update your copy when it is published.

Sincerely,
James
Microsoft Windows Developer Support

The fine but small print:

This posting is provided "AS IS" with no warranties, and confers no rights.

Q315780 - HOWTO: Save a GIF with a New Color Table Using GdiPlus

----------------------------------------------------------------------------
----
The information in this article applies to:

Microsoft GDI+ 1.0

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

SUMMARY
The CompuServe Graphics Interchange Format (GIF) has design maximum of 256
colors arranged in a color table. Common modifications of a GIF image file
require the manipulation of a custom color table. However, when GdiPlus
edits an Image object and is then asked to save the image with the GIF
encoder, the resulting GIF file contains a halftone color table. To save an
Image with a custom color table using the GIF encoder, one must work with a
256 color copy of the Image that is unmodified by GdiPlus.

MORE INFORMATION
The GIF image file can express a maximum of only 256 colors. Since color is
a scarce resource in the GIF, optimizing those colors is a commonly
requested task. To effect an optimized color table, one needs the ability
to set any arbitrary custom color table in a GIF file.

When GdiPlus has modified an Image and then writes an image to a file using
the GIF encoder, it writes the file using a halftone palette to which the
Image object's bits have been color reduced. GdiPlus does a color
conversion from 32 bits-per-pixel (32 bpp) when it writes the image to the
file because all modifications to the image are done with GdiPlus' 32 bpp
graphics engine.

Although GdiPlus supports the creation of Images and Bitmaps of various
pixel formats and can therefore load a GIF image, the use of the 32 bpp
graphics engine necessitates the conversion to 32 bpp when they are
modified by GdiPlus. However, an Image or Bitmap that is not modified by
GdiPlus retains its original pixel format and can be written to a file
using the Save() method with the appropriate encoder. This property forms
the basis for a technique that can save an Image to a GIF file with a
custom color table.

Since an unmodified Bitmap can be written with the GIF encoder such that
its color table is kept intact, the solution is to copy the image data from
an original Image object to a temporary Bitmap object. This temporary
Bitmap is created as an 8 bpp indexed Bitmap since that is the pixel format
that is used to save a GIF file. The Bitmap's color table is set using the
SetPalette() method and then the image definition is copied to the
temporary Bitmap. Once the temporary Bitmap has been created with a
duplicate definition, it can be saved with the GIF encoder using the Save()
method which will preserve the 8 bpp color table.

To write a GIF image to a file with a custom color table follow these steps
:

Create a duplicate Bitmap object of the same size as the source Image.

Set the custom color table of the Bitmap object to the desired color table.

Use the LockBits() method to gain write access to the image bits of the
copy.

Create an image definition in the copy by writing color indices to the
memory obtained from the LockBits() method that duplicate the pixels in the
original Image.

Release the image bits via the UnLockBits() method.

Use the Bitmap copy with the custom color table to save the Image to a file
using the Save() method and the GIF encoder.

Release the Bitmap copy of the Image.

The following C++ sample code demonstrates this solution:

Status SaveGIFWithNewColorTable(
  Image *pImage,
  const WCHAR* filename,
  const CLSID* clsidEncoder,
  DWORD nColors,
  BOOL fTransparent
)
{
    Status stat = GenericError;

    // GIF codec supports 256 colors maximum
    if (nColors > 256)
        nColors = 256;
    if (nColors < 2)
        nColors = 2;

    // make a new 8 bpp pixel indexed bitmap the size of the source image
    DWORD   dwWidth = pImage->GetWidth();
    DWORD   dwHeight = pImage->GetHeight();

    // Always use PixelFormat8bppIndexed because that is the color table
    // based interface to the GIF codec.
    Bitmap  bitmap(dwWidth, dwHeight, PixelFormat8bppIndexed);

    stat = bitmap.GetLastStatus();

    if (stat != Ok)
        return stat;        // no point in continueing

    // allocate the new color table
    DWORD   dwSizeColorPalette;
    dwSizeColorPalette = sizeof(ColorPalette);      // size of core struct
    dwSizeColorPalette += sizeof(ARGB)*(nColors-1);   // the other entries

    // Allocate some raw space to back our ColorPalette structure pointer
    ColorPalette *ppal = (ColorPalette *)new BYTE[dwSizeColorPalette];
    if (ppal == NULL) return OutOfMemory;

    ZeroMemory(ppal, dwSizeColorPalette);

    // initialize a new color table with entries that are determined by
    // some optimal palette finding algorithm, for demonstration
    // purposes just do a grayscale
    if (fTransparent)
        ppal->Flags = PaletteFlagsHasAlpha;
    else
        ppal->Flags = 0;
    ppal->Flags |= PaletteFlagsGrayScale;
    ppal->Count = nColors;
    for (UINT i = 0; i < nColors; i++)
    {
        int Alpha = 0xFF;       // Colors are opaque by default
        int Intensity = i*0xFF/(nColors-1); // even distribution

        // The GIF encoder will make the first entry in the palette with a
        // zero alpha the "transparent" color in the GIF.
        // We pick the first one arbitrarily for demonstration purposes.

        if ( i == 0 && fTransparent) // Make this color index...
            Alpha = 0;          // Transparent

        // Create a gray scale for demonstration purposes
        // Otherwise, one's favorite color reduction algorithm should be
used
        // and an optimum palette for that algorithm generated here
        // For example, a color histogram, or a median cut palette
        ppal->Entries[i] = Color::MakeARGB( Alpha,
                                            Intensity,
                                            Intensity,
                                            Intensity );
    }

    // set the palette into the new Bitmap object
    bitmap.SetPalette(ppal);

    // We want to use GetPixel below to pull out the color data of pImage
    // since GetPixel isn't defined on an Image, make a copy in a Bitmap
    // instead. So now make a new Bitmap the size of the image that
    // you want to export. Otherwise, one could try to interpret the native
    // pixel format of the image via a LockBits call.
    // Use PixelFormat32bppARGB so we can wrap a Graphics around it.
    Bitmap BmpCopy(dwWidth, dwHeight, PixelFormat32bppARGB);
    stat = BmpCopy.GetLastStatus();
    if (stat == Ok)
    {
        Graphics g(&BmpCopy);

        // Transfer the Image to the Bitmap
        stat = g.DrawImage(pImage, 0, 0, dwWidth, dwHeight);

        // g goes out of scope here and cleans up
    }

    if (stat != Ok)
    {
        delete [] (LPBYTE) ppal;
        ppal = NULL;
        return stat;
    }

    // Lock the whole of the bitmap for writting.
    BitmapData  bitmapData;
    Rect        rect(0, 0, dwWidth, dwHeight);

    stat = bitmap.LockBits(
      &rect,
      ImageLockModeWrite,
      PixelFormat8bppIndexed,
      &bitmapData);

    if (stat == Ok)
    {
        // Write to the temporary buffer provided by LockBits.
        // One copies the pixels from the source image in this loop.
        // Since we want an index, we convert RGB to the appropriate
        // palette index here.
        BYTE *pixels = (BYTE*)bitmapData.Scan0;
        // top-down / bottom-up not relevant to this algorithm
        UINT stride = abs(bitmapData.Stride);

        for(UINT row = 0; row < dwHeight; ++row)
        {
          for(UINT col = 0; col < dwWidth; ++col)
          {
              // map palette indices for a gray scale
              // one's favorite color reduction algorithm goes here
              // if color converting using some other technique
              Color     pixel;
              UINT      i8bppPixel = row * stride + col;

              BmpCopy.GetPixel(col, row, &pixel);

              // Use luminance/chrominance conversion to get grayscale.
              // Basically - turn the image into black and white TV - YCrCb.
              // We do not even bother to calculate Cr or Cb since we are
              // throwing away the color anyway.
              // Y = Red * 0.299 + Green * 0.587 + Blue * 0.114

              // This expression should be integer math for performance.
              // But, since GetPixel above is by far the slowest part of
              // this loop, the expression is left as floating point
              // for clarity.
              double luminance = (pixel.GetRed() * 0.299) +
                                (pixel.GetGreen() * 0.587) +
                                (pixel.GetBlue() * 0.114);

              // Gray scale is an intensity map from black to white
              // Compute the index to the gray scale entry that
approximates the
              // luminance and round the index.        
              // Also, constrain the index choices by the number of colors
to do
              pixels[i8bppPixel] = (BYTE)(luminance * (nColors-1)/255 +0.5);
          }
        }
        // Commit the changes by unlocking the portion of the bitmap.  
        stat = bitmap.UnlockBits(&bitmapData);
    }

    // if destination work was successful, see if the source was
    if (stat == Ok) stat = BmpCopy.GetLastStatus();

    // See if we were successful so far
    if (stat == Ok)
    {
        // Write it out to the disk
        stat =  bitmap.Save(filename, clsidEncoder, NULL);
    }

    // Cleanup after
...

read more »