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

C++图像处理 -- PCX格式图像(上)(下)C 图像处理 PCX 格式图像 上 PCX 是一个比较早的图像文件格式 它也有过一段时间的辉煌 但随着计算机硬 软件的发展 该图像格式基本已成过去时 主要是因为早期 PCX 格式图像是配合当时显卡硬件而设计的 如 CGA EGA VGA 等

大家好,我是讯享网,很高兴认识大家。

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

    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; // 最小X WORD yMin; // 最小Y WORD xMax; // 最大X WORD yMax; // 最大Y WORD hRes; // 水平分辨率 WORD vRes; // 垂直分辨率 BYTE palette[48]; // 16色调色板 BYTE reserved; // 保留 BYTE planes; // 平面数 WORD bytesPreLine; // 每行字节数 WORD paletteType; // 调色板类型。1:彩色或黑白,2:灰度 BYTE filler[58]; }PcxFileHeader, *PPcxFileHeader; //--------------------------------------------------------------------------- FORCEINLINE LPBYTE 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位真彩色读取,而不再管其它描述。

讯享网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保存,或者自己写这种格式文件。

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


讯享网

《C++图像处理 -- PCX格式图像(上)》将PCX格式图像转换为GDI+位图,本文则介绍将GDI+位图转换为PCX格式图像。

下面是GDI+位图转换为PCX格式图像代码:

