Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Database

JPEG-Like Image Compression, Part 2


AUG95: JPEG-Like Image Compression, Part 2

CAL is a C++ class library that provides fast, efficient compression

Craig is a founder of Enhanced Data Technology and author of Practical Image Processing in C, Practical Ray Tracing in C, and Photographic Imaging Techniques for Windows (all published by John Wiley & Sons). Craig can be contacted at [email protected]. EDT also maintains a home page on the WWW at www.mirical.com.


In last month's installment of this two-part article, I described the basic techniques, algorithms, and vocabulary of JPEG image compression. In particular, I examined the concepts of discrete-cosine transforms, frequency-coefficient quantization, Huffman encoding, color-space conversion, and image subsampling. I also presented the various considerations which influenced the implementation of an image-compression technique called "CAL" (my initials), which implements the same algorithms as JPEG, but encapsulates the images in a simple, proprietary file format. This month, I'll focus on CAL and how it differs from JPEG, then present the C++ classes on which CAL is built.

JPEG versus CAL

CAL differs from JPEG in that it provides only a partial subset of the overall functionality described in the JPEG specification. In particular, CAL:

  • Supports only the baseline-sequential mode of operation.
  • Supports only Huffman encoding for entropy coding.
  • Uses the default, static Huffman tables from the JPEG specification for encoding/decoding of images.
  • Does not carry any Huffman code tables or symbol tables within the encoded CAL image when it is stored in a file.
  • Uses a simple file format.
  • Supports only 8-bit gray-scale and 24-bit RGB true-color images.
CAL's image compression is greater than that of lossless techniques (100:1, in some cases). CAL is also fast, small, easy to use, and royalty free. Unlike standard JPEG images, however, CAL images can't be shared or exchanged with non-CAL-aware applications. Still, you could use CAL image compression as an internal format for an application program that requires JPEG-like image-compression levels, and you could exchange images between two CAL-aware applications. CAL could be especially useful for transmission of images via modem. Because of CAL's small size, the receiving program that incorporates CAL might even be sent over the modem.

The CAL Image File Format

JPEG and CAL differ greatly in the file format in which they store compressed images. JPEG images are stored in JFIF or TIFF formats, whereas CAL images are stored in a simple file format.

The CAL file format is simple because the format of compressed images is fixed. In contrast, the JPEG specification allows for many different image formats. The specification supports a varying number of image components, a variety of color spaces, and various subsampling factors, in addition to custom Huffman tables. To enable this kind of flexibility, the JPEG file formats, by necessity, must be more complex.

A CAL image file is made up of a file header, followed by the compressed image data. Example 1 defines a CAL file header (see also Listing One). The file header for a CAL file is built by the CAL compression software. Part of the information destined for the file header is passed into the CompressCALImage class constructor as parameters, while other portions are calculated within the constructor before being stored into the header. In either case, the compression software fills in each of the header entries as the image is being compressed. CAL expansion software in the ExpandCALImage class is driven completely from the information contained in the CAL file header. This allows the expansion software to be simple.

The image data following the header is stored in sequential, Huffman-encoded, minimum coded unit (MCU) blocks. Each MCU of the image is compressed separately and padded to a byte boundary with 1 bits. Four blocks of image data make up an MCU for a gray-scale image, whereas six blocks are used for true-color images (four Y, one Cb, and one Cr). The number of blocks per MCU is contained in the file header.

The CAL Code

Table 1 lists the files which comprise the CAL code. All of the CAL files listed (along with sample images and programs) are available electronically; see "Availability," page 3.

Although the CAL code was developed using Borland C++ 3.1 for 16-bit Windows, it should easily port to other environments. Most of the code is standard C++ with a few Windows-specific exceptions. Areas that need attention during porting are the file I/O code in fileio.cpp, which uses the Windows calls for I/O, and the functions MyAlloc and MyFree (see Listing Two), which use the Windows functions GlobalAlloc, GlobalLock, and GlobalFree. In addition, all references to huge pointers will need attention in non-MS-DOS environments. Finally, since true-color RGB pixel data is stored as B, G, and R in Windows' DIB format, care must be taken in environments that store RGB data rationally. Porting the code to the Win32 environment will probably result in a significant speed increase.

As presented, the example Windows application (used only to exercise the CAL code) in the file caltst.cpp (available electronically) is nonoperational; it depends on a graphics file library that can't be distributed with the CAL code in this article. (See my book Photographic Imaging Techniques for Windows for information on acquiring this graphics library.) All of the CAL code itself, however, is fully operational as presented.

Image-Expansion Code

