TIFF 元数据格式规范和使用说明
读取图像 写入图像原生流元数据格式
本机图像元数据格式
读取图像
TIFF 图像由ImageReader
读取,它可以由其公共接口以及通过提供的 TIFFImageReadParam
控制。
颜色转换
如果源图像数据的光度类型为 CIE L*a*b* 或 YCbCr,并且目标颜色空间类型为 RGB,则源图像数据将使用内部颜色转换器自动转换为 RGB。
色彩空间
默认分配的原始颜色空间,即在没有用户提供的ImageTypeSpecifier
的情况下,将是以下应用中的第一个:
- 从 国际商会简介 元数据字段创建的颜色空间(如果它存在并且与图像数据布局兼容)。
- sRGB 如果图像是单色/双色(内部创建了两色图)。
- 如果图像是调色板颜色,则为 sRGB。
- 线性 RGB,如果图像每个像素有三个样本,光度类型为 CIE L*a*b*,或光度类型为 YCbCr,并且是notJPEG 压缩。
- A 默认 CMYK 色彩空间 如果图像具有光度类型 CMYK 并且每个像素有四个样本。
- 如果图像每个像素有一个或两个样本并且每个样本统一为 1、2、4、8、16 或 32 位,或者是浮点数,则为灰度。
- sRGB 如果图像每个像素有三个或四个样本并且每个样本统一为 1、2、4、8、16 或 32 位,或者是浮点数。
- 如果图像每个像素有四个以上的样本,并且所有波段的每个样本的位数相同并且是 8 的倍数,则为捏造的 通用色彩空间。
- 如果图像每个像素有一个或两个样本而不考虑每个样本的位数,则为灰度。
- sRGB 如果图像每个像素有三个或四个样本,而不管每个样本的位数。
- 一个捏造的,通用色彩空间 如果图像每个像素有四个以上的样本,而不管每个样本的位数。
- CMYK 到线性 RGB
R = (1 - K)*(1 - C) G = (1 - K)*(1 - M) B = (1 - K)*(1 - Y)
- 线性 RGB 到 CMYK
K = min{1 - R, 1 - G, 1 - B} if(K != 1) { C = (1 - R - K)/(1 - K) M = (1 - G - K)/(1 - K) Y = (1 - B - K)/(1 - K) } else { C = M = Y = 0 }
在无法推断出其他颜色空间时使用的通用颜色空间仅用于加载数据。它无意提供任何类型的准确转换。
如果已知数据位于上述方法未正确处理的颜色空间中,则应向读取器提供ImageTypeSpecifier
,并且应从对所讨论数据正确的颜色空间中导出。
国际刑事法院简介
如果图像元数据中包含 ICC 配置文件(BaselineTIFFTagSet
.TAG_ICC_PROFILE,标记号 34675),将尝试使用它来创建加载图像的色彩空间。如果数据布局是组件类型并且每个像素的样本数等于或大于 ICC 配置文件描述的组件数,则将使用它。如果不使用 ICC 配置文件,则将在 多于 描述的后续步骤之一中推断颜色空间。
如果由于某种原因嵌入的 ICC 配置文件没有自动使用,则可以按照以下过程手动使用它:
- 从
ImageReader.getImageMetadata
获取图像元数据 - 提取 ICC 配置文件字段及其值。
- 使用
ICC_Profile.getInstance(byte[])
从 ICC 配置文件字段数据创建的ICC_Profile
创建一个ICC_ColorSpace
。 - 使用其接受
ICC_ColorSpace
的工厂方法之一从新颜色空间创建ImageTypeSpecifier
。 - 创建兼容的
ImageReadParam
并使用ImageReadParam.setDestinationType
设置ImageTypeSpecifier
。 - 将参数对象传递给适当的
read
方法。
如果不基于 ICC 配置文件字段的推断颜色空间与基于 ICC 配置文件的颜色空间兼容,则从该推断颜色空间派生的第二个 ImageTypeSpecifier
将包含在 Iterator
返回的 ImageReader.getImageTypes
中。如果迭代器包含多个类型,第一个将基于 ICC 配置文件,第二个将基于推断的颜色空间。
元数据问题
默认情况下,TIFF 图像文件目录 (IFD) 中的所有已识别字段都加载到本机图像元数据对象中。可以通过设置允许阅读器识别哪些 TIFF 标签、是否读取带有无法识别标签的字段以及是否忽略所有元数据来控制加载哪些字段。通过ImageReader.setInput(Object,boolean,boolean)
的 ignoreMetadata
参数通知读者照常忽略所有元数据。通过TIFFImageReadParam.addAllowedTagSet(TIFFTagSet)
和TIFFImageReadParam.removeAllowedTagSet(TIFFTagSet)
通知哪些TIFFTag
识别或不识别。如果 ignoreMetadata
为 true
,则只有读取图像所必需的元数据才会加载到本机图像元数据对象中。如果 ignoreMetadata
是 false
,那么默认情况下,读取器将仅加载到本机图像元数据对象中,这些字段要么是读取图像所必需的,要么具有 TIFFTag
包含在允许的 TIFFTagSet
之一中。可以通过传入一个 TIFFImageReadParam
来强制读取带有不在允许的 TIFFTagSet
中的标签的字段,其中 TIFFImageReadParam.setReadUnknownTags(boolean)
已使用参数 true
调用。
使用 TIFFDirectory
对象可以简化对元数据值的访问。可以使用 TIFFDirectory.createFromMetadata
方法从 TIFF 读取器返回的 IIOMetadata
对象创建 TIFFDirectory
的实例。
TIFF 原生图像元数据到标准元数据格式的映射
索引 | 标准元数据元素 | 从 TIFF 字段派生 |
---|---|---|
1 | /色度/ColorSpaceType@name | PhotometricInterpretation: WhiteIsZero, BlackIsZero, TransparencyMask = "GRAY"; RGB, PaletteColor => "RGB"; CMYK => "CMYK"; YCbCr => "YCbCr"; CIELab、ICCLab =>“实验室”。 |
2 | /色度/NumChannels@value | SamplesPerPixel |
3 | /色度/BlackIsZero@value | "TRUE" <=> PhotometricInterpretation => WhiteIsZero |
4 | /色度/调色板 | ColorMap |
5 | /Compression/CompressionTypeName@value | 压缩:未压缩=>“无”; CCITT 1D => "CCITT RLE";第 3 组传真 => "CCITT T.4";第 4 组传真 => "CCITT T.6"; LZW => "LZW"; JPEG => "旧 JPEG";新的 JPEG => "JPEG"; Zlib =>> "ZLib"; PackBits => "PackBits";放气 => "放气"; Exif JPEG => “JPEG”。 |
6 | /Compression/Lossless@value | 压缩:JPEG 或 New JPEG => "FALSE";否则为“真”。 |
7 | /数据/PlanarConfiguration@value | 矮矮胖胖的 => "PixelInterleaved";平面 => “PlaneInterleaved”。 |
8 | /数据/SampleFormat@值 | PhotometricInterpretation PaletteColor => "Index"; SampleFormat 无符号整数数据 => "UnsignedIntegral"; SampleFormat 二进制补码有符号整数数据 => "SignedIntegral"; SampleFormat IEEE 浮点数据 => "Real";否则不发射元素。 |
9 | /数据/BitsPerSample@value | BitsPerSample 作为空格分隔的列表。 |
10 | /数据/样本MSB@值 | FillOrder:从左到右 => 以空格分隔的 BitsPerSample-1 列表;从右到左 => 以空格分隔的 0 列表。 |
11 | /维度/PixelAspectRatio@value | (1/X分辨率)/(1/Y分辨率) |
12 | /Dimension/ImageOrientation@value | Orientation |
13 | /维度/HorizontalPixelSize@value | 1/XResolution 以毫米为单位,如果 ResolutionUnit 不是 None。 |
14 | /维度/VerticalPixelSize@value | 如果 ResolutionUnit 不是 None,则 1/YResolution 以毫米为单位。 |
15 | /尺寸/水平位置@值 | 如果 ResolutionUnit 不是 None,则以毫米为单位的 XPosition。 |
16 | /尺寸/垂直位置@值 | 如果 ResolutionUnit 不是 None,则以毫米为单位的 YPosition。 |
17 | /文档/格式版本@值 | 6.0 |
18 | /文档/子图像解释@值 | NewSubFileType: transparency => "TransparencyMask";降低分辨率 => "ReducedResolution";单页 => “单页”。 |
19 | /文档/ImageCreationTime@value | DateTime |
20 | /文本/文本输入 | DocumentName、ImageDescription、Make、Model、PageName、Software、Artist、HostComputer、InkNames、Copyright:/Text/TextEntry@keyword = 字段名称,/Text/TextEntry@value = 字段值。 示例:TIFF 软件字段 => /Text/TextEntry@keyword =“软件”,/Text/TextEntry@value = 用于创建图像的软件包的名称和版本号。 |
21 | /透明度/Alpha@value | ExtraSamples:关联的 alpha =>“预乘”;未关联的 alpha => “非预乘”。 |
读取 Exif 图像
TIFF 阅读器可用于读取未压缩的 Exif 图像或压缩的 Exif 图像的APP1
标记段的内容。
读取未压缩的Exif图像
未压缩的 Exif 图像是一页或两页的未压缩 TIFF 图像,其 IFD 和图像数据内容具有特定的顺序。每个像素都有三个 8 位样本,具有光度解释 RGB 或 YCbCr。图像流必须包含单个主图像,并且可能包含单个缩略图,如果存在,也必须是未压缩的。通常的ImageReader
方法可用于读取图像数据和元数据:
ImageInputStream input;
ImageReader tiffReader;
ImageReadParam tiffReadParam;
tiffReader.setInput(input);
// Read primary image and IFD.
BufferedImage image = tiffReader.read(0, tiffReadParam);
IIOMetadata primaryIFD = tiffReader.getImageMetadata(0);
// Read thumbnail if present.
BufferedImage thumbnail = null;
if (tiffReader.getNumImages(true) > 1) {
thumbnail = tiffReader.read(1, tiffReadParam);
}
请注意,Exif 缩略图在 TIFF 流中被视为单独的页面而不是缩略图,即 tiffReader.hasThumbnails(0)
将返回 false
。
读取压缩的Exif图像
压缩的 Exif 图像是带有插入的APP1
标记段的 3 波段 ISO/IEC 10918-1 基线 DCT JPEG 流。长度之后的标记段的参数是 6 字节序列{'E', 'x', 'i', 'f', 0x00, 0x00}
,后面跟着一个完整的 TIFF 流。嵌入的 TIFF 流包含描述 JPEG 图像的主要 IFD,可选地后跟缩略图 IFD 和压缩或未压缩的缩略图图像数据。请注意,嵌入的 TIFF 流不包含任何与主 IFD 关联的图像数据,也不包含任何重复 JPEG 流本身中的信息的描述性字段。
APP1
标记段的参数内容可以从JPEG读取器返回的图像元数据对象中提取的javax_imageio_jpeg_image_1.0
原生图像元数据树中关联的Node
的用户对象获取。此 APP1 Exif 节点将是名为“markerSequence”的节点的子节点,名称为 unknown
,属性名为 MarkerTag
,整数值为 0xE1
(String
值为 "225"
)。该节点的用户对象将是一个以六个字节 {'E', 'x', 'i', 'f', '0', '0'}
开头的字节数组。主 IFD 和缩略图 IFD 和图像可以通过通常的 ImageReader
方法从用户对象中读取:
ImageReader jpegReader;
ImageReader tiffReader;
// Obtain the APP1 Exif marker data from the JPEG image metadata.
IIOMetadata jpegImageMetadata = jpegReader.getImageMetadata(0);
String nativeFormat = jpegImageMetadata.getNativeMetadataFormatName();
Node jpegImageMetadataTree = jpegImageMetadata.getAsTree(nativeFormat);
// getExifMarkerData() returns the byte array which is the user object
// of the APP1 Exif marker node.
byte[] app1Params = getExifMarkerData(jpegImageMetadataTree);
if (app1Params == null) {
throw new IIOException("APP1 Exif marker not found.");
}
// Set up input, skipping Exif ID 6-byte sequence.
MemoryCacheImageInputStream app1ExifInput
= new MemoryCacheImageInputStream
(new ByteArrayInputStream(app1Params, 6, app1Params.length - 6));
tiffReader.setInput(app1ExifInput);
// Read primary IFD.
IIOMetadata primaryIFD = tiffReader.getImageMetadata(0);
// Read thumbnail if present.
BufferedImage thumbnail = null;
if (tiffReader.getNumImages(true) > 1) {
thumbnail = tiffReader.read(1, tiffReadParam);
}
// Read the primary image.
BufferedImage image = jpegReader.read(0);
请注意,tiffReader.getNumImages(true)
返回嵌入的 TIFF 流中的 IFD 数量,包括那些对应于空图像的 IFD。调用 tiffReader.read(0, readParam)
将引发异常,因为嵌入的 TIFF 流中的主图像始终为空;应使用 JPEG 阅读器本身获取主图像。
写入图像
TIFF 图像由ImageWriter
写入,它可以由其公共接口以及通过提供的 ImageWriteParam
控制。对于 TIFF ImageWriter
的 getDefaultWriteParam()
方法返回的 ImageWriteParam
,canWriteTiles()
和 canWriteCompressed()
方法将返回 true
; canOffsetTiles()
和 canWriteProgressive()
方法将返回 false
。 TIFF 编写器支持许多可选功能,包括写入平铺图像、插入图像、写入或插入空图像以及替换图像数据。像素可以在空图像或非空图像中被替换,但当且仅当数据未被压缩时。
如果正在写入磁贴,则根据 TIFF 规范,它们的每个尺寸都将四舍五入到最接近的 16 的倍数。如果正在使用 JPEG-in-TIFF 压缩,并且正在写入图块,则每个图块尺寸将四舍五入到最接近该尺寸中 JPEG 最小编码单元 (MCU) 的 8 倍的倍数。如果正在使用 JPEG-in-TIFF 压缩并且正在写入条带,则每个条带的行数将四舍五入为两个维度上最大 MCU 的 8 倍。
Compression
在将压缩模式设置为MODE_EXPLICIT
之后,可以通过 ImageWriteParam
的 setCompressionType()
方法设置压缩类型。下表列出了一组固有支持的压缩类型:
索引 | 压缩类型 | Description | Reference |
---|---|---|---|
1 | CCITT RLE | 改进的霍夫曼压缩 | TIFF 6.0 规范,第 10 节 |
2 | CCITT T.4 | CCITT T.4 双级编码/Group 3 传真压缩 | TIFF 6.0 规范,第 11 节 |
3 | CCITT T.6 | CCITT T.6 双级编码/Group 4 传真压缩 | TIFF 6.0 规范,第 11 节 |
4 | LZW | LZW压缩 | TIFF 6.0 规范,第 13 节 |
5 | JPEG | “新”JPEG-in-TIFF 压缩 | TIFF 技术说明 #2 |
6 | 自由库 | “放气/膨胀”压缩(见本表后的注释) | Adobe Photoshop® TIFF 技术说明 |
7 | PackBits | 面向字节的游程压缩 | TIFF 6.0 规范,第 9 节 |
8 | 放气 | “Zip-in-TIFF”压缩(请参阅此表后的注释) | ZLIB 压缩数据格式规范 , DEFLATE 压缩数据格式规范 |
9 | 图片格式 | 特定于 Exif 的 JPEG 压缩(请参阅此表后的注释) | Exif 2.3 规范 (PDF),第 4.5.8 节,“缩略图数据的基本结构” |
TIFF 6.0 规范第 22 节中描述的旧式 JPEG 压缩是not支持的。
CCITT 压缩类型仅适用于二值(1 位)图像。 JPEG 压缩类型仅适用于字节灰度(1 波段)和 RGB(3 波段)图像。
除了 TIFF 压缩字段的值外,ZLib 和 Deflate 压缩是相同的:对于 ZLib,压缩字段的值为 8,而对于 Deflate,它的值为 32946 (0x80b2)。在这两种情况下,每个图像片段(条带或平铺)都被写入一个完整的 zlib 数据流。
“Exif JPEG”是一种压缩类型,在写入 APP1 Exif 标记段的内容以包含在 JPEG 原生图像元数据树中时使用。使用此压缩类型时附加到输出的内容是写入空图像还是非空图像的函数。如果图像为空,则附加符合压缩 Exif 主 IFD 规范的 TIFF IFD。如果图像非空,则附加一个完整的 IFD 和符合压缩 Exif 缩略图 IFD 和图像规范的图像。请注意,空图像的数据可能not稍后使用 TIFF 编写器的像素替换功能附加。
如果使用 ZLib/Deflate 或 JPEG 压缩,则可以设置压缩质量。对于 ZLib/Deflate,提供的浮点质量值被重新缩放到范围 [1, 9]
并截断为整数以得出 Deflate 压缩级别。对于 JPEG,浮点质量值直接传递给 JPEG 编写器插件,后者以通常的方式对其进行解释。
颜色转换
如果源图像数据颜色空间类型为 RGB,而目标光度类型为 CIE L*a*b* 或 YCbCr,则源图像数据将使用内部颜色转换器自动从 RGB 转换。
国际刑事法院简介
如果出现以下任一情况,将写入ICC Profile
字段:
- 一个存在于提供给编写器的本机图像元数据
IIOMetadata
实例中,或者 - 目标
ImageTypeSpecifier
的ColorSpace
是ICC_ColorSpace
的实例,它不是ColorSpace
类中的CS_*
常量定义的标准颜色空间之一。目标类型通过ImageWriteParam.setDestinationType(ImageTypeSpecifier)
设置,默认为正在写入的图像的ImageTypeSpecifier
。
元数据问题
作者的某些行为受或可能影响用户可能提供的图像元数据的内容。对于双层图像,FillOrder
和 T4Options
字段会影响输出数据。如果 FillOrder
的值为 2 (BaselineTIFFTagSet.FILL_ORDER_RIGHT_TO_LEFT
),则数据将从右到左填充,否则从左到右填充。 T4Options
的值指定数据应该是一维编码还是二维编码,以及是否应该使用 EOL 填充。
对于所有图像,RowsPerStrip
字段的值用于设置每个条带的行数(如果图像未平铺)。每个条带的默认行数是 8 或填充不超过 8 KB 的行数,以较大者为准。
对于所有图像,如果平铺模式为 ImageWriteParam.MODE_COPY_FROM_METADATA
,则可以使用 TileWidth
和 TileLength
字段值设置平铺尺寸。如果设置了此模式但未设置字段,则它们各自的默认值为图像宽度和高度。
当使用 JPEG-in-TIFF 压缩时,JPEGTables
字段将被写入 IFD,并将缩写的 JPEG 流写入每个条带或拼贴,当且仅当 JPEGTables
字段包含在提供给编写器的元数据对象中时。如果 JPEGTables
字段的内容是一个有效的纯表格 JPEG 流,那么它将被使用;否则该字段的内容将被替换为默认的视觉无损表。如果元数据中不存在这样的 JPEGTables
字段,则不会将任何 JPEGTables
字段写入输出,并且每个条带或图块将作为单独的、自包含的 JPEG 流写入。
当使用 Deflate/ZLib 或 LZW 压缩时,如果图像每个样本有 8 位,如果 Predictor
字段的值为 2 (BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING
),则将使用水平差分预测器。如果如此请求预测,但图像每个样本没有 8 位,则该字段将重置为值 1 (BaselineTIFFTagSet.PREDICTOR_NONE
)。
可以添加或修改一些字段:
PhotometricInterpretation
如果不存在。PlanarConfiguration
如果此字段存在且值为Planar
则重置为Chunky
。Compression
总是。BitsPerSample
如果图像不是双层的。SamplesPerPixel
总是。ExtraSamples
如果存在 alpha 通道。SampleFormat
如果不存在且数据为 16 位或 32 位整数或浮点数。ColorMap
如果PhotometricInterpretation
是RGBPalette
。ImageWidth
和ImageLength
总是。TileWidth
、TileLength
、TileOffsets
和TileByteCounts
(如果正在写入平铺图像)。RowsPerStrip
、StripOffsets
和StripByteCounts
如果平铺图像是not被写入。XResolution
、YResolution
和ResolutionUnit
如果这些都不存在。YCbCrSubsampling
和YCbCrPositioning
如果光度解释是 YCbCr 并且压缩类型不是 JPEG(非 JPEG YCbCr 输出仅支持 [1, 1] 子采样和共位定位)。YCbCrSubsampling
、YCbCrPositioning
和ReferenceBlackWhite
:如果压缩类型为 JPEG 并且色彩空间为 RGB,这些将被重置为 [2, 2] 居中子采样,没有净空余量/净空余量 (0:255,128:255,128:255)。
一些字段可能会被删除:
BitsPerSample
如果图像是双层的。ExtraSamples
如果图像没有 alpha 通道。ColorMap
如果光度解释不是RGBPalette
。TileWidth
、TileLength
、TileOffsets
和TileByteCounts
(如果平铺)不是正在使用。RowsPerStrip
、StripOffsets
和StripByteCounts
(如果平铺)是正在使用。YCbCrSubsampling
、YCbCrPositioning
和ReferenceBlackWhite
如果压缩类型为 JPEG 且颜色空间为灰度。JPEGProc
、JPEGInterchangeFormat
、JPEGInterchangeFormatLength
、JPEGRestartInterval
、JPEGLosslessPredictors
、JPEGPointTransforms
、JPEGQTables
、JPEGDCTables
和JPEGACTables
(如果压缩类型为 JPEG)。
提供的元数据中存在的其他字段未经解释,将按提供的方式编写。
如果正在写入 Exif 图像,则存在的字段集及其值将被修改,以便结果符合 Exif 2.3 规范。
通过使用表示 TIFF IFD 的 TIFFDirectory
类,可以简化设置图像元数据以写入 TIFF 流。 TIFF IFD 中的字段由 TIFFField
的实例表示。对于要写入的每个字段,可以将 TIFFField
添加到 TIFFDirectory
,后者通过调用 TIFFDirectory.getAsMetadata
转换为 IIOMetadata
对象。如此获得的 IIOMetadata
对象然后可以传递给 TIFF 编写器。
标准元数据格式到 TIFF 原生图像元数据的映射
索引 | TIFF 字段 | 从标准元数据元素派生 |
---|---|---|
1 | PhotometricInterpretation | /Chroma/ColorSpaceType@name: "GRAY" and /Chroma/BlackIsZero@value = "FALSE" => WhiteIsZero; "GRAY" and /Document/SubimageInterpretation@value = "TransparencyMask" => TransparencyMask; "RGB" 和 /Chroma/Palette present => PaletteColor; "灰色" => BlackIsZero; "RGB" => RGB; "YCbCr" => YCbCr; "CMYK" => 三色; “实验室”=> CIELab。 |
2 | SamplesPerPixel | /色度/NumChannels@value |
3 | ColorMap | /色度/调色板 |
4 | Compression | /Compression/CompressionTypeName@value: "none" => 未压缩; "CCITT RLE" => CCITT 1D; "CCITT T.4" => 第 3 组传真; "CCITT T.6" => 第 4 组传真; "LZW" => LZW; “旧 JPEG”=> JPEG; "JPEG" => 新 JPEG; "ZLib" => ZLib; "PackBits" => PackBits; “放气” => 放气。 |
5 | PlanarConfiguration | /Data/PlanarConfiguration@value: "PixelInterleaved" => 厚实; "PlaneInterleaved" => 平面。 |
6 | SampleFormat | /Data/SampleFormat@value: "SignedIntegral" => 二进制补码有符号整数数据; "UnsignedIntegral" => 无符号整数数据; "Real" => IEEE 浮点数据; “索引”=>无符号整数数据。 |
7 | BitsPerSample | /Data/BitsPerSample@value:解析为 char 数组的空格分隔列表。 |
8 | FillOrder | /Data/SampleMSB@value:如果以空格分隔的列表中的所有值都是 0s => 从右到左;否则 => 从左到右。 |
9 | X分辨率 | (10 / /Dimension/HorizontalPixelSize@value) 或 (10 / (/Dimension/VerticalPixelSize@value * /Dimension/PixelAspectRatio@value)) |
10 | YResolution | (10 / /Dimension/VerticalPixelSize@value) 或 (10 / (/Dimension/HorizontalPixelSize@value / /Dimension/PixelAspectRatio@value)) |
11 | ResolutionUnit | 如果设置了 XResolution 或 YResolution,则为厘米;否则无。 |
12 | Orientation | /Dimension/ImageOrientation@value |
13 | X位置 | /维度/水平位置@值/10 |
14 | YPosition | /Dimension/VerticalPosition@value / 10 |
15 | NewSubFileType | /Document/SubimageInterpretation@value: "TransparencyMask" => 透明遮罩; "ReducedResolution" => 降低分辨率; "SinglePage" => 单页。 |
16 | DateTime | /文档/ImageCreationTime@value |
17 | DocumentName、ImageDescription、Make、Model、PageName、Software、Artist、HostComputer、InkNames、Copyright | /Text/TextEntry:如果 /Text/TextEntry@keyword 是任何 TIFF 字段的名称,例如“软件”,则该字段将添加内容 /Text/TextEntry@value 和计数 1。 |
18 | ExtraSamples | /Transparency/Alpha@value: "premultiplied" => 关联的 alpha,计数 1; "nonpremultiplied" => 未关联的 alpha,计数 1。 |
编写 Exif 图像
TIFF 写入器可用于写入未压缩的 Exif 图像或压缩的 Exif 图像的APP1
标记段的内容。
写入未压缩的Exif图像
当写入一系列图像时,每个图像通常记录为 {IFD, IFD Value, Image Data}。 Exif 规范要求未压缩的 Exif 图像的结构如下:- 图像文件头
- Primary IFD
- 主要 IFD 值
- 缩略图 IFD
- 缩略图 IFD 值
- 缩略图数据
- 原始图像数据
ImageWriter tiffWriter;
ImageWriteParam tiffWriteParam;
IIOMetadata tiffStreamMetadata;
IIOMetadata primaryIFD;
BufferedImage image;
BufferedImage thumbnail;
// Specify uncompressed output.
tiffWriteParam.setCompressionMode(ImageWriteParam.MODE_DISABLED);
if (thumbnail != null) {
// Write the TIFF header.
tiffWriter.prepareWriteSequence(tiffStreamMetadata);
// Append the primary IFD.
tiffWriter.prepareInsertEmpty(-1, // append
new ImageTypeSpecifier(image),
image.getWidth(),
image.getHeight(),
primaryIFD,
null, // thumbnails
tiffWriteParam);
tiffWriter.endInsertEmpty();
// Append the thumbnail image data.
tiffWriter.writeToSequence(new IIOImage(thumbnail, null, null),
tiffWriteParam);
// Insert the primary image data.
tiffWriter.prepareReplacePixels(0, new Rectangle(image.getWidth(),
image.getHeight()));
tiffWriter.replacePixels(image, tiffWriteParam);
tiffWriter.endReplacePixels();
// End writing.
tiffWriter.endWriteSequence();
} else {
// Write only the primary IFD and image data.
tiffWriter.write(tiffStreamMetadata,
new IIOImage(image, null, primaryIFD),
tiffWriteParam);
}
编写压缩的Exif图像
压缩 Exif 图像的APP1
段中嵌入的 TIFF 流的结构与 未压缩的Exif图像结构 相同,只是没有主要图像数据,即主要 IFD 不引用任何图像数据。
ImageWriter tiffWriter;
ImageWriteParam tiffWriteParam;
IIOMetadata tiffStreamMetadata;
BufferedImage image;
BufferedImage thumbnail;
IIOMetadata primaryIFD;
ImageOutputStream output;
// Set up an output to contain the APP1 Exif TIFF stream.
ByteArrayOutputStream baos = new ByteArrayOutputStream();
MemoryCacheImageOutputStream app1ExifOutput =
new MemoryCacheImageOutputStream(baos);
tiffWriter.setOutput(app1ExifOutput);
// Set compression for the thumbnail.
tiffWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
tiffWriteParam.setCompressionType("Exif JPEG");
// Write the APP1 Exif TIFF stream.
if (thumbnail != null) {
// Write the TIFF header.
tiffWriter.prepareWriteSequence(tiffStreamMetadata);
// Append the primary IFD.
tiffWriter.prepareInsertEmpty(-1, // append
new ImageTypeSpecifier(image),
image.getWidth(),
image.getHeight(),
primaryIFD,
null, // thumbnails
tiffWriteParam);
tiffWriter.endInsertEmpty();
// Append the thumbnail IFD and image data.
tiffWriter.writeToSequence(new IIOImage(thumbnail, null,
null), tiffWriteParam);
// End writing.
tiffWriter.endWriteSequence();
} else {
// Write only the primary IFD.
tiffWriter.prepareWriteEmpty(tiffStreamMetadata,
new ImageTypeSpecifier(image),
image.getWidth(),
image.getHeight(),
primaryIFD,
null, // thumbnails
tiffWriteParam);
tiffWriter.endWriteEmpty();
}
// Flush data into byte stream.
app1ExifOutput.flush();
// Create APP1 parameter array.
byte[] app1Parameters = new byte[6 + baos.size()];
// Add APP1 Exif ID bytes.
app1Parameters[0] = (byte) 'E';
app1Parameters[1] = (byte) 'x';
app1Parameters[2] = (byte) 'i';
app1Parameters[3] = (byte) 'f';
app1Parameters[4] = app1Parameters[5] = (byte) 0;
// Append TIFF stream to APP1 parameters.
System.arraycopy(baos.toByteArray(), 0, app1Parameters, 6, baos.size());
// Create the APP1 Exif node to be added to native JPEG image metadata.
IIOMetadataNode app1Node = new IIOMetadataNode("unknown");
app1Node.setAttribute("MarkerTag", String.valueOf(0xE1));
app1Node.setUserObject(app1Parameters);
// Append the APP1 Exif marker to the "markerSequence" node.
IIOMetadata jpegImageMetadata =
jpegWriter.getDefaultImageMetadata(new ImageTypeSpecifier(image),
jpegWriteParam);
String nativeFormat = jpegImageMetadata.getNativeMetadataFormatName();
Node tree = jpegImageMetadata.getAsTree(nativeFormat);
NodeList children = tree.getChildNodes();
int numChildren = children.getLength();
for (int i = 0; i < numChildren; i++) {
Node child = children.item(i);
if (child.getNodeName().equals("markerSequence")) {
child.appendChild(app1Node);
break;
}
}
jpegImageMetadata.setFromTree(nativeFormat, tree);
// Write the JPEG image data including the APP1 Exif marker.
jpegWriter.setOutput(output);
jpegWriter.write(new IIOImage(image, null, jpegImageMetadata));
上面创建的 "unknown"
节点将附加到本机 JPEG 图像元数据的 "markerSequence"
节点,并在使用 JPEG 写入器写入主图像时写入 JPEG 流。
流元数据
TIFF 原生流元数据格式的 DTD 如下:<!DOCTYPE "javax_imageio_tiff_stream_1.0" [ <!ELEMENT "javax_imageio_tiff_stream_1.0" (ByteOrder)> <!ELEMENT "ByteOrder" EMPTY> <!-- The stream byte order --> <!ATTLIST "ByteOrder" "value" #CDATA #REQUIRED> <!-- One of "BIG_ENDIAN" or "LITTLE_ENDIAN" --> <!-- Data type: String --> ]>
图像元数据
TIFF 本机图像元数据格式的 DTD 如下:<!DOCTYPE "javax_imageio_tiff_image_1.0" [ <!ELEMENT "javax_imageio_tiff_image_1.0" (TIFFIFD)*> <!ELEMENT "TIFFIFD" (TIFFField | TIFFIFD)*> <!-- An IFD (directory) containing fields --> <!ATTLIST "TIFFIFD" "tagSets" #CDATA #REQUIRED> <!-- Data type: String --> <!ATTLIST "TIFFIFD" "parentTagNumber" #CDATA #IMPLIED> <!-- The tag number of the field pointing to this IFD --> <!-- Data type: Integer --> <!ATTLIST "TIFFIFD" "parentTagName" #CDATA #IMPLIED> <!-- A mnemonic name for the field pointing to this IFD, if known --> <!-- Data type: String --> <!ELEMENT "TIFFField" (TIFFBytes | TIFFAsciis | TIFFShorts | TIFFSShorts | TIFFLongs | TIFFSLongs | TIFFRationals | TIFFSRationals | TIFFFloats | TIFFDoubles | TIFFUndefined)> <!-- A field containing data --> <!ATTLIST "TIFFField" "number" #CDATA #REQUIRED> <!-- The tag number associated with the field --> <!-- Data type: String --> <!ATTLIST "TIFFField" "name" #CDATA #IMPLIED> <!-- A mnemonic name associated with the field, if known --> <!-- Data type: String --> <!ELEMENT "TIFFBytes" (TIFFByte)*> <!-- A sequence of TIFFByte nodes --> <!ELEMENT "TIFFByte" EMPTY> <!-- An integral value between 0 and 255 --> <!ATTLIST "TIFFByte" "value" #CDATA #IMPLIED> <!-- The value --> <!-- Data type: String --> <!ATTLIST "TIFFByte" "description" #CDATA #IMPLIED> <!-- A description, if available --> <!-- Data type: String --> <!ELEMENT "TIFFAsciis" (TIFFAscii)*> <!-- A sequence of TIFFAscii nodes --> <!ELEMENT "TIFFAscii" EMPTY> <!-- A String value --> <!ATTLIST "TIFFAscii" "value" #CDATA #IMPLIED> <!-- The value --> <!-- Data type: String --> <!ELEMENT "TIFFShorts" (TIFFShort)*> <!-- A sequence of TIFFShort nodes --> <!ELEMENT "TIFFShort" EMPTY> <!-- An integral value between 0 and 65535 --> <!ATTLIST "TIFFShort" "value" #CDATA #IMPLIED> <!-- The value --> <!-- Data type: String --> <!ATTLIST "TIFFShort" "description" #CDATA #IMPLIED> <!-- A description, if available --> <!-- Data type: String --> <!ELEMENT "TIFFSShorts" (TIFFSShort)*> <!-- A sequence of TIFFSShort nodes --> <!ELEMENT "TIFFSShort" EMPTY> <!-- An integral value between -32768 and 32767 --> <!ATTLIST "TIFFSShort" "value" #CDATA #IMPLIED> <!-- The value --> <!-- Data type: String --> <!ATTLIST "TIFFSShort" "description" #CDATA #IMPLIED> <!-- A description, if available --> <!-- Data type: String --> <!ELEMENT "TIFFLongs" (TIFFLong)*> <!-- A sequence of TIFFLong nodes --> <!ELEMENT "TIFFLong" EMPTY> <!-- An integral value between 0 and 4294967295 --> <!ATTLIST "TIFFLong" "value" #CDATA #IMPLIED> <!-- The value --> <!-- Data type: String --> <!ATTLIST "TIFFLong" "description" #CDATA #IMPLIED> <!-- A description, if available --> <!-- Data type: String --> <!ELEMENT "TIFFSLongs" (TIFFSLong)*> <!-- A sequence of TIFFSLong nodes --> <!ELEMENT "TIFFSLong" EMPTY> <!-- An integral value between -2147483648 and 2147482647 --> <!ATTLIST "TIFFSLong" "value" #CDATA #IMPLIED> <!-- The value --> <!-- Data type: String --> <!ATTLIST "TIFFSLong" "description" #CDATA #IMPLIED> <!-- A description, if available --> <!-- Data type: String --> <!ELEMENT "TIFFRationals" (TIFFRational)*> <!-- A sequence of TIFFRational nodes --> <!ELEMENT "TIFFRational" EMPTY> <!-- A rational value consisting of an unsigned numerator and denominator --> <!ATTLIST "TIFFRational" "value" #CDATA #IMPLIED> <!-- The numerator and denominator, separated by a slash --> <!-- Data type: String --> <!ELEMENT "TIFFSRationals" (TIFFSRational)*> <!-- A sequence of TIFFSRational nodes --> <!ELEMENT "TIFFSRational" EMPTY> <!-- A rational value consisting of a signed numerator and denominator --> <!ATTLIST "TIFFSRational" "value" #CDATA #IMPLIED> <!-- The numerator and denominator, separated by a slash --> <!-- Data type: String --> <!ELEMENT "TIFFFloats" (TIFFFloat)*> <!-- A sequence of TIFFFloat nodes --> <!ELEMENT "TIFFFloat" EMPTY> <!-- A single-precision floating-point value --> <!ATTLIST "TIFFFloat" "value" #CDATA #IMPLIED> <!-- The value --> <!-- Data type: String --> <!ELEMENT "TIFFDoubles" (TIFFDouble)*> <!-- A sequence of TIFFDouble nodes --> <!ELEMENT "TIFFDouble" EMPTY> <!-- A double-precision floating-point value --> <!ATTLIST "TIFFDouble" "value" #CDATA #IMPLIED> <!-- The value --> <!-- Data type: String --> <!ELEMENT "TIFFUndefined" EMPTY> <!-- Uninterpreted byte data --> <!ATTLIST "TIFFUndefined" "value" #CDATA #IMPLIED> <!-- A list of comma-separated byte values --> <!-- Data type: String --> ]>
- 自从:
- 9