阅读提示:
《C++图像处理》系列以代码清晰,可读性为主,全部使用C++代码。
《Delphi图像处理》系列以效率为侧重点,一般代码为PASCAL,核心代码采用BASM。
尽可能保持二者内容一致,可相互对照。
《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格式图像(上)》中。
下面是个GDI+位图转换为PCX格式图像例子(BCB2010):
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文件格式,请网上搜索有关文档。
因水平有限,错误在所难免,欢迎指正和指导。邮箱地址:[email protected]
这里可访问《C++图像处理 -- 文章索引》。