_JPEG-LIKE IMAGE COMPRESSION, PART 2_ by Craig A. Lindley 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 } Example 1: 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]; // Palette for image display. Required only for gray scale images DWORD Unused1; // Whatever you want you can put here } CALFILEHEADER; Example 2: 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