C++图像处理 -- PCX格式图像(上)

来源:互联网 发布:青岛知豆电动车租赁 编辑:程序博客网 时间:2024/05/22 00:23

阅读提示

    《C++图像处理》系列以代码清晰,可读性为主,全部使用C++代码。

    《Delphi图像处理》系列以效率为侧重点,一般代码为PASCAL,核心代码采用BASM。

    尽可能保持二者内容一致,可相互对照。

 

    PCX是一个比较早的图像文件格式,它也有过一段时间的辉煌,但随着计算机硬、软件的发展,该图像格式基本已成过去时,主要是因为早期PCX格式图像是配合当时显卡硬件而设计的,如CGA/EGA/VGA等,现在显然已经过时了,虽然后来的版本增加了对256色和24位真彩色的支持,但仍然因其文件格式的先天不足,导致操作很不方便,如256色图像调色板就是以“补丁”形式追加到文件最后面的,24位真彩色用以前EGA图像卡按行以彩色面形式存放等;另外PCX的RLE编码对6位以下像素格式是比较有效的,对目前8位为主的像素格式压缩也不尽人意,如24位像素格式压缩后,有时比不压缩空间占用还大。

    虽然PCX格式图像目前使用不多,但还是有很多软件是支持这种格式的,如Photoshop。在图像处理编程时,偶尔也会遇到这种格式的图像,但不象BMP、JPEG、GIF等图像格式容易找到现存的库函数或组件,所以本文提供了PCX格式图像与GDI+位图的相互转换的代码,本文分上下两篇,上篇将PCX格式图像转换为GDI+位图,下篇将GDI+位图转换为PCX格式图像。下面是转换源码:

