Java 解析Tiff深入研究
最近在读取客户发过来的tiff文件是,底层竟然报错了,错误: bandOffsets.length is wrong! 没办法,因为错误消息出现在tiff的read中,因此就对底层序中tiff读取的代码进行了研究。
借助这篇文章,我们需要先了解Tiff文件的具体结构,可以参考这篇文章, TIFF文件结构详解 https://blog.csdn.net/oYinHeZhiGuang/article/details/121710467 讲的很好!
下面我们来看下imageio-ext中的tiff读取代码,主要类TiffImageReader,我们来看下Java程序是如何读取tiff文件的。
构造方法:
public TIFFImageReader(ImageReaderSpi originatingProvider) {super(originatingProvider);}
这个类需要通过一个ImageReaderSpi来实例化,其实这种SPI的设计模式,Java的很多开源项目都在用到,这里我们通过TIFFImageReaderSpi这个类即可。
其次设置文件的路径,以及其它一些参数,通过该类的如下方法:
public void setInput(Object input,boolean seekForwardOnly,boolean ignoreMetadata)
这个方法,里面有input就是需要读取的文件, seekForwardOnly设置为true表示: 只能从这个输入源按升序读取图像和元数据。 ignoreMetadata设置为true表示读取忽略元数据
接下来就是对tiff元数据的读取,具体参见getImageMetadata(int imageIndex)这个方法:
public IIOMetadata getImageMetadata(int imageIndex) throws IIOException {seekToImage(imageIndex, true);TIFFImageMetadata im =new TIFFImageMetadata(imageMetadata.getRootIFD().getTagSetList());Node root =imageMetadata.getAsTree(TIFFImageMetadata.nativeMetadataFormatName);im.setFromTree(TIFFImageMetadata.nativeMetadataFormatName, root);if (noData != null) {im.setNoData(new double[] {noData, noData});}if (scales != null && offsets != null) {im.setScales(scales);im.setOffsets(offsets);}return im;}
其中的seekToImage(imageIndex, true)为最主要的逻辑处理,这个方法中,第一个参数,imageIndex为tiff多页中的第几个,第二参数设置标示该tiff页是否已经被解析过
private void seekToImage(int imageIndex, boolean optimized) throws IIOException {checkIndex(imageIndex);// TODO we should do this initialization just once!!!int index = locateImage(imageIndex);if (index != imageIndex) {throw new IndexOutOfBoundsException("imageIndex out of bounds!");}final Integer i= Integer.valueOf(index);//optimized branchif(!optimized){readMetadata();initializeFromMetadata();return;}// in case we have cache the info for this pageif(pagesInfo.containsKey(i)){// initialize from cachedinfo only if needed// TODO Improveif(imageMetadata == null || !initialized) {// this means the curindex has changedfinal PageInfo info = pagesInfo.get(i);final TIFFImageMetadata metadata = info.imageMetadata.get();if (metadata != null) {initializeFromCachedInfo(info, metadata);return;}pagesInfo.put(i,null);}}readMetadata();initializeFromMetadata();}
这个方法当中,第一次加载tiff,通过readMetadata()和initializeFromMetadata()将tiff的元信息缓存起来,方便后面再次读取。
读取过程
主要是要结合Tiff的格式进行理解,大体主要是解析tiff头,然后获取到IFD(tiff的图像目录信息),然后再依次去解析每个目录的具体内容,代码就不再这里罗列了。
这里主要说下,解析目录信息是获取tiff的元信息的过程,通常是解析每个tag的信息,解析代码TIFFIFD类的initialize(ImageInputStream stream, boolean ignoreUnknownFields, final boolean isBTIFF)方法中
public void initialize(ImageInputStream stream,boolean ignoreUnknownFields, final boolean isBTIFF) throws IOException {removeTIFFFields();List tagSetList = getTagSetList();final long numEntries;if(isBTIFF)numEntries= stream.readLong();elsenumEntries= stream.readUnsignedShort();for (int i = 0; i < numEntries; i++) {// Read tag number, value type, and value count.int tag = stream.readUnsignedShort();int type = stream.readUnsignedShort();int count;if(isBTIFF){long count_=stream.readLong();count = (int)count_;if(count!=count_)throw new IllegalArgumentException("unable to use long number of values");}else count = (int)stream.readUnsignedInt();// Get the associated TIFFTag.TIFFTag tiffTag = getTag(tag, tagSetList);// Ignore unknown fields.if(ignoreUnknownFields && tiffTag == null) {// Skip the value/offset so as to leave the stream// position at the start of the next IFD entry.if(isBTIFF)stream.skipBytes(8);elsestream.skipBytes(4);// XXX Warning message ...// Continue with the next IFD entry.continue;}long nextTagOffset;if(isBTIFF){nextTagOffset = stream.getStreamPosition() + 8;int sizeOfType = TIFFTag.getSizeOfType(type);if (count*sizeOfType > 8) {long value = stream.readLong();stream.seek(value);}}else{ nextTagOffset = stream.getStreamPosition() + 4;int sizeOfType = TIFFTag.getSizeOfType(type);if (count*sizeOfType > 4) {long value = stream.readUnsignedInt();stream.seek(value);}}if (tag == BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS ||tag == BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS ||tag == BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH) {this.stripOrTileByteCountsPosition =stream.getStreamPosition();if (LAZY_LOADING) {type = type == TIFFTag.TIFF_LONG ? TIFFTag.TIFF_LAZY_LONG : TIFFTag.TIFF_LAZY_LONG8;}} else if (tag == BaselineTIFFTagSet.TAG_STRIP_OFFSETS ||tag == BaselineTIFFTagSet.TAG_TILE_OFFSETS ||tag == BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT) {this.stripOrTileOffsetsPosition =stream.getStreamPosition();if (LAZY_LOADING) {type = type == TIFFTag.TIFF_LONG ? TIFFTag.TIFF_LAZY_LONG : TIFFTag.TIFF_LAZY_LONG8;}}Object obj = null;try {switch (type) {case TIFFTag.TIFF_BYTE:case TIFFTag.TIFF_SBYTE:case TIFFTag.TIFF_UNDEFINED:case TIFFTag.TIFF_ASCII:byte[] bvalues = new byte[count];stream.readFully(bvalues, 0, count);if (type == TIFFTag.TIFF_ASCII) {// Can be multiple stringsfinal List<String> v = new ArrayList<String>();boolean inString = false;int prevIndex = 0;for (int index = 0; index <= count; index++) {if (index < count && bvalues[index] != 0) {if (!inString) {// start of stringprevIndex = index;inString = true;}} else { // null or special case at end of stringif (inString) {// end of stringfinal String s = new String(bvalues, prevIndex,index - prevIndex);v.add(s);inString = false;}}}count = v.size();String[] strings;if(count != 0) {strings = new String[count];for (int c = 0 ; c < count; c++) {strings[c] = v.get(c);}} else {// This case has been observed when the value of// 'count' recorded in the field is non-zero but// the value portion contains all nulls.count = 1;strings = new String[] {""};}obj = strings;} else {obj = bvalues;}break;case TIFFTag.TIFF_SHORT:char[] cvalues = new char[count];for (int j = 0; j < count; j++) {cvalues[j] = (char)(stream.readUnsignedShort());}obj = cvalues;break;case TIFFTag.TIFF_LONG:case TIFFTag.TIFF_IFD_POINTER:long[] lvalues = new long[count];for (int j = 0; j < count; j++) {lvalues[j] = stream.readUnsignedInt();}obj = lvalues;break;case TIFFTag.TIFF_RATIONAL:long[][] llvalues = new long[count][2];for (int j = 0; j < count; j++) {llvalues[j][0] = stream.readUnsignedInt();llvalues[j][1] = stream.readUnsignedInt();}obj = llvalues;break;case TIFFTag.TIFF_SSHORT:short[] svalues = new short[count];for (int j = 0; j < count; j++) {svalues[j] = stream.readShort();}obj = svalues;break;case TIFFTag.TIFF_SLONG:int[] ivalues = new int[count];for (int j = 0; j < count; j++) {ivalues[j] = stream.readInt();}obj = ivalues;break;case TIFFTag.TIFF_SRATIONAL:int[][] iivalues = new int[count][2];for (int j = 0; j < count; j++) {iivalues[j][0] = stream.readInt();iivalues[j][1] = stream.readInt();}obj = iivalues;break;case TIFFTag.TIFF_FLOAT:float[] fvalues = new float[count];for (int j = 0; j < count; j++) {fvalues[j] = stream.readFloat();}obj = fvalues;break;case TIFFTag.TIFF_DOUBLE:double[] dvalues = new double[count];for (int j = 0; j < count; j++) {dvalues[j] = stream.readDouble();}obj = dvalues;break;case TIFFTag.TIFF_LONG8:case TIFFTag.TIFF_SLONG8: case TIFFTag.TIFF_IFD8:long[] lBvalues = new long[count];for (int j = 0; j < count; j++) {lBvalues[j] = stream.readLong();}obj = lBvalues;break;case TIFFTag.TIFF_LAZY_LONG8: case TIFFTag.TIFF_LAZY_LONG: obj = new TIFFLazyData(stream, type, count);break;default:// XXX Warningbreak;}} catch(EOFException eofe) {// The TIFF 6.0 fields have tag numbers less than or equal// to 532 (ReferenceBlackWhite) or equal to 33432 (Copyright).// If there is an error reading a baseline tag, then re-throw// the exception and fail; otherwise continue with the next// field.if(BaselineTIFFTagSet.getInstance().getTag(tag) == null) {throw eofe;}}if (tiffTag == null) {// XXX Warning: unknown tag} else if (!tiffTag.isDataTypeOK(type)) {// XXX Warning: bad data type} else if (tiffTag.isIFDPointer() && obj != null) {stream.mark();stream.seek(((long[])obj)[0]);List tagSets = new ArrayList(1);tagSets.add(tiffTag.getTagSet());TIFFIFD subIFD = new TIFFIFD(tagSets);// XXX Use same ignore policy for sub-IFD fields?subIFD.initialize(stream, ignoreUnknownFields);obj = subIFD;stream.reset();}if (tiffTag == null) {tiffTag = new TIFFTag(null, tag, 1 << type, null);}// Add the field if its contents have been initialized which// will not be the case if an EOF was ignored above.if(obj != null) {TIFFField f = new TIFFField(tiffTag, type, count, obj);addTIFFField(f);}stream.seek(nextTagOffset);}this.lastPosition = stream.getStreamPosition();}View Code
Tiff常用的Tag标签类有 BaseLineTiffTagSet、FaxTiffTagSet、GeoTiffTagSet、EXIFPTiffTagSet、 PrivateTIFFTagSet等。
其中的GeoTiffTagSet用于geotiff的额外存储信息,在这里说明下,Geotiff是Tiff格式对Gis数据的一种存储支持,而 PrivateTIFFTagSet是对gdal的支持,增加了NODATA、MEATADATA的信息。
对于文章开头提的关于bandOffsets.length is wrong!,主要原因出现在getImageTypes(int imageIndex)这个方法的下面这个实现中。
ImageTypeSpecifier itsRaw = TIFFDecompressor.getRawImageTypeSpecifier(photometricInterpretation,compression,samplesPerPixel,bitsPerSample,sampleFormat,extraSamples,colorMap);
最终我们在ImageTypeSpecifier这个类的Interleaved(ColorSpace colorSpace,int[] bandOffsets,int dataType,boolean hasAlpha,boolean isAlphaPremultiplied) 方法中发现问题。
public Interleaved(ColorSpace colorSpace,int[] bandOffsets,int dataType,boolean hasAlpha,boolean isAlphaPremultiplied) {if (colorSpace == null) {throw new IllegalArgumentException("colorSpace == null!");}if (bandOffsets == null) {throw new IllegalArgumentException("bandOffsets == null!");}int numBands = colorSpace.getNumComponents() +(hasAlpha ? 1 : 0);if (bandOffsets.length != numBands) {throw new IllegalArgumentException("bandOffsets.length is wrong!");}
我们发现只有当我们的图像偏移数量和我们的通道数不一致的时候,就会报这个错误!
总结
通过研究这个问题,基本上梳理了Java基于ImageIO-ext读取tiff的过程,基本跟tiff的数据结构对应起来。
来源:https://www.cnblogs.com/share-gis/p/16630396.html
Java 解析Tiff深入研究相关推荐
- java解析字符串_用Java解析字符串有哪些不同的方法?
用Java解析字符串有哪些不同的方法? 对于解析播放器命令,我最常使用split方法通过定界符对字符串进行分割,然后再通过一系列ifs或switches找出其余部分. Java中解析字符串的几种不同方 ...
- Java解析证书内容
Java解析证书获取证书内部信息,在证书交换环节中常使用,网络上也提供很多参考方案,本文主要是提供证书base64格式解析和证书路径解析证书内容.在解析时可能会遇到一些问题,后面根据情况再具体说明. ...
- Office中数学公式用Java解析,java解析word公式
公司正在做教育类产品,在遇到数学公式时,我们一般会使用latex表达式来做保存和渲染. 在其中一个项目上,遇到一个需求是要从office文档(Word或Excel)中导入题目内容至数据库,题目内容中就 ...
- java 解析/读取 种子/bt/torrent 内容
碰到不会的技术问题,我还是先度娘.能中文看懂,为什么非要看英文呢. java 解析/读取 种子/bt/torrent 内容,这个度娘给的满意答案并不是很多.GG之后的搜索结果出现了stackover ...
- poi处理word内容的公式_Office中数学公式用Java解析
公司正在做教育类产品,在遇到数学公式时,我们一般会使用latex表达式来做保存和渲染. 在其中一个项目上,遇到一个需求是要从office文档(Word或Excel)中导入题目内容至数据库,题目内容中就 ...
- IDEA Java解析GeoJson.json文件
IDEA Java解析GeoJson.json文件 一.遇到的问题 1. 无法导入成功 2. org.geotools.StyleFactory is not an ImageIO SPI class ...
- easyexcel生成excel_阿里JAVA解析Excel工具easyexcel
java解析.生成Excel比较有名的框架有Apache poi.jxl.但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有 ...
- java图书分析echarts_Echarts后台Java解析
Echarts 后台 Java 解析 一.引入 Echarts 的封装类 1 .基于 maven 架构的 com.github.abel533 ECharts 2.2.7 com.google.cod ...
- java 解析 csv 文件
文章分类:JavaEye 一.貌似有bug,不行用 二.或 三. 的方法 Java代码 import java.io.BufferedReader; import java.io.FileInpu ...
- 用正则表达式和java解析csv文件
用正则表达式和java解析csv文件 作者:弹着钢琴设计 来源:博客园 发布时间:2009-06-15 18:31 阅读:337 次 原文链接 [收藏] 在解析csv文件之前,先来看看 ...
最新文章
- 隐私全无!错发1700多条Alexa录音,上报后亚马逊淡定回应是“个人错误”
- TFS 2008 中文版下载及安装完整图解
- uoj22 外星人(dp)
- C语言入门(4)——常量、变量与赋值
- 一键托管,阿里云全链路追踪服务正式商用:成本仅自建1/5或更少
- 使用正则表达式小心换行和回车
- 一千万条数据去重_simhash算法:海量千万级的数据去重
- C++ static、const和static const类型成员变量声明及其初始化
- 黑苹果Mojave下驱动高通模块Atheros DHXA-195(AR9285无线网卡和AR3011 蓝牙3.0)
- android mediaplayer单曲循环播放,android mediaplayer永远在ICS上循环播放
- 【JZOJ A组】东风谷早苗
- Python3 语音识别谷歌验证码
- android+otg+apk,android手机 OTG功能调试usb串口的demo程序
- 玩寻仙一个月之我感受
- Servlet入门学习(二)
- linux运维工程师 pdf下载,linux运维工程师命令.pdf
- Druid SQL和Security在美团点评的实践
- selenium闪退
- 《JavaWeb视频教程》(p34)
- 局域网网上邻居无法访问问题的解决