最近的一個項目用到tif圖片格式讀寫。tif是一種圖像文件格式,最初用於黑白傳真,后來也支持彩色。相對於其他圖像格式,tif有點像容器,支持多頁不同尺寸、不同的壓縮格式。黑白的壓縮算法常見為CCITT 4/6,無損壓縮,不支持灰度和彩色;彩色的常見壓縮算法為LZW無損壓縮,對文字和矢量圖形的效果不錯,但對於照片的壓縮率很差。最新的tif格式也支持jpeg有損壓縮和zip壓縮,不過很多舊版軟件不支持,如XP圖片查看器等。
最初為了圖方便,我使用了windows自帶的gdi+來讀寫tif,但后來發現幾個無法解決的問題
1.在32位系統上,打開2G以上的tif文件失敗;
2.被某些應用(如splwow64)調用時,總是失敗;
無奈之下只好換方案,使用libtiff庫。本來想下載編譯好的dll文件,不過都沒64位的,干脆自己編一下吧。
【編譯】
libtiff引用了jpeg,zip庫,一開始我用不着這兩個,就把他們去掉了:
1.libtiff\makefile.vc 注釋tif_jpeg/pixarlog/zip三行
2.tiffconf.h 注釋 JPEG_SUPPORT,PIXARLOG,ZIP三行
編譯64位版本是我用了VS2008 x64 win64 command prompt tools,運行 nmake /f makefile.vc 但編譯出來的dll依賴mfc90.dll等文件,最好改為靜態鏈接: nmake.opt OPTFLAGS, MD->MT 另外可以不生成pdb: nmake.opt- LD=link /nologo 加上 /pdb:none
32位也可以用VC6編譯:VC6\VC98\Bin\VCVARS32.bat 命令同上
編譯生成libtiff.dll, libtif_i.lib(dynamic), libtiff.lib(static) 不過我還是建了一個VC工程來編譯,更方便一些。 ==========================================================
如果要支持jpeg編碼,請到http://www.ijg.org/ 下載源碼包jpegsrxc
copy jconfig.vc jconfig.h ; nmake /f makefile.vc libjpeg.lib ; 默認生成libjpeg.lib靜態庫 在libtiff\makefile.vc,tiffconf.h開啟jpeg行,nmake.opt中打開jpeg項目並寫入路徑 64位編譯時,要在makefile.vc CFLAGS=..加上/Ox /MT /GX /W3 靜態鏈接MFC.
==========================================================
用VC編譯應用程序沒問題,但在WDK編譯驅動時遇到錯誤[unresolved external symbol __imp__TIFFOpen@4 referenced].這里TIFFOpen后面為什么有個@4?看了下Lib文件里有這些函數但沒有@序號。這是因為libtiff都是c函數,默認是cdecl調用方式,dll輸出函數不帶@序號。而WDK編譯驅動時默認是stdcall(/Gz)編譯方式,鏈接時就找不到了。這里需要把libtiff里的函數都加上__stdcall修飾再重編譯,不過改動比較多,也可以加上/Gz編譯選項,或在VC的Code Generation/Advanced - calling convention 選為stdcall(/Gz)編譯,注意libjpeg也要重新編譯。如果以stdcall編譯后,頭文件tiffio.h中的函數也必須加上__stdcall修飾。
【使用】
#include "tiffio.h"
int main()
{
int i,nret,nw,nh,nbpp,npage=1;
TIFF* pTif = TIFFOpen("d:\\1.tif", "r");
TIFFSetDirectory(pTif, 1); // 跳到指定的頁數1
nret = TIFFGetField(pTif, TIFFTAG_IMAGEWIDTH, &nw); // 獲取圖像長、寬
nret = TIFFGetField(pTif, TIFFTAG_IMAGELENGTH, &nh);
npage = TIFFNumberOfDirectories(pTif); // 讀取頁數
TIFFClose(pTif);
}
錯誤和警告信息
libtiff使用CallBack方式顯示錯誤和警告。定義如下函數
void TIFFErrorProc(const char* pModule, const char* pFormat, va_list pArg)
{
char szMsg[512];
vsprintf(szMsg, pFormat, pArg);
printf("tifferr-%s: %s", pModule, szMsg);
}
//然后設置為錯誤和警告處理函數,最好分開兩個。
TIFFSetWarningHandler(TIFFWarnProc);
TIFFSetErrorHandler(TIFFErrorProc);
//其中pModule是函數模塊,如TiffEncode; pFormat是帶參數的信息如"height should be %d", pArg是可變參數表
Directories 多頁
一個tif文件可以包含多頁,每頁的寬高大小都可以不同,在libtiff中稱為Directories.
獲取頁數 npage = TIFFNumberOfDirectories(TIFF*);
跳到指定頁數 TIFFSetDirectory(TIFF*, tdir_t);
寫入一頁 TIFFWriteDirectory(TIFF*);
tif圖像的三種壓縮和組織形式scanline,strip,tile
scanline:每行圖像壓縮,只支持ccitt等算法,不支持lzw,jpeg等
strip:圖像分為幾個橫條壓縮,
tile: 圖像分為若干個正方形塊進行壓縮
與其他圖像格式轉換的問題
彩色tif內的顏色順序為rgb,在bmp內的順序為bgr,兩者需要翻轉
黑白tif可調用TIFFSetField(pTif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK/WHITE)來指定0-1為黑、白,不過有些軟件不支持。
tif內的每行數據為1字節對齊,bmp為4字節對齊 使用jpeg算法壓縮時,strip的高度必須為8的倍數
最后附上tif-bmp轉換的代碼例子
void SaveBmpFile(LPTSTR pszBmp, int nW, int nH, int nBpp, LPBYTE pBuf)
{
BITMAPFILEHEADER bfh={0};
BITMAPINFOHEADER bih={0};
DWORD pal[256]={0,0XFFFFFF};
int nLineByte,nwb,y;
bfh.bfType = 'MB';
bfh.bfSize = sizeof bfh;
bfh.bfOffBits = sizeof(bfh)+sizeof(bih);
if(nBpp == 1)
bfh.bfOffBits += 8;
bih.biSize = sizeof bih;
bih.biWidth = nW;
bih.biHeight = nH;
bih.biBitCount = nBpp;
bih.biPlanes = 1;
nwb = (nW*nBpp+31)/32*4; //bmp 4 bytes align
bih.biSizeImage = nwb*nH;
CFile fBmp(pszBmp, CFile::modeCreate|CFile::modeWrite);
fBmp.Write(&bfh, sizeof bfh);
fBmp.Write(&bih, sizeof bih);
if(nBpp == 1)
fBmp.Write(pal, 8);
nLineByte = (nW*nBpp+7)/8; //tif 1 bytes align
LPBYTE pLine = pBuf+nLineByte*(nH-1);
for(y=0; y<nH; y++)
{
fBmp.Write(pLine, nLineByte);
if(nwb > nLineByte)
fBmp.Write(pal, nwb-nLineByte);
pLine -= nLineByte;
}
fBmp.Close();
}
void SaveTif2Bmp(LPTSTR pszTif, LPTSTR pszBmp)
{
TIFF* pTif = TIFFOpen(pszTif, "r");
if(!pTif)
return;
int i,nret,nw,nh,npage,nrps;
unsigned short nComp, nPho, nBps,nSpp;
TIFFSetErrorHandler(TIFFErrorHandler);
nret = TIFFGetField(pTif, TIFFTAG_IMAGEWIDTH, &nw);
nret = TIFFGetField(pTif, TIFFTAG_IMAGELENGTH, &nh);
nret = TIFFGetField(pTif, TIFFTAG_COMPRESSION, &nComp);
nret = TIFFGetField(pTif, TIFFTAG_PHOTOMETRIC, &nPho);
nret = TIFFGetField(pTif, TIFFTAG_BITSPERSAMPLE, &nBps);
nret = TIFFGetField(pTif, TIFFTAG_SAMPLESPERPIXEL, &nSpp);
nret = TIFFGetField(pTif, TIFFTAG_ROWSPERSTRIP, &nrps);
npage = TIFFNumberOfDirectories(pTif);
int nSize = TIFFStripSize(pTif);
int nStrip = TIFFNumberOfStrips(pTif);
uint32* bc; // wrong size??
nret = TIFFGetField(pTif, TIFFTAG_STRIPBYTECOUNTS, &bc);
int nwb = (nw*nBps*nSpp+31)/32*4; // 4-byte align for bmp
LPBYTE pBufBmp = new BYTE[nwb*nh];
//uint32 stripsize = bc[0];
//tdata_t buf = _TIFFmalloc(nSize);//stripsize);
LPBYTE pStripBmp = pBufBmp;
for (i=0; i<nStrip; i++)
{
nret = TIFFReadEncodedStrip(pTif, i, pStripBmp, nSize);
int nHStrip = nret/(nw*nBps*nSpp/8);
pStripBmp += nHStrip * nwb;
}
//uint32* raster = (uint32*) _TIFFmalloc(nw*nh*sizeof(uint32));
//TIFFReadRGBAImage(tif, nw, nh, raster, 0);
SaveBmpFile(pszBmp, nw,nh, nBps*nSpp, (LPBYTE)pBufBmp);
//_TIFFfree(buf);
delete [] pBufBmp;
TIFFClose(pTif);
}
// pszBmp: d:\1.bmp|d:\2.bmp
void SaveBmp2Tif(LPTSTR pszBmp, LPTSTR pszTif)
{
BITMAPFILEHEADER bfh;
BITMAPINFOHEADER bih;
DWORD pal[256];
int i,x,nwb;
LPBYTE pBuf,pLine;
//LPDWORD pdw;
LPTSTR pBmpFile = pszBmp, pc;
TIFF *pTif = TIFFOpen(pszTif, "w+");
if(!pTif) return;
pc = strchr(pszBmp, '|');
while (pBmpFile)
{
if(pc) *pc = 0;
CFile fBmp(pBmpFile, CFile::modeRead);
fBmp.Read(&bfh, sizeof(bfh));
fBmp.Read(&bih, sizeof(bih));
if (bih.biBitCount == 1)
{
fBmp.Read(pal, 2*4);
nwb = (bih.biWidth+31)/32 * 4;
} else if (bih.biBitCount == 24)
{
nwb = (bih.biWidth*3+3)/4*4;
}
pBuf = (LPBYTE)malloc(nwb*bih.biHeight);
fBmp.Read(pBuf, nwb*bih.biHeight);
fBmp.Close();
TIFFSetField(pTif, TIFFTAG_IMAGEWIDTH, bih.biWidth);
TIFFSetField(pTif, TIFFTAG_IMAGELENGTH, bih.biHeight);
//TIFFSetField(pTif, TIFFTAG_FILLORDER, FILLORDER_MSB2LSB);
TIFFSetField(pTif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); //single image plane
TIFFSetField(pTif, TIFFTAG_XRESOLUTION, 300.0); // must be double
TIFFSetField(pTif, TIFFTAG_YRESOLUTION, 300.0);
TIFFSetField(pTif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH);
TIFFSetField(pTif, TIFFTAG_IMAGEDESCRIPTION, "PaperSize=2100x2970;");
TIFFSetField(pTif, TIFFTAG_DOCUMENTNAME, "tif-test by chaos;");
if (bih.biBitCount == 1)
{
TIFFSetField(pTif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK); // 0=black
TIFFSetField(pTif, TIFFTAG_COMPRESSION, COMPRESSION_CCITT_T6);//
TIFFSetField(pTif, TIFFTAG_BITSPERSAMPLE, 1);
TIFFSetField(pTif, TIFFTAG_SAMPLESPERPIXEL, 1);
TIFFSetField(pTif, TIFFTAG_ROWSPERSTRIP, bih.biHeight);
pLine = pBuf + nwb*(bih.biHeight-1);
for (i=0; i<bih.biHeight; i++)
{
//pdw = (LPDWORD)pLine; // invert color
//for(x=0; x<nwb/4; x++)
//{
// *pdw = ~(*pdw);
// pdw++;
//}
TIFFWriteScanline(pTif, pLine, i);
pLine -= nwb;
}
} else if (bih.biBitCount == 24)
{
TIFFSetField(pTif, TIFFTAG_COMPRESSION, COMPRESSION_JPEG);//LZW);//
TIFFSetField(pTif, TIFFTAG_BITSPERSAMPLE, 8);
TIFFSetField(pTif, TIFFTAG_SAMPLESPERPIXEL, 3);
TIFFSetField(pTif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
int nJpegQuality = 75;
TIFFSetField(pTif, TIFFTAG_JPEGQUALITY, nJpegQuality);
TIFFSetField(pTif, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB);
// bgr->rgb
BYTE btmp, *pDot;
pLine = pBuf + nwb*(bih.biHeight-1);
for (i=0; i<bih.biHeight; i++)
{
pDot = pLine;
for (x=0; x<bih.biWidth; x++)
{
btmp = *pDot;
*pDot = pDot[2];
pDot[2] = btmp;
pDot += 3;
}
pLine -= nwb;
}
TIFFSetField(pTif, TIFFTAG_ROWSPERSTRIP, bih.biHeight);
TIFFWriteEncodedStrip(pTif, 0, pBuf, nwb*bih.biHeight);
//Sleep(100);
//TIFFWriteEncodedStrip(pTif, 1, pBuf+nwb*(bih.biHeight/2), nwb*(bih.biHeight/2));
}
TIFFWriteDirectory(pTif);
free(pBuf);
if(pc)
{
pBmpFile = pc+1;
pc = strchr(pBmpFile, '|');
} else
pBmpFile = NULL;
}
TIFFClose(pTif);
}