An object of the ExpandCALImage class is used to expand a previously compressed CAL image file. The single constructor for ExpandCALImage requires a filename of a CAL image as its parameter. One of the first operations performed within the constructor is the creation of a Huffman class object for file I/O and Huffman decoding. The new Huffman object first reads in the header of the specified image file. To verify that the specified file does indeed contain a CAL image, the CALFileTag entry in the file header is checked for the letters "CL." If the file contains a CAL image, a block of memory is allocated to store the image as it is expanded; the block size is specified in the file header. Next, an instance of the BufferManager class is created for managing the decoded image data. The BufferManager, in conjunction with the ColorConvert class it spawns, handles YCbCr-to-RGB color conversion on the expanded image data as the image is made available. Finally, the quantization tables are initialized to the quality factor specified in the image file header, and the variables used in the DC-coefficient difference calculations are initialized to 0. If all operations performed in the constructor are successful, the class variable ErrorCode is set to a no-error indication. Many member functions of this class check ErrorCode for the no-error indication before proceeding with execution.

The only purpose of the destructor for ExpandCALImage is to free the Huffman and BufferManager objects along with the memory allocated for the image by the constructor.

The ExpandImage member function expands images by organizing calls to functions in other classes that perform the expansion process. The expansion process is entirely driven by information contained in the CAL file header. Specifically, the header tells the code the number of MCUs in the image, and how many blocks make up each MCU. Since CAL uses a fixed subsampling mechanism, the content of a block in an MCU can be determined by the block number. This is important because Y blocks must be processed differently than either Cb or Cr blocks. For gray-scale images, there are four blocks in an MCU numbered 0..3 and they all contain Y information. For true-color image, there are six blocks in an MCU numbered 0..5. Blocks 0..3 contain Y information, block 4 contains Cb information, and block 5 contains Cr information. A switch statement determines how to process each block.

All Y blocks are processed in exactly the same fashion. First, the Huffman-encoded frequency coefficients must be decoded from the bit stream by a call to the DecodeBlock function. Notice that DecodeBlock is instructed to use the luminance tables for decoding Y data. With the block decoded, the actual value of the DC coefficient for the block can be calculated from the decoded value and the previous DC-coefficient value stored in the class object. The new value of the DC coefficient is stored for use by the next block of Y data. The next step is to subject the decoded frequency-coefficient data to dequantization with a call to QuantizeBlock. Here, too, the function is told to use the luminance dequantization table on the Y data block and to perform the DEQUANT operation.

Operations on the Cb and Cr chrominance blocks are very similar to those performed on the Y block with the exception that chrominance tables are used in place of the luminance table for decoding and dequantizing the data. Also, the DC-coefficient calculations are performed independently for the Cb and Cr blocks.

After either a luminance or chrominance block is decoded, it is subjected to an inverse zigzag reordering that places the ordered frequency-coefficient values back into pixel order. This done, the data is processed by an inverse cosine transform to retrieve the pixel data from the frequency data. The PutNextBlock function in the BufferManager class object is finally called to store the recovered pixel values back into the image buffer (after color conversion to RGB, of course).

The Huffman object's FlushInputStream member function is called between processing MCUs of image data. Remember, each MCU is separate and is padded to a byte boundary by the addition of 1 bits to the data stream. At the conclusion of MCU processing, the FlushInputStream function resets the Huffman-decoding software for processing the next MCU of data. This properly disposes of any padding bits added to the bit stream.

The processing of compressed image-data blocks continues until there are no more blocks to process. At that time ExpandImage returns a True, indicating success.

After the image-expansion process completes successfully, the ExpandCALImage object contains the image in memory (in DIB format). The application software can then poll the object for the image parameters and data.

Image-Compression Code

CAL image compression is handled by an object of the CompressCALImage class. The constructor for CompressCALImage is passed the following:

  • A filename to be given the compressed image.
  • An indication of whether the image is gray scale or true color.
  • A pointer to the image data in memory.
  • A pointer to the image palette in memory (if required).
  • The dimensions of the image in pixels.
  • The number of bits per pixel for the image.
  • The number of colors considered important for the image.
  • A quality-factor compression control.
As mentioned, some of these parameters are placed directly in the CAL file header, while others are used in calculations that eventually become entries in the header. For now, the header entries BitsPerPixel and NumberOfColors are not used by the CAL code but are included for future capability expansion, specifically to allow CAL to handle palettized color images.

