c++做图像处理

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位真彩色读取,而不再管其它描述。

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

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格式图像(上)》中。

    下面是个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文件格式,请网上搜索有关文档。

转自:

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

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

 

知秋君
上一篇 2024-09-10 08:12
下一篇 2024-09-10 07:48

相关推荐