typedef struct         // pcx文件头{BYTE flag;// 标记BYTE version;// 版本号BYTE encodeing;// 编码方式BYTE bitsPrePixel;// 平面像素位数WORD xMin;// 最小XWORD yMin;// 最小YWORD xMax;// 最大XWORD yMax;// 最大YWORD hRes;// 水平分辨率WORD vRes;// 垂直分辨率BYTE palette[48];// 16色调色板BYTE reserved;// 保留BYTE planes;// 平面数WORD bytesPreLine;// 每行字节数WORD paletteType;// 调色板类型。1:彩色或黑白,2:灰度BYTE filler[58];}PcxFileHeader, *PPcxFileHeader;//---------------------------------------------------------------------------FORCEINLINELPBYTE UnpackPckLine(LPBYTE dest, LPBYTE source, INT bytes){while (bytes > 0){if (*source > 0xc0){INT count = *source ++ & 0x3f;BYTE c = *source ++;bytes -= count;for (; count > 0; *dest ++ = c, count --);}else{*dest ++ = *source ++;bytes --;}}return source;}//---------------------------------------------------------------------------// 单色或256色VOID UnpackPck(BitmapData *data, LPBYTE bitsMem, INT bytesPreLine){LPBYTE p = (LPBYTE)data->Scan0;LPBYTE m = bitsMem;for (UINT y = 0; y < data->Height; y ++, p += data->Stride){m = UnpackPckLine(p, m, bytesPreLine);}}//---------------------------------------------------------------------------// 16色VOID UnpackPck4(BitmapData *data, LPBYTE bitsMem, INT bytesPreLine){LPBYTE p = (LPBYTE)data->Scan0;LPBYTE m = bitsMem;INT datOffset = data->Stride -((GetPixelFormatSize(data->PixelFormat) * data->Width + 7) >> 3);if (data->Width & 1) datOffset ++;INT bytes1 = bytesPreLine;INT bytes2 = bytes1 << 1;INT bytes3 = bytes2 + bytes1;INT bytes = bytes1 << 2;LPBYTE buffer = new BYTE[bytes];for (UINT y = 0; y < data->Height; y ++, p += datOffset){m = UnpackPckLine(buffer, m, bytes);LPBYTE b = buffer;BYTE mask = 0x80;for (UINT x = 0; x < data->Width; x ++){if (*b & mask) *p |= 1;if (*(b + bytes1) & mask) *p |= 2;if (*(b + bytes2) & mask) *p |= 4;if (*(b + bytes3) & mask) *p |= 8;if (x & 1) p ++;else *p <<= 4;mask >>= 1;if (!mask){mask = 0x80;b ++;            }        }}delete[] buffer;}//---------------------------------------------------------------------------// 24位真彩色VOID UnpackPck24(BitmapData *data, LPBYTE bitsMem, INT bytesPreLine){INT bytes1 = bytesPreLine;INT bytes2 = bytes1 << 1;INT bytes = bytes2 + bytes1;INT width = (INT)data->Width > bytesPreLine? bytesPreLine : data->Width;INT datOffset = data->Stride - width * 3;PRGBTriple p = (PRGBTriple)data->Scan0;LPBYTE m = bitsMem;LPBYTE buffer = new BYTE[bytes];for (INT y = 0; y < (INT)data->Height; y ++, (LPBYTE)p += datOffset){m = UnpackPckLine(buffer, m, bytes);LPBYTE b = buffer;for (INT x = 0; x < width; x ++, p ++, b ++){p->rgbtRed  = *b;p->rgbtGreen = *(b + bytes1);p->rgbtBlue  = *(b + bytes2);}}delete[] buffer;}//---------------------------------------------------------------------------Bitmap *UnpackPckImage(LPBYTE imageMem, INT imageBytes){PcxFileHeader *header = (PcxFileHeader*)imageMem;if (header->flag != 0x0a) return NULL;PRGBTriple ppal = NULL;PixelFormat format = PixelFormatUndefined;if (header->bitsPrePixel == 1){if (header->planes == 4){format = PixelFormat4bppIndexed;ppal = (PRGBTriple)header->palette;}else format = PixelFormat1bppIndexed;}else{if (header->planes == 3)format = PixelFormat24bppRGB;else if (header->planes == 1){ppal = (PRGBTriple)(imageMem + imageBytes - 256 * 3);if (*((LPBYTE)ppal - 1) == 0x0c)format = PixelFormat8bppIndexed;}}if (format == PixelFormatUndefined) return NULL;Bitmap *bmp = new Bitmap(header->xMax - header->xMin + 1,header->yMax - header->yMin + 1, format);if (ppal){INT count = 1 << (header->bitsPrePixel * header->planes);ColorPalette *pal = (ColorPalette*)new BYTE[count * sizeof(ARGB) + sizeof(ColorPalette)];PRGBQuad pp = (PRGBQuad)pal->Entries;for (INT i = 0; i < count; i ++){pp[i].rgbBlue = ppal[i].rgbtRed;pp[i].rgbGreen = ppal[i].rgbtGreen;pp[i].rgbRed = ppal[i].rgbtBlue;pp[i].rgbReserved = 255;}pal->Flags = 0;pal->Count = count;bmp->SetPalette(pal);delete[] pal;}LPBYTE bitsMem = imageMem + sizeof(PcxFileHeader);BitmapData data;Gdiplus::Rect r(0, 0, bmp->GetWidth(), bmp->GetHeight());bmp->LockBits(&r, ImageLockModeRead | ImageLockModeWrite, format, &data);switch (format){case PixelFormat4bppIndexed:UnpackPck4(&data, bitsMem, header->bytesPreLine);break;case PixelFormat24bppRGB:UnpackPck24(&data, bitsMem, header->bytesPreLine);break;default:UnpackPck(&data, bitsMem, header->bytesPreLine);}bmp->UnlockBits(&data);return bmp;}//---------------------------------------------------------------------------Bitmap *LoadPcxImageFromStream(IStream *stream){LARGE_INTEGER move;ULARGE_INTEGER size;move.QuadPart = 0;if (stream->Seek(move, STREAM_SEEK_END, &size) != S_OK)return NULL;stream->Seek(move, STREAM_SEEK_SET, NULL);LPBYTE imageMem = new BYTE[size.LowPart];Bitmap *bmp = NULL;if (stream->Read(imageMem, size.LowPart, NULL) == S_OK)bmp = UnpackPckImage(imageMem, size.LowPart);delete[] imageMem;return bmp;}//---------------------------------------------------------------------------

    上面代码中,UnpackPckImage函数是核心代码,负责对PCX格式图像内存映像进行解析并转换。现在版本的PCX格式图像主要是单色、16色、256色和24位真彩色,本文代码能准确的解析这几种图像。但有时也可能有些不规范图像,如16色图像,规范的格式应该是像素位数bitsPrePixel=1,像素平面planes=4,同时调色板数据在文件头的palette中,这是从EGA显示卡遗留下来的格式,但如bitsPrePixel=4,像素平面planes=1的格式描述,也同样是16色格式,而且是符合现代16色格式的,我用Photoshop对这种格式描述做过实验,但会显示文件不完整的错误,既然是“不完整”而不是非法错误,证明这种16色格式描述也应该是正确的,因此我尝试将调色板从文件头移到文件尾,结果Photoshop果然将图像读出来了,但只显示了一半的宽度,由此,我得知这是Photoshop的容错读取,即它忽略了bitsPrePixel=4这个描述,而是把它当256色图像处理的,事实上,在Photoshop中是没法正确保存16色图像的,它总是将16色用256色方式保存的,我在UnpackPckImage函数中也采用了这种容错方式,只要图像尾部有调色板,就可以当256色处理;只要planes=3,就当24位真彩色读取,而不再管其它描述。

    LoadPcxImageFromStream函数只是简单的从流读取PCX格式图像到内存映像而已,之所以选择从流读取,主要是考虑通用性。为了能从文件读取PCX图像,我也写了一个不完整的文件流类,只需要前面6个接口函数能用就行了(事实上,只要Read、Write和Seek3个函数能用即可)。下面是这个文件流类和LoadPcxImageFromFile函数代码:

