最近的一個項目用到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);
}
 
        
