//--------------------------------------------------------------- // File: pcximage.cpp // // Image manipulation functions for PCX format images. //--------------------------------------------------------------- #include "stdafx.h" #include "global.h" #include "pbrush.h" #include "pbrusfrm.h" #include "pbrusvw.h" #include "minifwnd.h" #include "bmobject.h" #include "imgsuprt.h" #include "imgwnd.h" #include "imgbrush.h" #include "imgwell.h" #include "imgtools.h" #include "toolbox.h" #include "imgfile.h" #include "imgcolor.h" #include "undo.h" #include "props.h" #include "ferr.h" #include "ctype.h" #include "cmpmsg.h" #define COLORMAPLENGTH 48 #define FILLERLENGTH 58 #ifdef PCX_SUPPORT struct PCXHeader { unsigned char manufacturer; unsigned char version; unsigned char encoding; unsigned char bits_per_pixel_per_plane; short xmin; short ymin; short xmax; short ymax; unsigned short hresolution; unsigned short vresolution; unsigned char colormap[COLORMAPLENGTH]; unsigned char reserved; unsigned char nplanes; unsigned short bytes_per_line; short palette_info; unsigned char filler[FILLERLENGTH]; // Header is 128 bytes }; #endif class CFileBuffer : public CObject { DECLARE_DYNCREATE( CFileBuffer ) public: enum Type { READ, WRITE }; CFileBuffer(); ~CFileBuffer(); BOOL Create( CFile* pfile, Type IO ); short Get ( void ); BOOL Put ( BYTE cByte ); long Seek ( long lOff, UINT nFrom ); BOOL Flush ( void ); private: void Fill ( void ); enum { MAX_BUFFER = 2048 }; CFile* m_pFile; int m_iBuffPos; int m_iBuffSize; BYTE* m_pBuffer; }; IMPLEMENT_DYNCREATE( CFileBuffer, CObject ) #include "memtrace.h" /****************************************************************************/ CFileBuffer::CFileBuffer() : CObject() { m_pFile = 0; m_iBuffPos = 0; m_iBuffSize = 0; m_pBuffer = 0; } /****************************************************************************/ CFileBuffer::~CFileBuffer() { if (m_pBuffer) delete [] m_pBuffer; } /****************************************************************************/ BOOL CFileBuffer::Create( CFile* pfile, Type IO ) { ASSERT( pfile != NULL ); if (pfile == NULL) return FALSE; m_pFile = pfile; m_pBuffer = new BYTE[MAX_BUFFER]; if (! m_pBuffer) { theApp.SetMemoryEmergency(); return FALSE; } if (IO == READ) { Fill(); if (! m_iBuffSize) { theApp.SetFileError( IDS_ERROR_READLOAD, ferrIllformedFile ); return FALSE; } } return TRUE; } /****************************************************************************/ short CFileBuffer::Get( void ) { if (! m_iBuffSize) return EOF; short sByte = (short)(unsigned short)m_pBuffer[m_iBuffPos++]; if (m_iBuffPos == m_iBuffSize) Fill(); return sByte; } /****************************************************************************/ BOOL CFileBuffer::Put( BYTE cByte ) { m_pBuffer[m_iBuffSize++] = cByte; if (m_iBuffSize == MAX_BUFFER) return Flush(); return TRUE; } /****************************************************************************/ long CFileBuffer::Seek( long lOff, UINT nFrom ) { long lPos = m_pFile->Seek( lOff, nFrom ); Fill(); return lPos; } /****************************************************************************/ void CFileBuffer::Fill() { m_iBuffSize = m_pFile->Read( m_pBuffer, MAX_BUFFER ); m_iBuffPos = 0; } /****************************************************************************/ BOOL CFileBuffer::Flush( void ) { TRY { m_pFile->Write( m_pBuffer, m_iBuffSize ); } CATCH( CFileException, ex ) { m_pFile->Abort(); theApp.SetFileError( IDS_ERROR_SAVE, ex->m_cause ); return FALSE; } END_CATCH m_iBuffSize = 0; return TRUE; } /****************************************************************************/ /****************************************************************************/ #ifdef PCX_SUPPORT BOOL CBitmapObj::ReadPCX( CFile* pfile ) { if (! pfile->GetLength()) { if (m_hThing) Free(); m_bDirty = TRUE; return TRUE; } // if a PCX extension try to load this as a PCX image. PCXHeader hdr; PBITMAP p_dib; // Device independent bitmap short bytes_per_line; pfile->Read( (unsigned char*)&hdr, sizeof( PCXHeader ) ); // Check if image file format is acceptable if (hdr.manufacturer != 0x0a) { theApp.SetFileError( IDS_ERROR_READLOAD, ferrCantDetermineType ); return FALSE; } // We only handle 1, 4, 8, or 24-bit images short bits_per_pixel = hdr.nplanes * hdr.bits_per_pixel_per_plane; if (bits_per_pixel != 1 && bits_per_pixel != 4 && bits_per_pixel != 8 && bits_per_pixel != 24) { theApp.SetFileError( IDS_ERROR_READLOAD, ferrCantDetermineType ); return FALSE; } short image_width = hdr.xmax - hdr.xmin + 1; short image_height = hdr.ymax - hdr.ymin + 1; // Allocate space where the PCX image will be unpacked. long pcx_image_size = (long) hdr.nplanes * (long) image_height * (long) hdr.bytes_per_line; BYTE* image = (BYTE*) new BYTE[pcx_image_size]; if (image == NULL) { theApp.SetMemoryEmergency(); return FALSE; } // Read in PCX image into this area. CFileBuffer FileBuffer; if (! FileBuffer.Create( pfile, CFileBuffer::READ )) { delete [] image; return FALSE; } // Decode run-length encoded image data short i; short byte; short count; long pos = 0L; while ((byte = FileBuffer.Get()) != EOF) { if ((byte & 0xc0) == 0xc0) { count = byte & 0x3f; if ((byte = FileBuffer.Get()) != EOF) { for (i = 0; i < count; i++) { if (pos >= pcx_image_size) break; image[pos] = (CHAR)byte; pos++; } } } else { if (pos >= pcx_image_size) break; image[pos] = (CHAR)byte; pos++; } } // Allocate memory for the device independent bitmap (DIB) // Note that the number of bytes in each line of a DIB image // must be a multiple of 4. short bytes_per_line_per_plane = (image_width * hdr.bits_per_pixel_per_plane + 7) / 8; short actual_bytes_per_line = (image_width * hdr.nplanes * hdr.bits_per_pixel_per_plane + 7) / 8; bytes_per_line = actual_bytes_per_line; if ( bytes_per_line % 4) bytes_per_line = 4 * ( bytes_per_line / 4 + 1); // Make room for a palette short palettesize = 16; if (bits_per_pixel == 1) palettesize = 2; if (hdr.version >= 5 && bits_per_pixel > 4) { // Go back 769 bytes from the end of the file FileBuffer.Seek( -769, CFile::end ); if (FileBuffer.Get() == 12) { // There is a 256-color palette following this byte palettesize = 256; } } // If image has more than 256 colors then there is no palette if (bits_per_pixel > 8) palettesize = 0; // Allocate space for the bitmap if (m_hThing) Free(); m_lMemSize = sizeof( BITMAPINFOHEADER ) + palettesize * sizeof( RGBQUAD ) + (long)bytes_per_line * (long)image_height; if (! Alloc()) return FALSE; p_dib = (PBITMAP) GlobalLock(m_hThing); // Set up bitmap info header LPBITMAPINFOHEADER p_bminfo = (LPBITMAPINFOHEADER)p_dib; p_bminfo->biSize = sizeof(BITMAPINFOHEADER); p_bminfo->biWidth = image_width; p_bminfo->biHeight = image_height; p_bminfo->biPlanes = 1; p_bminfo->biBitCount = hdr.bits_per_pixel_per_plane * hdr.nplanes; p_bminfo->biCompression = BI_RGB; p_bminfo->biSizeImage = (long)image_height * (long) bytes_per_line; p_bminfo->biXPelsPerMeter = (long)hdr.hresolution; p_bminfo->biYPelsPerMeter = (long)hdr.vresolution; p_bminfo->biClrUsed = 0; p_bminfo->biClrImportant = 0; // Set up the color palette if (palettesize > 0) { //***** RGBQUAD *palette = (RGBQUAD*) ((LPSTR)imdata->p_dib LPRGBQUAD palette = LPRGBQUAD((LPSTR)p_dib + sizeof(BITMAPINFOHEADER)); short palindex; for (palindex = 0; palindex < palettesize; palindex++) { if (palettesize == 256) { // Read palette from file palette[palindex].rgbRed = (BYTE)FileBuffer.Get(); palette[palindex].rgbGreen = (BYTE)FileBuffer.Get(); palette[palindex].rgbBlue = (BYTE)FileBuffer.Get(); palette[palindex].rgbReserved = 0; } if (palettesize == 16) { // 16-color palette from PCX header palette[palindex].rgbRed = (BYTE)hdr.colormap[3*palindex]; palette[palindex].rgbGreen = (BYTE)hdr.colormap[3*palindex+1]; palette[palindex].rgbBlue = (BYTE)hdr.colormap[3*palindex+2]; palette[palindex].rgbReserved = 0; } if (palettesize == 2) { // Set up palette for black and white images palette[palindex].rgbRed = palindex * 255; palette[palindex].rgbGreen = palindex * 255; palette[palindex].rgbBlue = palindex * 255; palette[palindex].rgbReserved = 0; } } } // Load image data into the DIB. Note the DIB image must be // stored "bottom to top" line order. That's why we position // data at the end of the array so that the image can be // stored backwards--from the last line to the first. BYTE* data = (BYTE*)p_dib + ((long)sizeof( BITMAPINFOHEADER ) + palettesize * sizeof( RGBQUAD ) + (image_height - 1) * bytes_per_line); // Define a macro to access bytes in the PCX image according // to specified line and plane index. short lineindex, byteindex, planeindex; #define bytepos(lineindex, planeindex, byteindex) \ ((long)(lineindex)*(long)hdr.bytes_per_line* \ (long)hdr.nplanes + \ (long)(planeindex)*(long)hdr.bytes_per_line + \ (long)(byteindex)) // Construct packed pixels out of decoded PCX image. short loc; unsigned short onebyte; unsigned short bits_copied; unsigned short few_bits; unsigned short k; unsigned short bbpb = 8/hdr.bits_per_pixel_per_plane; // Build a mask to pick out bits from each byte of the PCX image unsigned short himask = 0x80, mask; if (hdr.bits_per_pixel_per_plane > 1) for (i = 0; i < hdr.bits_per_pixel_per_plane - 1; i++) himask = 0x80 | (himask >> 1); for (lineindex = 0; lineindex < image_height; lineindex++, data -= bytes_per_line) { if (actual_bytes_per_line < bytes_per_line) for (loc = actual_bytes_per_line; loc < bytes_per_line; loc++) data[loc] = 0; loc = 0; onebyte = 0; bits_copied = 0; for (byteindex = 0; byteindex < bytes_per_line_per_plane; byteindex++) { for (k = 0, mask = himask; k < bbpb; k++, mask >>= hdr.bits_per_pixel_per_plane) { // Go through all scan line for all planes and copy bits into // the data array for (planeindex = 0; planeindex < hdr.nplanes; planeindex++) { few_bits = image[bytepos(lineindex, planeindex, byteindex)] & mask; // Shift the selected bits to the most significant position if (k > 0) few_bits <<= (k*hdr.bits_per_pixel_per_plane); // OR the bits with current pixel after shifting them right if (bits_copied > 0) few_bits >>= bits_copied; onebyte |= few_bits; bits_copied += hdr.bits_per_pixel_per_plane; if (bits_copied >= 8) { data[loc] = (UCHAR)onebyte; loc++; bits_copied = 0; onebyte = 0; } } } } } // Success! delete [] (BYTE*)image; GlobalUnlock(m_hThing); return TRUE; } /****************************************************************************/ #define WIDTHBYTES(bits) ((((bits) + 31) / 32) * 4) BOOL CBitmapObj::WritePCX( CFile* pfile ) { if (m_pImg == NULL) { // The image has not been loaded, so we'll just copy the // original out to the file... ASSERT( m_hThing ); if (! m_hThing) return FALSE; } else { // The image has been loaded and may have been edited, so // we'll convert it back to a dib to save... if (! m_hThing) SaveResource( FALSE ); if (! m_hThing) return FALSE; } // build pcx file from the DIB PBITMAP p_dib = (PBITMAP)GlobalLock(m_hThing); // Device independent bitmap PCXHeader hdr; // PCX bitmap header LPBITMAPINFOHEADER p_bminfo = (LPBITMAPINFOHEADER)p_dib; // Set up bitmap info header short palettesize = DIBNumColors( (LPSTR)p_dib); // Get palette size hdr.manufacturer = 10; // hdr.version = (char)((hPalette || (GetDeviceCaps(fileDC, RASTERCAPS) & RC_PALETTE)) ? 5 : 3); hdr.version = (CHAR)( palettesize ? 5 : 3); hdr.encoding = 1; hdr.xmin = hdr.ymin = 0; hdr.xmax = p_bminfo->biWidth - 1; hdr.ymax = p_bminfo->biHeight- 1; // hdr.hresolution = theApp.ScreenDeviceInfo.iWidthinPels; // hdr.vresolution = theApp.ScreenDeviceInfo.iHeightinPels; hdr.hresolution = (WORD)p_bminfo->biXPelsPerMeter; hdr.vresolution = (WORD)p_bminfo->biYPelsPerMeter; hdr.reserved = 0; hdr.nplanes = (BYTE)p_bminfo->biPlanes; //biPlanes should always be 1 hdr.palette_info = (BYTE)p_dib->bmWidthBytes; hdr.bits_per_pixel_per_plane = (CHAR) p_bminfo->biBitCount; hdr.bytes_per_line = WIDTHBYTES( (LONG) (p_bminfo->biBitCount * p_bminfo->biWidth) ); // Clean up filler for (int index = FILLERLENGTH; index--; ) hdr.filler[index] ='\0'; // If there are at most 16 colors place them in header LPRGBQUAD palette = LPRGBQUAD((LPSTR)p_dib + sizeof(BITMAPINFOHEADER)); LPSTR lpDst = (LPSTR)hdr.colormap; // Clean up colormap for (index = COLORMAPLENGTH; index--; ) lpDst[index] ='\0'; if (palettesize <= 16) for (index = palettesize; index--; ) { *lpDst++ = palette->rgbRed; /* swap RED and BLUE components */ *lpDst++ = palette->rgbGreen; *lpDst++ = palette->rgbBlue; palette++; } pfile->Write( (unsigned char*)&hdr, sizeof( PCXHeader ) ); // Now pack the image // Load image data from the DIB. Note the DIB image is // stored "bottom to top" line order. That's why we position // data at the end of the array so that the image can be // stored backwards--from the last line to the first. CFileBuffer FileBuffer; if (! FileBuffer.Create( pfile, CFileBuffer::WRITE )) { GlobalUnlock(m_hThing); return FALSE; } // find the start of the bitmap data then go to the end of the data // the PCX is stored in reverse order of the DIB int TopofData = sizeof( BITMAPINFOHEADER ) + palettesize * sizeof( RGBQUAD ); BYTE* data = (BYTE*)p_dib + TopofData + hdr.bytes_per_line * (p_bminfo->biHeight ); for (index = p_bminfo->biHeight; index--; ) { data -= hdr.bytes_per_line; if (! PackBuff( &FileBuffer, data, hdr.bytes_per_line )) //convert to run length encoding. { GlobalUnlock(m_hThing); return FALSE; } } if (palettesize == 256) // Write palette to file { if (! FileBuffer.Put( 12 )) // Tag number for palette information { GlobalUnlock(m_hThing); return FALSE; } for (index = 0; index < palettesize; index++) { if (! FileBuffer.Put( palette[index].rgbRed ) || ! FileBuffer.Put( palette[index].rgbGreen ) || ! FileBuffer.Put( palette[index].rgbBlue )) { GlobalUnlock(m_hThing); return FALSE; } } } GlobalUnlock(m_hThing); return FileBuffer.Flush(); } #endif //PCX_SUPPORT /****************************************************************************/ /* run length encoding equates */ #define MINcount 2 #define MAXcount 63 #define ESCbits 0xC0 #define BUFFER_SIZE 1024 /* bitmaps are ordered but PCX is ordered ... */ BOOL CBitmapObj::PackBuff(CFileBuffer *FileBuffer, BYTE *PtrDib, int byteWidth ) { BYTE runChar; BYTE runCount; BYTE* endPtr = PtrDib + byteWidth; for (runCount = 1, runChar = *PtrDib++; PtrDib <= endPtr; ++PtrDib) { if (PtrDib != endPtr && *PtrDib == runChar && runCount < MAXcount) ++runCount; else if (*PtrDib != runChar && runCount < MINcount && (runChar & ESCbits) != ESCbits) { while (runCount--) if (! FileBuffer->Put( runChar )) return FALSE; runCount = 1; runChar = *PtrDib; } else { runCount |= ESCbits; if (! FileBuffer->Put( runCount ) || ! FileBuffer->Put( runChar )) return FALSE; runCount = 1; runChar = *PtrDib; } } return TRUE; } /****************************************************************************/