讯享网INT PackPcxLine(LPBYTE dest, LPBYTE source, INT bytesPreLine, INT planes) { LPBYTE pd = dest; INT delta = planes --; LPBYTE ps = source + planes; INT bytes = bytesPreLine; while(planes >= 0) { INT count = 0; BYTE c = *ps; do { count ++; if (-- bytes == 0) { if (-- planes < 0) break; bytes = bytesPreLine; ps = source + planes; } else ps += delta; } while(c == *ps && count < 0x3f); if (c >= 0xc0 || count > 1) *pd ++ = count | 0xc0; *pd ++ = c; } return pd - dest; } //--------------------------------------------------------------------------- typedef union { WORD value; struct { BYTE low; BYTE high; }; }testMask; INT PackPcx4Line(LPBYTE dest, LPBYTE source, INT bytesPreLine, INT width) { INT bytes = bytesPreLine << 2; LPBYTE buf = dest + bytes; testMask mask; mask.value = 0x1001; width = (width + 1) >> 1; while(mask.high) { BYTE c = 0; BYTE bit = 0x80; LPBYTE pb = buf; for (INT i = 0; i < width; i ++) { if (source[i] & mask.high) c |= bit; bit >>= 1; if (source[i] & mask.low) c |= bit; bit >>= 1; if (bit == 0) { *pb ++ = c; c = 0; bit = 0x80; } } buf += bytesPreLine; mask.value <<= 1; } return PackPcxLine(dest, dest + bytes, bytes, 1); } //--------------------------------------------------------------------------- VOID ARGBQuadToRGBTriple(PRGBTriple dest, PRGBQuad source, INT count) { for (INT i = 0; i < count; i++) { dest[i].rgbtBlue = source[i].rgbRed; dest[i].rgbtGreen = source[i].rgbGreen; dest[i].rgbtRed = source[i].rgbBlue; } } //--------------------------------------------------------------------------- BOOL SavePcxImageToStream(IStream *stream, Bitmap *bmp) { PixelFormat format = bmp->GetPixelFormat(); if (format == PixelFormatUndefined) return FALSE; PcxFileHeader header; ColorPalette *pal = NULL; BYTE palette[256 * 3 + 1]; ZeroMemory(&header, sizeof(PcxFileHeader)); header.bitsPrePixel = GetPixelFormatSize(format); header.planes = 1; if (header.bitsPrePixel > 8) { format = PixelFormat24bppRGB; header.planes = 3; header.bitsPrePixel = 8; } else //if (header.bitsPrePixel > 1) { pal = (ColorPalette*)new BYTE[256 * sizeof(ARGB) + sizeof(ColorPalette)]; bmp->GetPalette(pal, bmp->GetPaletteSize()); PRGBTriple ppal = (PRGBTriple)&palette[1]; // 如果是16色位图,调色板保存到文件头 if (format == PixelFormat4bppIndexed) { header.planes = header.bitsPrePixel; header.bitsPrePixel = 1; ppal = (PRGBTriple)header.palette; } ARGBQuadToRGBTriple(ppal, (PRGBQuad)pal->Entries, pal->Count); delete[] pal; } Gdiplus::Rect r(0, 0, bmp->GetWidth(), bmp->GetHeight()); header.flag = 0x0A; header.version = 5; header.encodeing = 1; header.xMax = r.Width - 1; header.yMax = r.Height - 1; header.hRes = 96; header.vRes = 96; header.paletteType = 1; header.bytesPreLine = (r.Width * header.bitsPrePixel + 7) >> 3; if (header.bytesPreLine & 1) header.bytesPreLine ++; // 保存PCX文件头 if (stream->Write(&header, sizeof(PcxFileHeader), NULL) != S_OK) return FALSE; // 获取GDI+位图数据到位图数据结构 BitmapData data; data.Stride = ((r.Width * GetPixelFormatSize(format) + 31) & -32) >> 3; INT size = r.Height * data.Stride; // size为位图数据字节数,header.bytesPreLine*header.planes*2为编码缓冲区字节数 data.Scan0 = (LPVOID)new BYTE[size + header.bytesPreLine * header.planes * 2]; bmp->LockBits(&r, ImageLockModeRead | ImageLockModeUserInputBuf, format, &data); bmp->UnlockBits(&data); // 如果单色位图调色板首项不为0,位图数据反向 if (format == PixelFormat1bppIndexed && (*(ARGB*)&palette[1] & 0xffffff)) { INT count = data.Height * (data.Stride >> 2); LPDWORD pd = (LPDWORD)data.Scan0; for (INT i = 0; i < count; pd[i] ^= (DWORD)(-1), i ++); } LPBYTE p = (LPBYTE)data.Scan0; LPBYTE buffer = p + size; INT bytes; // 逐行进行RLE编码并保存到流 for (UINT y = 0; y < data.Height; y ++, p += data.Stride) { if (format == PixelFormat4bppIndexed) bytes = PackPcx4Line(buffer, p, header.bytesPreLine, data.Width); else bytes = PackPcxLine(buffer, p, header.bytesPreLine, header.planes); stream->Write(buffer, bytes, NULL); } delete[] data.Scan0; // 如果是256色位图,调色板保存到流的尾部 if (format == PixelFormat8bppIndexed) { palette[0] = 0x0c; stream->Write(palette, 256 * 3 + 1, NULL); } return TRUE; } //--------------------------------------------------------------------------- BOOL SavePcxImageToFile(LPTSTR fileName, Bitmap *bmp) { IStream *stream = new FileStream(fileName, FALSE); stream->AddRef(); BOOL result = SavePcxImageToStream(stream, bmp); stream->Release(); return result; } //--------------------------------------------------------------------------- 

 代码中SavePcxImageToStream函数已经将大致的转换流程作了注释,本文不再罗嗦,而SavePcxImageToFile函数仍然是利用我写的简易文件流将转换后的PCX格式图像保存到文件,FileStream类在本文上篇《C++图像处理 -- PCX格式图像(上)》中。

void __fastcall TForm1::Button3Click(TObject *Sender) { Gdiplus::Bitmap *bmp = new Gdiplus::Bitmap(L"d:\\1-4.bmp"); if (bmp->GetLastStatus() != Ok) throw new Exception("Load Image fail"); Gdiplus::Graphics *g = new Gdiplus::Graphics(Canvas->Handle); g->DrawImage(bmp, 0, 0); delete g; SavePcxImageToFile("d:\\1-4.pcx", bmp); delete bmp; } 

同样,关于PCX文件格式,请网上搜索有关文档。

转自:

https://blog.csdn.net/maozefa/article/details/

https://blog.csdn.net/maozefa/article/details/

 

小讯
上一篇 2025-02-20 13:34
下一篇 2025-04-03 16:34

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/21487.html