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.
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.
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.
Copyright © 1995, Dr. Dobb's Journal
<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
}