tif文件格式概览
tif图由四个部分组成:
1、图像文件头(image file header简称ifh):
图一 ifh结构描述
ifh数据结构包含3个成员共计8个字节,byte order成员可能是“mm”(0x4d4d)或“ii”(0x4949),0x4d4d表示该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):
tag值
十进制 16进制 说明
256 100 图像宽度
257 101 图像高度
258 102 bitspersample,对于rgb是8,8,8,通过偏移来表示
259 103 压缩方式(1/2/32773),1为不压缩
262 106 光度滴定,对于rgb图,该值是2
273 111 带的偏移,每个带是不同的
277 115 每像素的颜色成分,对于rgb这个值一般是3,如果 需要 其他有extrasamples标志
278 116 每带的行数
279 117 每带中压缩之后的数据字节常长度
284 11c 每个分量(r,g,b)是怎么存储的,1表示按照rgbrgb的顺序来存储
296 128 xresolution和yresolution的单位
共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。以下的示例说明遵循了这一观察结果。
三 一个tiff文件格式分析
四、实战
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 {
ptemp = pde i;
if(256 == ptemp->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;
}