The CompressCALImage constructor first creates a Huffman object for file I/O and a BufferManager class object for extraction of pixel data from the image being compressed. After these objects are instantiated, the header structure within the class is filled with appropriate image information. Next, the quantization tables are initialized by a call to SetQuality. The quantization tables are parameterized using the quality factor passed in as a parameter to the constructor. Finally, the previous DC-coefficient variables are set to 0 so that they can be used to encode DC-coefficient differences instead of absolute values. The destructor for the CompressCALImage is simple; it serves only to delete the Huffman and the BufferManager class objects, as they aren't needed by the time the destructor runs.

Image compression occurs when CompressImage is called. After checking if the initialization performed within the class constructor was successful, the header (filled in by the constructor) is written to the output file. Following this, two nested loops control the compression process. The outer loop is traversed for each MCU in the image, and the inner loop is traversed for each block of each MCU. Image compression proceeds in reverse order from image expansion. First, a call to GetNextBlock retrieves the next block of image data. The block number to be fetched is passed to this function as a parameter. A block number of 0..3 will fetch a Y block, a block number of 4 will fetch a Cb block, and a block number of 5 will fetch a Cr block. Because gray-scale images have only four blocks per MCU, blocks of nonexistent CB or Cr data will never be fetched. True-color images, however, utilize all block varieties.

After a block of image data is fetched, it undergoes discrete cosine transformation, followed by a forward zigzag. The frequency coefficients are then in the proper, increasing-frequency order for the quantization and encoding processes. The processing that follows depends upon the type of block, which is determined by the block number. Block numbers 0..3 of Y data are forward quantized with luminance-quantization tables. The difference in DC-coefficient value replaces the DC coefficient in the data block, whereupon EncodeBlock encodes the luminance data. Chrominance data is handled in a similar fashion, except chrominance tables are used for quantization and Huffman encoding. When a complete MCU of data has been processed, the Huffman data stream is flushed (padded with 1s) and processing begins on the next MCU. When there are no more MCUs to process, the CAL file is closed with a call to CloseFile. A Boolean True is returned if image compression was successful.

The BufferManager and ColorConvert Classes

During image compression, the buffer manager breaks up an image into blocks appropriate for compression. Conversely, during image expansion, the buffer manager reconstructs an image from blocks of data passed to it. The buffer manager manages four internal buffers (Y1, Y2, Cb, and Cr) when true-color images are processed and just two buffers (Y1 and Y2) for gray-scale images. These buffers can accommodate 16 rows of full-width image data, which are necessary to support the two-dimensional 4:2:2 subsampling performed on the image's chrominance components. Because of the subsampling, each 16x16 group of pixels in a true-color image results in four Y blocks and one block each of Cb and Cr. Each 16x16 group of pixels in a gray-scale image results in four Y blocks only.

During image compression, BufferManager reads 16 rows of image data at a time from the DIB image pointed to in memory, calls upon a ColorConvert class object to convert the RGB data to YCbCr, and then stores the converted data in its internal buffers. Every call to GetNextBlock retrieves a block of data from the buffer identified by the requested block number. Blocks 0 and 1 are fetched from Y1; blocks 2 and 3, from Y2; block 4, from Cb; and block 5, from the Cr buffer. When there is no more data in the buffers, the next 16 rows of image data are read in and the process repeats.

During image expansion, a call to PutNextBlock places a block of image data into the buffer identified by the specified block number. Block numbers 0 and 1 are stored in the Y1 buffer; block numbers 2 and 3, in the Y2 buffer; block number 4, in the Cb buffer; and block number 5, in the Cr buffer. When PutNextBlock is called and the internal buffers are full, 16 rows of data will be retrieved from the buffers, converted back to RGB format, and stored in the memory allocated for the image at the appropriate location (determined by the row number).

