TIFTIFTIFTIF 格式图像文件初探
作者:三辰卡通集团技术部 刘晖
一、前言
TIF 是可扩充标记的文件,所以理论上是不可能编写一个能识别所有类别
TIF 格式的读写程序,这里只是给大家提供一个思路, 将 TIF 图像转换成 BMP
图像后就可以使用 Windows 提供的 API 函数对其进行编辑并方便直观地显示出
来,测试代码中包含 了一个用于测试的 TIF 图。本代码也只能针对这一种类别
的 TIF 图进行操作,如何对其他类别的 TIF 图进行编码,读完本例子自然就能融
会 贯通了。
二、TIF 图像格式概览
TIF 图由四个部分组成:
1、图像文件头( Image File Header 简称 IFH):
图一 IFH 结构描述
IFH 数据结构包含 3 个成员共计 8 个字节,Byte order 成员可能是
“MM”(0x4d4d)或 “II”(0x4949),0x4d4d
表
关于同志近三年现实表现材料材料类招标技术评分表图表与交易pdf视力表打印pdf用图表说话 pdf
示该 TIFF 图是摩托罗拉整数格式
0x4949 表示该图是 Intel 整数格式;Version 成员总是包含十进制 42(0x2a),它
用于进一步校验该文件是否为 TIF 格式,42 这个数并不是一般人 想象中的那样
认为是 tif 软件的版本,实际上,42 这个数大概永远不会变化;第三个成员是 IFD
(接下来要说的第二个数据结构)相对文件开始 处的偏移量。
2、图像文件目录( Image File Directory 简称 IFD):
图二 IFD 及 DE 结构描述
IFD 是 TIF 图中最重要的数据结构,它包含了一个 TIF 文件中最重要的信息,
一个 TIF 图可能有多个 IFD,这说明文件中有多个图像,每个 IFD 标识 1 个图像
的基本属性。 IFD 结构中包含了三类成员,Directory Entry Count 指出该结构
里面有多少个目录入口;接下来就是 N 个线性排列的 DE 序列,数量不定(这就
是 为什么称 TIF 格式文件为可扩充标记的文件,甚至用户可以添加自定义的标
记属性),每个 DE 标识了图像的某一个属性;最后就是一个偏移量, 标识下
一个文件目录相对于文件开始处的位置,当然,如果该 TIF 文件只包含了一幅图
像,那么就只有一个 IFD,显然,这个偏移量就等于 0;
3、目录入口(Directory Entry 简称 DE):
共 12 个字节,见图二。简单说,一个 DE 就是一幅图像的某一个属性。例
如图像的大小、分辨率、是否压缩、像素的行列数、一个像素由几位 表示(1
位代表黑白两色,8 位代表 256 色等等)等。其中: tag 成员是该属性的编号,
在图像文件目录中,它是按照升序排列的。我们可以通过读 这些编号,然后到
TIF 格式官方白皮书中查找相应的含义。属性是用数据来表示的,那么 type 就
是代表着该数据的类型,TIF 官方指定的有 5 种数据类型。 type=1 就是 BYTE
类型(8 位无标记整数)、type=2 是 ASCII 类型(7 位 ASCII 码加 1 位二进制 0)、
type=3 是 SHORT 类型(16 位无标记整数)、 type=4 是 LONG 类型(32 位无
标记整数)、type=5 是 RATIONAL 类型(2 个 LONG,第一个是分子,第二个是
分母)。 length 成员是数据的数量而不是数据类型的长度。 第 4 个成员
valueOffset 很重要,它是 tag 标识的属性代表的变量值相对文件开始处的偏移
量。如果变量值占用的空间小于 4 个字节,那么该值就存放在 valueOffset 中即
可,没必要再另外指向一个地方了。
4、图像数据 本例提供的图像是基于 256 灰度级的,即一个字节代表一个像素
点,它是 0x00~0xff 区间中 256 个灰度级的任意一个整数。通过使用 UltraEdit
工具观察, 我们发现该图像文件的组织形式是: IFH--数据 --IFD。以下的示例
说明遵循了这一观察结果。
三、实战
1、VC 创建一个 MFC AppWizard(exe)
工程
路基工程安全技术交底工程项目施工成本控制工程量增项单年度零星工程技术标正投影法基本原理
取名 TiffTest,选择单文档程序。
2、添加 TiffStruct.h 文件,定义 IFH 和 DE 结构(参考前面的结构描述),用来
接收读 TIF 文件的信息。
#ifndef _TIFFSTRUCT_
#define _TIFFSTRUCT_
typedef struct tagIMAGEFILEHEADER
{
WORD byteOrder;
WORD version;
DWORD offsetToIFD;
}IFH;
typedef struct tagDIRECTORYENTRY
{
WORD tag;
WORD type;
DWORD length;
DWORD valueOffset;
}DE;
#endif
3、在文档类中添加 4 个公有变量,并将其初始化为 0。在 TiffTestDoc.cpp 中
#include "TiffStruct.h"
DWORD m_dwBmSize; //图象的数据部分的大小
CPalette m_palDIB; //BMP 图象调色板
HANDLE m_hDIB; //BMP 图象内存块句柄
CSize m_sizeDoc; //图象的长和宽
4、在文档类的 OnOpenDocument 函数中定义局部工具变量并读文件
DWORD dwFileLength = 0;
CString strTemp = _T("");
WORD wDECount = 0;
BYTE* pDIB = NULL;
int i = 0;
IFH ifh;
ZeroMemory(&ifh, sizeof(IFH));
CFile file;
CFileException fe;
if(0 == file.Open(lpszPathName, CFile::modeRead |
CFile::shareDenyWrite, &fe))
{
AfxMessageBox("打开文件失败");
return FALSE;
}
dwFileLength = file.GetLength();
读 IFH 文件头
if(sizeof(IFH) != file.Read(&ifh, sizeof(IFH)))
{
AfxMessageBox("读 TIF 文件头失败");
return FALSE;
}
if(0x2a != ifh.version)
{
AfxMessageBox("该文件不是 TIF 格式,读文件失败");
return FALSE;
}
if(0x4949 != ifh.byteOrder)
{
AfxMessageBox("该 TIF 文件不是 IBMPC 字节序,读文
件失败");
return FALSE;
}
file.Seek(ifh.offsetToIFD, CFile::begin);//将文件指针定
位到 IFD
读文件有多少个目录入口
if(2 != file.Read(&wDECount, 2))
{
AfxMessageBox("无法获得 TIF 文件目录入口数量");
return FALSE;
}
strTemp.Format("该 TIF 文件有%d 个目录入口", wDECount);
AfxMessageBox(strTemp);
创建 DE 数组,接收信息,数组中有 wDECount 个元素
DE* pde = new DE[wDECount];
DE* pTemp = pde;
memset(pde, 0, sizeof(DE)*wDECount);
if(sizeof(DE)*wDECount != file.ReadHuge(pde,
sizeof(DE)*wDECount))
{
AfxMessageBox("读图象文件目录失败");
delete []pde;
return FALSE;
}
显示图像文件目录信息
for(i=0; itag, i, pTemp->type, i, pTemp->length, i,
pTemp->valueOffset);
AfxMessageBox(strTemp);
}
把图像的大小和图像数据的容量保存到成员变量中
for(i=0; i
tag) //tag为 256的目录入口
中的变量标识了图象宽度
{
m_sizeDoc.cx = pTemp->valueOffset;
}
if(257 == pTemp->tag) //图象高度
{
m_sizeDoc.cy = pTemp->valueOffset;
}
if(273 == pTemp->tag) //计算图象数据占用字
节数
{
//m_dwBmSize = pTemp->valueOffset -
sizeof(IFH);
//或者把 tag=256 的 valueOffset 乘以 tag=257
的 valueOffset
m_dwBmSize = m_sizeDoc.cx * m_sizeDoc.cy;
}
}
在文档类中创建一个成员工具函数 CreateBmpBuffer,申请全局内存块以存放
BMP 文件结构数据
BOOL CTiffTestDoc::CreateBmpBuffer()
{
//申请 BMP 内存块
m_hDIB = GlobalAlloc(GMEM_MOVEABLE |
GMEM_ZEROINIT,
sizeof(BITMAPFILEHEADER) +
sizeof(BITMAPINFOHEADER) +
256*sizeof(RGBQUAD) +
m_dwBmSize);
if(NULL == m_hDIB)
{
AfxMessageBox("申请 BMP 内存块失败");
return FALSE;
}
else
{
return TRUE;
}
}
回到 OnOpenDocument 成员函数中调用工具函数并获得全局内存块指针
//构造 BMP 图象内存块
if(!CreateBmpBuffer())
{
AfxMessageBox("构造 BMP 图象内存块失败");
delete []pde;
return FALSE;
}
//获得 BMP 内存块指针
pDIB = (BYTE*)GlobalLock(m_hDIB);
if(NULL == pDIB)
{
AfxMessageBox("获得 BMP 内存块指针失败");
GlobalUnlock(m_hDIB);
delete []pde;
return FALSE;
}
以下是将图像信息填充到 BMP 内存块中,网上介绍 BMP 格式的文章很多,
这里就不详述了。因测试图像数据表达的是 0x00-0xff 灰度,正好和 BMP 文件调
色板索引值巧合。 故在代码中直接把图像数据信息当成索引即可,减少了编码
复杂度。注:BMP 文件中图像数据的第一行代表的是最终显示光栅的最后一行,
所以在数据排列中要颠倒过来。
//构造 BITMAPFILEHEADER 并复制到 BMP 内存块
BITMAPFILEHEADER bmfHdr;
memset(&bmfHdr, 0, sizeof(BITMAPFILEHEADER));
bmfHdr.bfOffBits = sizeof(BITMAPFILEHEADER) +
sizeof(BITMAPINFOHEADER) +
256*sizeof(RGBQUAD);
bmfHdr.bfReserved1 = 0;
bmfHdr.bfReserved2 = 0;
bmfHdr.bfSize = bmfHdr.bfOffBits + m_dwBmSize;
bmfHdr.bfType = 0x4d42;
memmove(pDIB, &bmfHdr, sizeof(BITMAPFILEHEADER));
//构造 BITMAPINFOHEADER 并复制到 BMP 内存块
BITMAPINFOHEADER bmiHdr;
memset(&bmiHdr, 0, sizeof(BITMAPINFOHEADER));
bmiHdr.biBitCount = 8;
bmiHdr.biClrImportant = 0;
bmiHdr.biClrUsed = 0;
bmiHdr.biCompression = 0;
bmiHdr.biHeight = m_sizeDoc.cy;
bmiHdr.biPlanes = 1;
bmiHdr.biSize = sizeof(BITMAPINFOHEADER);
bmiHdr.biSizeImage = 0;
bmiHdr.biWidth = m_sizeDoc.cx;
bmiHdr.biXPelsPerMeter = 2834;
bmiHdr.biYPelsPerMeter = 2834;
memmove((BITMAPFILEHEADER*)pDIB + 1, &bmiHdr,
sizeof(BITMAPINFOHEADER));
//构造 256 个 RGBQUAD 并复制到 BMP 内存块
RGBQUAD* pRgbQuad = (RGBQUAD*)(pDIB +
sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER));
RGBQUAD* pOldQuad = pRgbQuad;
RGBQUAD rgbQuad;
memset(&rgbQuad, 0, sizeof(RGBQUAD));
for(i=0; i<256; i++)
{
rgbQuad.rgbBlue = i;
rgbQuad.rgbGreen = i;
rgbQuad.rgbRed = i;
rgbQuad.rgbReserved = 0;
pRgbQuad = pOldQuad + i;
memmove(pRgbQuad, &rgbQuad, sizeof(RGBQUAD));
}
//填充所有像素数据, 颠倒图象数据从最后一行开始读起
int j = 0;
for(i=m_sizeDoc.cy-1; i>=0; i--)
{
file.Seek(sizeof(IFH) + i*m_sizeDoc.cx,
CFile::begin);
file.Read((BYTE*)(pRgbQuad + 1) + j*m_sizeDoc.cx,
m_sizeDoc.cx);
j++;
}
初始化 BMP 调色板,为显示 BMP 文件做准备
//初始化专用调色板
BYTE buf[2+2+4*256];
LOGPALETTE* pPal = (LOGPALETTE*)buf;
pPal->palVersion = 0x300;
pPal->palNumEntries = 256;
for(i=0; i<255; i++)
{
pPal->palPalEntry[i].peBlue = i;
pPal->palPalEntry[i].peFlags = 0;
pPal->palPalEntry[i].peGreen = i;
pPal->palPalEntry[i].peRed = i;
}
m_palDIB.CreatePalette(pPal);
最后是 OnOpenDocument 成员函数返回前的清理工作
GlobalUnlock(m_hDIB);
delete []pde;
return TRUE;
至此,TIF 文件信息已转换为 BMP 图像并保存在全局内存块中了,接下来就
可以在 OnDraw 中调用 WinAPI 函数 StretchDIBits 来显示它。
if(NULL == pDoc->m_hDIB)
{
return;
}
HDC hdc = pDC->m_hDC;
BYTE* pBuf = (BYTE*)GlobalLock(pDoc->m_hDIB);
pBuf += sizeof(BITMAPFILEHEADER);
BYTE* pData = pBuf + sizeof(BITMAPINFOHEADER) +
256*sizeof(RGBQUAD);
CPalette* pOldPal = pDC->SelectPalette(&pDoc->m_palDIB,
TRUE);
pDC->RealizePalette();
::SetStretchBltMode(hdc, COLORONCOLOR);
::StretchDIBits(hdc, 10, 10, pDoc->m_sizeDoc.cx,
pDoc->m_sizeDoc.cy,
0, 0, pDoc->m_sizeDoc.cx, pDoc->m_sizeDoc.cy,
pData,
(BITMAPINFO*)pBuf, DIB_RGB_COLORS, SRCCOPY);
pDC->SelectPalette(pOldPal, FALSE);
GlobalUnlock(pDoc->m_hDIB);
最后别忘了在文档类析构函数~CTiffTestDoc 中销毁全局内存块
if(NULL != m_hDIB)
{
GlobalFree(m_hDIB);
m_hDIB = NULL;
}
四、结束语
测试用的 TIF 文件在工程目录下,文件名为 SSS00.TIF。本人学习图形图像
编程不久,文章难免挂一漏万,请高手指教。