class FileStream : public IStream{HANDLE handle;INT refCount;public:HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID **ppvObject){if (lstrcmp((LPTSTR)&riid, (LPTSTR)&IID_IStream) == 0 ||lstrcmp((LPTSTR)&riid, (LPTSTR)&IID_IUnknown) == 0){*ppvObject = this;AddRef();return S_OK;}*ppvObject = NULL;return E_NOINTERFACE;}ULONG STDMETHODCALLTYPE AddRef(VOID){refCount ++;return refCount;}ULONG STDMETHODCALLTYPE Release(VOID){if (refCount > 0) -- refCount;if (refCount == 0) delete this;return refCount;}HRESULT STDMETHODCALLTYPE Read(VOID *pv, ULONG cb, ULONG *pcbRead){ULONG readBytes;if (ReadFile(handle, pv, cb, &readBytes, NULL)){if (pcbRead) *pcbRead = readBytes;return S_OK;}return E_FAIL;}HRESULT STDMETHODCALLTYPE Write(CONST VOID *pv, ULONG cb, ULONG *pcbWritten){ULONG writeBytes;if (WriteFile(handle, pv, cb, &writeBytes, NULL)){if (pcbWritten) *pcbWritten = writeBytes;return S_OK;}return E_FAIL;}HRESULT STDMETHODCALLTYPE Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition){ULARGE_INTEGER pos;dlibMove.LowPart = SetFilePointer(handle, dlibMove.LowPart, &dlibMove.HighPart, dwOrigin);if (plibNewPosition)plibNewPosition->QuadPart = dlibMove.QuadPart;return dlibMove.QuadPart == -1? E_FAIL : S_OK;}HRESULT STDMETHODCALLTYPE SetSize(ULARGE_INTEGER libNewSize){return Seek(*(LARGE_INTEGER*)&libNewSize, STREAM_SEEK_END, NULL);}HRESULT STDMETHODCALLTYPE CopyTo(IStream *pstm, ULARGE_INTEGER cb, ULARGE_INTEGER *pcbRead, ULARGE_INTEGER *pcbWritten){return S_OK;}HRESULT STDMETHODCALLTYPE Commit(DWORD grfCommitFlags){return S_OK;}HRESULT STDMETHODCALLTYPE Revert(VOID){return STG_E_REVERTED;}HRESULT STDMETHODCALLTYPE LockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType){return STG_E_INVALIDFUNCTION;}HRESULT STDMETHODCALLTYPE UnlockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType){return STG_E_INVALIDFUNCTION;}HRESULT STDMETHODCALLTYPE Stat(STATSTG *pstatstg, DWORD grfStatFlag){return S_OK;}HRESULT STDMETHODCALLTYPE Clone(IStream **ppstm){return E_NOTIMPL;}public:FileStream(VOID) : refCount(0), handle((HANDLE)(-1)) {}FileStream(LPTSTR fileName, BOOL isRead) : refCount(0){handle = CreateFile(fileName, GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,isRead? OPEN_EXISTING : CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);}~FileStream(VOID){if (handle != (HANDLE)(-1))CloseHandle(handle);}};//---------------------------------------------------------------------------Bitmap *LoadPcxImageFromFile(LPTSTR fileName){IStream *stream = new FileStream(fileName, TRUE);stream->AddRef();Bitmap *bmp = LoadPcxImageFromStream(stream);stream->Release();return bmp;}//---------------------------------------------------------------------------

    下面是个从文件读取并显示的例子代码(BCB2010):

void __fastcall TForm1::Button2Click(TObject *Sender){Bitmap *bmp;if ((bmp = LoadPcxImageFromFile("d:\\1-1-8.pcx")) == NULL)throw new Exception("Load Image fail");Gdiplus::Graphics *g = new Gdiplus::Graphics(Canvas->Handle);g->DrawImage(bmp, 0, 0);delete g;delete bmp;}

    本文没有对PCX文件格式进行详细讲解,主要原因是这些网上可以搜索得到,虽然并不完全可靠,但参考一下是可行的,而且,我自己也没法比网上讲的更透彻了,毕竟,PCX格式图像太“古老”了,古老的我想找几个以前版本的文件做实验都没法找到,只好靠Photoshop保存,或者自己写这种格式文件。

 

    因水平有限,错误在所难免,欢迎指正和指导。邮箱地址:maozefa@hotmail.com

    这里可访问《C++图像处理 -- 文章索引