The ColorConvert class is also complex because it converts colors using scaled long integers instead of the more traditional floating-point numbers. This was done for performance. (Refer to last month's installment for the equations used for RGB-to-YCbCr color-space conversions.) Note that all of the possible terms in the color equations in the ColorConvert code are precomputed within the class constructor, so only array lookups and adds are necessary when the code needs to convert colors. This was also done to enhance performance. It's also important to offset the Cb and Cr values that occupy the range -0.5 to +0.5 when the scale is from 0.0 to 1.0 so that they reside in the range of byte values from 0 to 255. This is done by setting the value of 0 at code 127. Chrominance values above 127 are then considered positive, and values below 127 are negative. The code for the ColorConvert class (see color.hpp and color.cpp) shows how this offset is managed, how the 2-D chrominance subsampling is performed, and how long integers and scaling are used in place of floating-point numbers during the color calculations.

Using CAL in an Application

Example 2 compresses a true-color, in-memory Windows DIB image and stores it in a file called "cal50.cal." Once the CAL image has been compressed, the code expands the CAL image back into memory for processing/display. This code illustrates both CAL file compression and expansion. For simplicity, error detection and reporting are missing from this example.

Conclusions

JPEG compression is no more than the logical application of various image-processing algorithms to image data. As CAL illustrates, when JPEG compression is broken down into its constituent algorithms, the pieces are easily understood.

Table 1: CAL file/class breakdown.

<B>FILENAME:</B>buffman.hpp
<B>CONTENTS:</B><I>BufferManager </I>class-interface definition.
<B>PURPOSE:</B>This and buffman.cpp break images into blocks for encoding and reconstruct images from blocks during decoding.

<B>FILENAME:</B>buffman.cpp
<B>CONTENTS:</B><I>BufferManager </I>class-member functions.

<B>FILENAME:</B>cal.hpp
<B>CONTENTS:</B><I>ExpandCALImage/CompressCALImage </I>class-interface definitions.
<B>PURPOSE:</B>This and cal.cpp form the high-level interface into the CAL code.

<B>FILENAME:</B>cal.cpp
<B>CONTENTS:</B><I>ExpandCALImage/CompressCALImage </I>class-member functions.

<B>FILENAME:</B>cal.prj
<B>CONTENTS:</B>Project file for Borland C++ Version 3.1.
<B>PURPOSE:</B>Used to build the CAL code in the IDE.

<B>FILENAME:</B>caltst.cpp
<B>CONTENTS:</B>Example of CAL usage--nonfunctional.
<B>PURPOSE:</B>CAL code example.

<B>FILENAME:</B>caltst.def
<B>CONTENTS:</B>Module-definition file for Windows.
<B>PURPOSE:</B>Required for example Windows application.

<B>FILENAME:</B>color.hpp
<B>CONTENTS:</B><I>ColorConvert </I>class-interface definition.
<B>PURPOSE:</B>Performs RGB-to-YCbCr and YCbCr-to-RGB color-space conversions.

<B>FILENAME:</B>color.cpp
<B>CONTENTS:</B><I>ColorConvert </I>class-member functions.
<B>PURPOSE:</B>Tightly coupled with <I>BufferManager </I>class code.

<B>FILENAME:</B>dct.hpp
<B>CONTENTS:</B><I>DCT </I>class-interface definition.
<B>PURPOSE:</B>Brute-force attempt at a DCT.

<B>FILENAME:</B>dct.cpp
<B>CONTENTS:</B><I>DCT </I>class-member functions.
<B>PURPOSE:</B>Brute-force attempt at a DCT.

<B>FILENAME:</B>dct1.hpp
<B>CONTENTS:</B>Improved <I>DCT </I>class-interface definition.
<B>PURPOSE:</B>Improved DCT from Independent JPEG group's JPEG software.

<B>FILENAME:</B>dct1.cpp
<B>CONTENTS:</B>Improved <I>DCT </I>class-member functions.
<B>PURPOSE:</B>Improved DCT from Independent JPEG group's JPEG software.

<B>FILENAME:</B>errors.h
<B>CONTENTS:</B>Miscellaneous error-code definitions.
<B>PURPOSE:</B>Defines errors that can occur during reading and writing of CAL files.

<B>FILENAME:</B>fileio.hpp
<B>CONTENTS:</B><I>AFile </I>class interface definition.
<B>PURPOSE:</B>C++ class for reading and writing files.

<B>FILENAME:</B>fileio.cpp
<B>CONTENTS:</B><I>AFile </I>class member functions.
<B>PURPOSE:</B>Code is Windows specific.

<B>FILENAME:</B>huffman.hpp
<B>CONTENTS:</B><I>Huffman </I>class-interface definition.
<B>PURPOSE:</B>Contains all the entropy-encoding/decoding code.

<B>FILENAME:</B>huffman.cpp
<B>CONTENTS:</B><I>Huffman </I>class-member functions.
<B>PURPOSE:</B>Contains all the entropy-encoding/decoding code.

<B>FILENAME:</B>misc.hpp
<B>CONTENTS:</B>Miscellaneous data-structure and type definitions.

<B>FILENAME:</B>quant.hpp
<B>CONTENTS:</B><I>Quantize </I>class-interface definition.
<B>PURPOSE:</B>Performs quantization and dequantization of image data.

<B>FILENAME:</B>quant.cpp
<B>CONTENTS:</B><I>Quantize </I>class-member functions.
<B>PURPOSE:</B>Performs quantization and dequantization of image data.

<B>FILENAME:</B>tables.hpp
<B>CONTENTS:</B>Miscellaneous table-prototype definitions.
<B>PURPOSE:</B>Tables used throughout the CAL code, including quantization, zigzag, and Huffman encoding and decoding tables.

<B>FILENAME:</B>tables.cpp
<B>CONTENTS:</B>The tables.
Example 1: CAL file-header definition.

typedef struct {
WORD   StructureSize;   // Size of structure for version control
WORD   CALFileTag;      // Tag should be "CL"
IMAGETYPE ImageType;    // Type of image - gray scale or true color
WORD   ImageWidth;      // Image width in pixels
WORD   ImageHeight;     // Image height in pixels
DWORD  RasterSize;      // DIB raster size including padding
WORD   BitsPerPixel;    // Number of bits per pixel for the image.
                       // 8 for gray scale and 24 for true color images
WORD   NumberOfColors;  // Number of important colors in image usually 256 for
                       // gray scale images and always 0 for true color images
WORD   QualityFactor;   // Quality factor image compressed with. Range 10..100
WORD   NumberOfMCUs;    // Total number of MCUs in image
WORD   BlocksPerMCU;    // Number of blocks in a single MCU.
                      // 4 for gray scale and 6 for true color images
RGBCOLOR Palette[256];  // Palettefor image display. Required only for gray scale images
DWORD  Unused1;         // Whatever you want you can put here
} CALFILEHEADER;
Example 2: Storing a true-color, in-memory Windows DIB image.

CompressCALImage *C;    // Create an instance of class for CAL compression
ExpandCALImage *E;      // Create an instance of class for CAL expansion
// Attempt to compress a CAL image from a true color DIB image in memory
// Parameter passed to instance of CompressCALImage object refer to image
// in memory. Quality factor of 50 used.
C = new CompressCALImage("cal50.cal", TRUECOLORTYPE,
            Ptr to DIB data, NULL,
            ImageWidth, ImageHeight,
            24, 0, 50);
// Now compress the image
C->CompressImage();
delete C;              // Compression object is no longer needed
// Now attempt to expand the previously compressed CAL image
E = new ExpandCALImage("cal50.cal");
// Now expand the image
E->ExpandImage();
// At the conclusion of image expansion, the E object contains the DIB image and its
// associated specifications.Make sure to copy the image data out of the E object before
// the object is deleted otherwise the image will be destroyed as well as the object.
BYTE huge *lpImage = E->GetDataPtr(); // Get ptr to DIB image data
WORD ImageWidth  = E->GetWidth();
WORD ImageHeight = E->GetHeight();
// Process and/or display the image here
delete E;              // Delete object when it and the image are no longer needed

Listing One

// Compress and Expand CAL Files Class Interface Definition
#ifndef CAL_HPP
#define CAL_HPP
#include "dct1.hpp"
#include "quant.hpp"
#include "huffman.hpp"
#include "bufman.hpp"
#ifndef __RGBCOLOR
#define __RGBCOLOR
typedef struct  {
  BYTE Red;
  BYTE Green;
  BYTE Blue;
} RGBCOLOR;
#endif
// Define the CAL file header
typedef struct {
    WORD   StructureSize;         // Size of structure for version control
    WORD   CALFileTag;            // Tag should be CL
    IMAGETYPE ImageType;          // Type of image
    WORD   ImageWidth;            // Image width in pixels
    WORD   ImageHeight;           // Image height in pixels
    DWORD  RasterSize;            // DIB Raster size including padding
    WORD   BitsPerPixel;          // Number of bits per pixel
    WORD   NumberOfColors;        // Number of important colors in image
    WORD   QualityFactor;         // Quality factor image compressed with
    WORD   NumberOfMCUs;          // Total number of MCUs in image
    WORD   BlocksPerMCU;          // Number of blocks in a single MCU
    RGBCOLOR Palette[256];        // Palette for image display
    DWORD  Unused1;               // TBD
} CALFILEHEADER;
// The Expand Class Definition
class ExpandCALImage {
  private:
    int ErrorCode;
    CALFILEHEADER Header;
    BYTE huge *lpImageData;
    BufferManager *BM;
    DCT InvTransform;
    Quantize InvQuant;
    Huffman *Decoder;
    int PreviousYBlockDCValue;    // DC values of previously decoded blocks
    int PreviousCbBlockDCValue;
    int PreviousCrBlockDCValue;
  public:
    ExpandCALImage(LPSTR FileName);
    virtual ~ExpandCALImage(void);
    BOOL ExpandImage(void);
    WORD GetWidth(void)                { return Header.ImageWidth; }
    WORD GetHeight(void)               { return Header.ImageHeight; }
    WORD GetColors(void)               { return Header.NumberOfColors; }
    WORD GetBitsPerPixel(void)         { return Header.BitsPerPixel; }
    DWORD GetRasterSize(void)          { return Header.RasterSize; }
    BYTE huge * GetDataPtr(void)       { return lpImageData; }
    RGBCOLOR * GetPalettePtr(void)     { return Header.Palette; }
    int GetError(void);
};
// The Compress Class Definition
class CompressCALImage {
  private:
    int ErrorCode;
    CALFILEHEADER Header;
    BYTE huge *lpImageData;
    WORD BlocksPerMCU;
    WORD NumberOfMCUs;
    int PreviousYBlockDCValue;    // DC values of previously encoded blocks
    int PreviousCbBlockDCValue;
    int PreviousCrBlockDCValue;
    BufferManager *BM;
    DCT FwdTransform;
    Quantize FwdQuant;
    Huffman *Encoder;
  public:
    CompressCALImage(LPSTR FileName, IMAGETYPE Type,
                     BYTE huge *lpImage, RGBCOLOR *lpPalette,
                     WORD Width, WORD Height,
                     WORD BitsPerPixel, WORD NumOfColors,
                     WORD QualityFactor);
    virtual ~CompressCALImage(void);
    BOOL CompressImage(void);
    int GetError(void);
};
#endif

Listing Two

// Compress and Expand CAL Files Class Member Functions
#include "string.h"
#include "cal.hpp"
#include "errors.h"
// The following functions deal with CAL file expansion
// Class Constructor
ExpandCALImage::ExpandCALImage(LPSTR FileName) {
  ErrorCode = NoError;
  // Clear header storage
  memset(&Header, 0, sizeof(CALFILEHEADER));
  Decoder = NULL;                 // Initialize object ptrs to NULL
  BM = NULL;
  // Instantiate a Huffman object in order to read file header
  Decoder = new Huffman(FileName, HUFFMANDECODE);
  if (!Decoder) {                 // Memory problem if object not created
    ErrorCode = ENoMemory;
    return;
  }
  // Now read the file header
  Decoder->FileObject.ReadMBytes((BYTE huge *) &Header, sizeof(CALFILEHEADER));
  // Check header tag to verify file type
  if (strncmp((char *) &(Header.CALFileTag), "CL", 2) != 0) {
    ErrorCode = ENotCALFile;
    return;
  }
  // Now allocate a block of memory to contain the expanded image
  lpImageData = (BYTE huge *) MyAlloc(Header.RasterSize);
  if (!lpImageData) {
    ErrorCode = ENoMemory;
    return;
  }
  // Now instantiate a Buffer Manager object to manage the image data
  BM = new BufferManager(Header.ImageType, Header.ImageWidth, 
                                             Header.ImageHeight, lpImageData);
  if (!BM) {                      // Memory problem if object not created
    ErrorCode = ENoMemory;
    return;
  }
  // Build quantization tables for decoding image
  InvQuant.SetQuality(Header.QualityFactor);
  // Initialize previous DC values for the various image color components
  // to zero. Used in computing and decoding the DC difference values.
  PreviousYBlockDCValue  = 0;
  PreviousCbBlockDCValue = 0;
  PreviousCrBlockDCValue = 0;
}
ExpandCALImage::~ExpandCALImage(void) {
  // Release any objects and/or memory used
  if (Decoder) delete Decoder;
  if (BM)      delete BM;
  if (lpImageData) MyFree(lpImageData);
}
// The call to this function performs image expansion from CAL file to DIB.
BOOL ExpandCALImage::ExpandImage(void) {
  INTBLOCK  iBlock,  iBlock1;
  BYTEBLOCK bBlock;
  // Make sure no errors have occurred before preceeding
  if (ErrorCode != NoError)
    return FALSE;
  // For each MCU of image do
  for (register int MCU = 0; MCU < Header.NumberOfMCUs; MCU++) {
    // For each block of MCU
    for (register int Block = 0; Block < Header.BlocksPerMCU; Block++) {
      // Determine what to do from block count
      switch(Block) {             // Blocks 0..3 are luma samples
        case 0:
        case 1:
        case 2:
        case 3:                   // Decode the luma samples
          // Decode the luma samples
          Decoder->DecodeBlock((int *) iBlock, USELUMATABLE);
          // Decode the actual DC coefficient value from the encoded delta
          *((int *) iBlock) += PreviousYBlockDCValue;
          PreviousYBlockDCValue = *((int *) iBlock);
          // Dequantize the block
          InvQuant.QuantizeBlock((int *) iBlock, LUMA, DEQUANT);
          break;
        case 4:                   // Decode the chroma samples
        case 5:
          // Decode the chroma samples
          Decoder->DecodeBlock((int *) iBlock, USECHROMATABLE);
          // Decode the actual DC coefficient value from the encoded delta
          if (Block == 4) {       // If the Cb block
            *((int *) iBlock) += PreviousCbBlockDCValue;
            PreviousCbBlockDCValue = *((int *) iBlock);
          } else {                // If the Cr block
            *((int *) iBlock) += PreviousCrBlockDCValue;
            PreviousCrBlockDCValue = *((int *) iBlock);
          }
          // Dequantize the block
          InvQuant.QuantizeBlock((int *) iBlock, CHROMA, DEQUANT);
          break;
      }
      // Zigzag reorder block
      InvTransform.ZigZagReorder((int *) iBlock, (int *) iBlock1, INVERSEREORDER);
      // Now perform the inverse DCT on the image data
      InvTransform.IDCT(&iBlock1, &bBlock);
      // Store the recovered image data into the DIB memory
      BM->PutNextBlock((BYTE *) bBlock, Block);
    }
    Decoder->FlushInputStream();
  }
  // Flush the DIB image data to the buffer
  BM->FlushDIBData();
  // Close the file
  Decoder->FileObject.CloseFile();
  return TRUE;
}
// Return error code if any for last operation
int ExpandCALImage::GetError(void) {
  int Code = ErrorCode;
  ErrorCode = NoError;
  return Code;
}
// The following functions deal with CAL file compression
CompressCALImage::CompressCALImage(
                    LPSTR FileName, IMAGETYPE Type,
                    BYTE huge *lpImage, RGBCOLOR *lpPalette,
                    WORD Width, WORD Height,
                    WORD BitsPerPixel, WORD NumberOfColors,
                    WORD QualityFactor) {
  ErrorCode = NoError;            // Assume no errors have occurred
  // Clear header storage
  memset(&Header, 0, sizeof(CALFILEHEADER));
  Encoder = NULL;                 // Initialize object ptrs to NULL
  BM = NULL;
  // Instantiate a Huffman object in order to write file header
  Encoder = new Huffman(FileName, HUFFMANENCODE);
  if (!Encoder) {                 // Memory problem if object not created
    ErrorCode = ENoMemory;
    return;
  }
  // Now instantiate a Buffer Manager object to manage the image data
  BM = new BufferManager(Type, Width, Height, lpImage);
  if (!BM) {                      // Memory problem if object not created
    ErrorCode = ENoMemory;
    return;
  }
  // Fill in the header entries from the parameters passed in
  Header.StructureSize = sizeof(CALFILEHEADER);  // Write structure size
  lstrcpy((LPSTR) &Header.CALFileTag, "CL");     // Write CAL tag
  Header.ImageType = Type;        // Type of image
  Header.ImageWidth = Width;      // Image width in pixels
  Header.ImageHeight = Height;    // Image height in pixels
  // Calculate DIB Raster size including appropriate padding
  DWORD BytesPerLine = (Type == TRUECOLORTYPE) ? Width * 3:Width;
  BytesPerLine = ALIGN_DWORD(BytesPerLine);
  Header.RasterSize = BytesPerLine * Height;
  Header.BitsPerPixel = BitsPerPixel;       // Number of bits per pixel
  Header.NumberOfColors = NumberOfColors;   // Number of colors in image
  Header.QualityFactor = QualityFactor;     // Quality factor 
  // Now calculate image statistics for two dimensional color subsampling
  if (Type == TRUECOLORTYPE)      // If image is true color there are
    BlocksPerMCU = 6;             // 6 blocks / MCU. 4 luma and 2 chroma
  else                            // If image is black/white there are
    BlocksPerMCU = 4;             // 4 blocks / MCU. 4 luma
  // Store results in image header
  Header.BlocksPerMCU = BlocksPerMCU;
  WORD NumberOfHorzBlocks = ((Width  + 15) / 16) * 2; // Horizontal 8x8 blocks 
  WORD NumberOfVertBlocks = ((Height + 15) / 16) * 2; // Vertical 8x8 blocks 
  NumberOfMCUs = (NumberOfHorzBlocks / 2) * (NumberOfVertBlocks / 2);
  // Store results in image header
  Header.NumberOfMCUs = NumberOfMCUs;
  // Copy palette if required
  if ((Type == PALETTECOLORTYPE) || (Type == GRAYSCALETYPE))
    memcpy(&Header.Palette, lpPalette, NumberOfColors * sizeof(RGBCOLOR));
  lpImageData = lpImage;          // Copy ptr to DIB image data
  // Build quantization tables for encoding image
  FwdQuant.SetQuality(QualityFactor);
  // Initialize previous DC values for the various image color components
  // to zero. Used in computing and encoding the DC difference values.
  PreviousYBlockDCValue  = 0;
  PreviousCbBlockDCValue = 0;
  PreviousCrBlockDCValue = 0;
}
// Class Destructor
CompressCALImage::~CompressCALImage(void) {
  // Release any objects used
  if (Encoder) delete Encoder;
  if (BM)      delete BM;
}
BOOL CompressCALImage::CompressImage(void) {
  BYTEBLOCK bBlock;
  INTBLOCK  iBlock,  iBlock1;
  int TempInt;
  // Make sure no errors have occurred before preceeding
  if (ErrorCode != NoError)
    return FALSE;
  // First write the initialized header to the specified file
  Encoder->FileObject.WriteMBytes((BYTE huge *) &Header,sizeof(CALFILEHEADER));
  // For each MCU of image
  for (register int MCU = 0; MCU < NumberOfMCUs; MCU++) {
    // For each block of an MCU
    for (register int Block = 0; Block < BlocksPerMCU; Block++) {
      // Get a block of image data to process
      BM->GetNextBlock((BYTE *) bBlock, Block);
      // Do DCT on the block
      FwdTransform.FDCT(&bBlock, &iBlock);
      // Zigzag reorder block
      FwdTransform.ZigZagReorder((int *)iBlock,(int *) iBlock1,FORWARDREORDER);
      // Determine what to do next from block count
      switch(Block) {
        case 0:
        case 1:
        case 2:
        case 3:                   // Process luma samples
          // Quantize the block
          FwdQuant.QuantizeBlock((int *) iBlock1, LUMA, QUANT);
          // Calculate and encode the differential DC value. First get the
          // DC coefficient from the block (the first element) and save it.
          // Next subtract the previous DC value from it. Finally store
          // the actual DC coefficient value for encoding the next block.
          TempInt = *((int *) iBlock1);
          *((int *) iBlock1) -= PreviousYBlockDCValue;
          PreviousYBlockDCValue = TempInt;
          // Encode the block into the Huffman bit stream.
          Encoder->EncodeBlock((int *) iBlock1, USELUMATABLE);
          break;
        case 4:                   // Process Cb and Cr samples
        case 5:
          // Quantize the block
          FwdQuant.QuantizeBlock((int *) iBlock1, CHROMA, QUANT);
          // Calculate and encode differential DC value. See comments above.
          TempInt = *((int *) iBlock1);     // Get the DC coefficient of block
          if (Block == 4) {       // If the Cb block
            *((int *) iBlock1) -= PreviousCbBlockDCValue;
            PreviousCbBlockDCValue = TempInt;
          } else {                // If the Cr block
            *((int *) iBlock1) -= PreviousCrBlockDCValue;
            PreviousCrBlockDCValue = TempInt;
          }
          // Encode the block into the Huffman bit stream.
          Encoder->EncodeBlock((int *) iBlock1, USECHROMATABLE);
          break;
      }
    }
    Encoder->FlushOutputStream();
  }
  // Now close the output file
  Encoder->FileObject.CloseFile();
  // Signal all is well
  return TRUE;
}
// Return error code if any for last operation
int CompressCALImage::GetError(void) {
  int Code = ErrorCode;
  ErrorCode = NoError;
  return Code;
}
// The following miscellaneous functions are used throught the code.
// Allocate a block of memory from global heap. Store handle within block.
void far * MyAlloc(DWORD Size) {
  HGLOBAL hMem;
  // Attempt to allocate the desired size block of memory
  if ((hMem = GlobalAlloc (GHND, Size + sizeof(HGLOBAL)))== NULL)
    return (void far *) NULL;
  void far *pMem = GlobalLock(hMem);   // Get a pointer to the memory block
  *((HGLOBAL far *) pMem) = hMem; // Store handle in block
  // Return pointer that points past handle
  return ((LPSTR) pMem + sizeof(HGLOBAL));   
}
// Free a block of global memory. Handle is stored within block.
void MyFree(void far * pMem) {
  LPSTR HandlePtr = (LPSTR) pMem - sizeof(HGLOBAL);
  HGLOBAL hMem = *((HGLOBAL far *) HandlePtr);
  GlobalUnlock(hMem);
  GlobalFree(hMem);
  pMem = NULL;                    // Zero pointer before return
}


Copyright © 1995, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.