阅读提示:
《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文件格式,请网上搜索有关文档。
因水平有限,错误在所难免,欢迎指正和指导。邮箱地址:maozefa@hotmail.com
这里可访问《C++图像处理 -- 文章索引》。