在使用libtiff的应用程序中,TIFFGetVersion例程将返回一个指向包含软件版本信息的字符串的指针。包含文件<tiffio.h>的库包含一个C预处理器定义的TIFFLIB_VERSION,可用于在编译时检查库版本兼容性。
libtiff通过使用一组C类型概念定义了一个可移植的编程接口。在文件tiffo.h和tiffio.h中定义的这些定义将libtiff API与底层机器的特征隔离开来。为了确保可移植的代码和正确的操作,使用libtiff的应用程序应该使用typedefs,并遵循库API的函数原型。
Libtiff使用一组特定于机器的例程来管理动态分配的内存。_TIFFmalloc、_TIFFrealloc和_TIFFfree模拟普通的ANSI C例程。任何要传递到库中的动态分配内存都应该使用这些接口进行分配,以确保具有分段体系结构的机器的指针兼容性。(在32位UNIX系统中,这些例程只调用C库中的普通malloc、realloc和自由例程。)
为了处理分段指针问题,libtiff还提供了_TIFFmemcpy、_TIFFmemset和_TIFFmemmove例程,这些例程模拟等效的ANSI C例程,但它们是用于通过_TIFFmalloc和_TIFFrealloc分配的内存。
Libtiff在从函数调用返回时,通过返回一个无效/错误的值来处理大多数错误。库还可以生成各种诊断消息。所有错误消息都被定向到单个全局错误处理程序例程,该例程可以通过调用TIFFSetErrorHandler来指定。同样,警告消息被定向到单个处理程序例程,该例程可以通过调用TIFFSetWarningHandler来指定。
这个库是仿照普通的UNIX stdio库创建的。例如,要从现有的TIFF图像中读取文件,必须先打开:
#include "tiffio.h"
main()
{
TIFF* tif = TIFFOpen("foo.tif", "r");
... do stuff ...
TIFFClose(tif);
}
TIFFOpen返回的句柄是不透明的,即应用程序不允许知道它的内容。对该文件的所有后续库调用都必须将句柄作为参数传递。要创建或覆盖一个TIFF图像,也要打开文件,但带有一个“w”参数:
#include "tiffio.h"
main()
{
TIFF* tif = TIFFOpen("foo.tif", "w");
... do stuff ...
TIFFClose(tif);
}
如果文件已经存在,则首先将其截断为零长度。
注意,不像stdio库TIFF图像文件可能不能同时打开读写;不支持更改TIFF文件的内容。libtiff缓冲与写入有效TIFF图像相关的许多信息。因此,在写入TIFF图像时,必须始终调用TIFFClose或TIFFFlush将任何缓冲信息刷新到文件中。注意,如果调用TIFFClose,则不需要调用TIFFFlush。
TIFF支持在一个文件中存储多个图像。每个图像都有一个关联的数据结构,称为目录,其中包含有关图像数据格式和内容的所有信息。一个文件中的图像通常是相关的,但他们不需要;将彩色图像与黑白图像存储在一起是完全可以的。但是请注意,虽然图像可能是相关的,但它们的目录不是。也就是说,每个目录都是独立的;它们不需要读取不相关的目录来正确解释图像的内容。
Libtiff提供了几个读写目录的例程。在正常使用中,不需要显式地读或写一个目录:库在打开并读取文件时自动读取第一个目录,并且在写入时自动累积并写入要写入的目录信息(假设调用了TIFFClose或TIFFFlush)。
对于打开供读取的文件,TIFFSetDirectory例程可用于选择任意目录;目录通过编号引用,编号从0开始。否则,TIFFReadDirectory和TIFFWriteDirectory例程可以用于对目录的顺序访问。例如,要计算一个文件中的目录数,可以使用以下代码:
#include "tiffio.h"
main(int argc, char* argv[])
{
TIFF* tif = TIFFOpen(argv[1], "r");
if (tif) {
int dircount = 0;
do {
dircount++;
} while (TIFFReadDirectory(tif));
printf("%d directories in %s\n", dircount, argv[1]);
TIFFClose(tif);
}
exit(0);
}
最后,请注意有几个用于查询打开文件的目录状态的例程:TIFFCurrentDirectory返回当前目录的索引,而TIFFLastDirectory返回当前目录是否是文件中的最后一个目录的指示。还有一个例程TIFFPrintDirectory,可以调用它来打印当前目录内容的格式化描述;详细信息请参阅手册页。
与图像相关的信息,如图像的宽度和高度,样本数量,方向,色度信息等存储在每个图像目录的字段或标签中。标签由一个数字标识,这个数字通常是在Aldus(现在是Adobe)公司注册的值。但是要注意,有些供应商使用未注册的标签编写TIFF图像;在这种情况下,解释它们的内容通常是浪费时间。
Libtiff一次性读取目录的所有内容,并将磁盘上的信息转换为适当的内存形式。虽然TIFF规范允许在文件中定义和使用任意一组标记,但库只理解有限的一组标记。文件中遇到的任何未知标记都将被忽略。有一种机制可以在不修改库本身的情况下扩展库处理的标记集;这在别处有描述。
libtiff提供了两个获取和设置标记值的接口:TIFFGetField和TIFFSetField。这些例程使用变量参数列表样式的接口,通过单个函数接口传递不同类型的参数。get接口接受一个或多个指向将要返回标记值的内存位置的指针,并根据所请求的标记是否在目录中定义返回1或0。set接口按引用或按值接受标记值。TIFF规范定义了一些标记的默认值。要获取标签的值,或者它的默认值(如果它未定义的话),可以使用tiffgetfielddefaults接口。
标记get和set例程的手册页指定了库支持的每个标记所需的确切数据类型和调用约定。
Libtiff支持多种数据压缩方案。在正常操作中,当设置TIFF compression标签时,通过打开文件进行读取或在写入时设置标签,自动使用压缩方案。压缩方案由称为编解码器的软件模块实现,编解码器和编码器例程挂钩到核心库的i/o支持。除与库绑定的编解码器外,还可以注册用于TIFFRegisterCODEC例程。此接口还可用于覆盖压缩方案的核心库实现。
TIFF规范指出,并且总是指出,正确的TIFF阅读器必须以大端字节顺序和小端字节顺序处理图像。Libtiff符合这一点。因此没有意味着迫使一个特定字节顺序的数据写入一个TIFF图像文件(数据写在本机的主机CPU除非附加到现有文件,在这种情况下,它被写在文件)中指定的字节顺序。
TIFF规范要求除了8字节的头之外的所有信息都可以放在文件的任何地方。特别是,在图像数据本身之后写入目录信息是完全合法的。因此,TIFF本质上不适合通过面向流的机制(如UNIX管道)。要求数据以特定顺序组织在文件中的软件(例如,目录信息先于图像数据)不正确地支持TIFF。Libtiff没有提供控制文件中数据位置的机制;图像数据通常在目录信息之前写入。
libtiff提供了从TIFF文件读取图像数据的高级接口。该接口处理各种TIFF文件的数据组织和格式的细节;至少是人们通常会遇到的大部分文件。默认情况下,图像数据返回为打包成32位字的ABGR像素(每个样本8位)。可以读取矩形光栅或在中间水平上截取数据,并以更适合该应用程序的格式装入存储器。这个库处理存储在磁盘上的数据格式的所有细节,在大多数情况下,如果需要任何颜色空间转换:二层到RGB,灰度到RGB, CMYK到RGB, YCbCr到RGB, 16位采样到8位采样,关联/不关联alpha,等等。
有两种方法可以使用这个接口读取图像数据。如果所有的数据都存储在内存中,并且一次操作,那么例程TIFFReadRGBAImage可以使用:
#include "tiffio.h"
main(int argc, char* argv[])
{
TIFF* tif = TIFFOpen(argv[1], "r");
if (tif) {
uint32 w, h;
size_t npixels;
uint32* raster;
TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &w);
TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &h);
npixels = w * h;
raster = (uint32*) _TIFFmalloc(npixels * sizeof (uint32));
if (raster != NULL) {
if (TIFFReadRGBAImage(tif, w, h, raster, 0)) {
...process raster data...
}
_TIFFfree(raster);
}
TIFFClose(tif);
}
exit(0);
}
注意上面的_TIFFmalloc是用来为传递给TIFFReadRGBAImage的光栅分配内存的;这对于确保在具有分段架构的机器上传递“适当类型的内存”是很重要的。或者,TIFFReadRGBAImage可以用一个更低级的接口替换,该接口允许应用程序对这个读取过程有更多的控制。与上述等价的是:
#include "tiffio.h"
main(int argc, char* argv[])
{
TIFF* tif = TIFFOpen(argv[1], "r");
if (tif) {
TIFFRGBAImage img;
char emsg[1024];
if (TIFFRGBAImageBegin(&img, tif, 0, emsg)) {
size_t npixels;
uint32* raster;
npixels = img.width * img.height;
raster = (uint32*) _TIFFmalloc(npixels * sizeof (uint32));
if (raster != NULL) {
if (TIFFRGBAImageGet(&img, raster, img.width, img.height)) {
...process raster data...
}
_TIFFfree(raster);
}
TIFFRGBAImageEnd(&img);
} else
TIFFError(argv[1], emsg);
TIFFClose(tif);
}
exit(0);
}
然而,这种用法并没有利用更细粒度的控制。也就是说,通过使用这个接口,可以:
重复获取(和操作)图像而不打开和关闭文件
根据特定应用程序的需要(或直接写入数据)插入一种打包光栅像素数据的方法
处理核心库尚未处理的TIFF格式的插入方法
第一项意味着,例如,希望处理多个文件的图像查看器可以缓存解码信息,以加快显示TIFF图像所需的工作。
第二项是这个接口的主要原因。通过插入一个“put方法”(用于在光栅中打包像素数据的例程),可以共享理解如何处理TIFF的核心逻辑,同时将生成的像素打包成适合应用程序的格式。这种替代格式可能与库默认写的每个示例8位ABGR格式非常不同。例如,如果应用程序要在一个8位颜色地图显示上显示图像,那么put例程可能会获取数据并将其实时转换为显示的最佳颜色地图索引。
最后一项允许应用程序在不修改核心代码的情况下扩展库。通过覆盖所提供的代码,应用程序可能会添加对它所 需要的某些神秘的TIFF风格的支持,或者它可能会替换能够使用特定于应用程序/环境的信息进行优化的打包例程。在tools/sgigt.c中找到的TIFF图像查看器是一个使用TIFFRGBAImage支持的应用程序示例。
libtiff提供的最简单的接口是面向扫描线的接口,可用于读取以条带组织图像数据的TIFF图像(尝试使用此接口读取以块形式写入的数据将会产生错误)。扫描线是图像数据的一个高像素行,其宽度为图像的宽度。如果图像数据与打包在一起的样本一起存储,则返回数据;如果数据与分离的样本一起存储,则返回数据为独立样本的数组。scanline-oriented接口的主要限制,除了需要首先识别现有文件有一个合适的组织,是随机存取个人对线只能提供数据没有存储在一个压缩格式时,或者当地带的图像数据的行数设置为1 (RowsPerStrip是其中之一)。
为基于扫描线的i/o提供了两个例程:TIFFReadScanline和TIFFWriteScanline。例如,要读取一个假定以条带组织的文件的内容,可以使用以下方法:
#include "tiffio.h"
main()
{
TIFF* tif = TIFFOpen("myfile.tif", "r");
if (tif) {
uint32 imagelength;
tdata_t buf;
uint32 row;
TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &imagelength);
buf = _TIFFmalloc(TIFFScanlineSize(tif));
for (row = 0; row < imagelength; row++)
tiffreadscanline(tif, buf, row);
_tifffree(buf);
tiffclose(tif);
}
}
TIFFScanlineSize返回由TIFFReadScanline返回的已解码的扫描行中的字节数。但是请注意,如果文件是在不同的平面上创建的,那么上面的代码将只读取包含每个像素的第一个样本的数据;要处理这两种情况,可以使用以下方法:
#include "tiffio.h"
main()
{
TIFF* tif = TIFFOpen("myfile.tif", "r");
if (tif) {
uint32 imagelength;
tdata_t buf;
uint32 row;
TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &imagelength);
TIFFGetField(tif, TIFFTAG_PLANARCONFIG, &config);
buf = _TIFFmalloc(TIFFScanlineSize(tif));
if (config == PLANARCONFIG_CONTIG) {
for (row = 0; row < imagelength; row++)
tiffreadscanline(tif, buf, row);
} else if (config == planarconfig_separate) {
uint16 s, nsamples;
tiffgetfield(tif, tifftag_samplesperpixel, &nsamples);
for (s = 0; s < nsamples; s++)
for (row = 0; row < imagelength; row++)
tiffreadscanline(tif, buf, row, s);
}
_tifffree(buf);
tiffclose(tif);
}
}
但是请注意,如果下面的代码用于读取PLANARCONFIG_SEPARATE中的数据,…
for (row = 0; row < imagelength; row++)
for (s = 0; s < nsamples; s++)
tiffreadscanline(tif, buf, row, s);
…如果RowsPerStrip不是一个,那么问题就会出现,因为请求的扫描行顺序将要求随机访问条带内的数据(当条带被压缩时,库不支持这一点)。
该库提供的面向条带的接口提供对整个条带数据的访问。与面向扫描线的调用不同,数据可以被压缩或未压缩地读写。以条带(或平铺)级别访问数据通常是可取的,因为对于条带内的数据的随机访问没有复杂性。
一个按条带读取图像的简单例子是:
#include "tiffio.h"
main()
{
TIFF* tif = TIFFOpen("myfile.tif", "r");
if (tif) {
tdata_t buf;
tstrip_t strip;
buf = _TIFFmalloc(TIFFStripSize(tif));
for (strip = 0; strip < tiffnumberofstrips(tif); strip++)
tiffreadencodedstrip(tif, strip, buf, (tsize_t) -1);
_tifffree(buf);
tiffclose(tif);
}
}
请注意如何使用大小为-1的条带;TIFFReadEncodedStrip将在这种情况下计算适当的大小。上面的代码按照数据物理存储在文件中的顺序读取条带。如果存在多个样本,并且数据存储在PLANARCONFIG_SEPARATE中,那么保存第一个样本的所有数据条带将被读取,接着是保存第二个样本的条带,以此类推。最后,请注意图像中数据的最后一条可能比RowsPerStrip标记指定的行数要少。读取器不应该假定每个已解码的条带中包含完整的行集。以下是如何从文件中读取原始条带数据的示例:
#include "tiffio.h"
main()
{
TIFF* tif = TIFFOpen("myfile.tif", "r");
if (tif) {
tdata_t buf;
tstrip_t strip;
uint32* bc;
uint32 stripsize;
TIFFGetField(tif, TIFFTAG_STRIPBYTECOUNTS, &bc);
stripsize = bc[0];
buf = _TIFFmalloc(stripsize);
for (strip = 0; strip < tiffnumberofstrips(tif); strip++) {
if (bc[strip] > stripsize) {
buf = _TIFFrealloc(buf, bc[strip]);
stripsize = bc[strip];
}
TIFFReadRawStrip(tif, strip, buf, bc[strip]);
}
_TIFFfree(buf);
TIFFClose(tif);
}
}
如上所述,读取条带的顺序是按照它们在文件中的物理存储顺序;这可能与应用程序期望的逻辑顺序不同。
数据块可以以类似于条带的方式读取和写入。使用这个界面,图像被分解成一组矩形区域,这些区域的尺寸可能小于图像的宽和高。图像中的所有贴图都有相同的大小,并且贴图的宽度和长度都必须是16像素的倍数。在图像中,Tiles是从左到右,从上到下排列的。对于扫描线,样品可以连续包装,也可以分开包装。当分离时,样本的所有贴图都位于文件中。也就是说,样本0的所有贴图出现在样本1的贴图之前,以此类推。
Tiles和条也可以延伸到z的尺寸来形成体积。数据卷被组织为“片”。也就是说,一个片的所有数据都被分配了。数据以tile组织的卷也可以有tile深度,这样数据就可以以立方体组织。Tiles实际上有两个接口。一个类似于扫描线的界面,要读取平铺的图像,可以使用以下代码:
main()
{
TIFF* tif = TIFFOpen("myfile.tif", "r");
if (tif) {
uint32 imageWidth, imageLength;
uint32 tileWidth, tileLength;
uint32 x, y;
tdata_t buf;
TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &imageWidth);
TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &imageLength);
TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tileWidth);
TIFFGetField(tif, TIFFTAG_TILELENGTH, &tileLength);
buf = _TIFFmalloc(TIFFTileSize(tif));
for (y = 0; y < imagelength; y += tilelength)
for (x = 0; x < imagewidth; x += tilewidth)
tiffreadtile(tif, buf, x, y, 0);
_tifffree(buf);
tiffclose(tif);
}
}
(同样,我们假设样品是连续包装的。)
另一种方法是提供一个到低级数据的直接接口。tile可以用TIFFReadEncodedTile或TIFFReadRawTile读取,也可以用TIFFWriteEncodedTile或TIFFWriteRawTile写入。例如,要读取图像中的所有贴图:
#include "tiffio.h"
main()
{
TIFF* tif = TIFFOpen("myfile.tif", "r");
if (tif) {
tdata_t buf;
ttile_t tile;
buf = _TIFFmalloc(TIFFTileSize(tif));
for (tile = 0; tile < tiffnumberoftiles(tif); tile++)
tiffreadencodedtile(tif, tile, buf, (tsize_t) -1);
_tifffree(buf);
tiffclose(tif);
}
}
合理的脚本代码可以有效的提高工作效率,减少重复劳动。