C++影像處理 — PCX格式映像(上)

來源:互聯網
上載者:User

閱讀提示

    《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++影像處理 -- 文章索引》。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.