最近在读取客户发过来的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深入研究相关推荐

  1. java解析字符串_用Java解析字符串有哪些不同的方法?

    用Java解析字符串有哪些不同的方法? 对于解析播放器命令,我最常使用split方法通过定界符对字符串进行分割,然后再通过一系列ifs或switches找出其余部分. Java中解析字符串的几种不同方 ...

  2. Java解析证书内容

    Java解析证书获取证书内部信息,在证书交换环节中常使用,网络上也提供很多参考方案,本文主要是提供证书base64格式解析和证书路径解析证书内容.在解析时可能会遇到一些问题,后面根据情况再具体说明. ...

  3. Office中数学公式用Java解析,java解析word公式

    公司正在做教育类产品,在遇到数学公式时,我们一般会使用latex表达式来做保存和渲染. 在其中一个项目上,遇到一个需求是要从office文档(Word或Excel)中导入题目内容至数据库,题目内容中就 ...

  4. java 解析/读取 种子/bt/torrent 内容

    碰到不会的技术问题,我还是先度娘.能中文看懂,为什么非要看英文呢. java 解析/读取 种子/bt/torrent  内容,这个度娘给的满意答案并不是很多.GG之后的搜索结果出现了stackover ...

  5. poi处理word内容的公式_Office中数学公式用Java解析

    公司正在做教育类产品,在遇到数学公式时,我们一般会使用latex表达式来做保存和渲染. 在其中一个项目上,遇到一个需求是要从office文档(Word或Excel)中导入题目内容至数据库,题目内容中就 ...

  6. IDEA Java解析GeoJson.json文件

    IDEA Java解析GeoJson.json文件 一.遇到的问题 1. 无法导入成功 2. org.geotools.StyleFactory is not an ImageIO SPI class ...

  7. easyexcel生成excel_阿里JAVA解析Excel工具easyexcel

    java解析.生成Excel比较有名的框架有Apache poi.jxl.但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有 ...

  8. java图书分析echarts_Echarts后台Java解析

    Echarts 后台 Java 解析 一.引入 Echarts 的封装类 1 .基于 maven 架构的 com.github.abel533 ECharts 2.2.7 com.google.cod ...

  9. java 解析 csv 文件

    文章分类:JavaEye 一.貌似有bug,不行用 二.或 三. 的方法 Java代码   import java.io.BufferedReader; import java.io.FileInpu ...

  10. 用正则表达式和java解析csv文件

    用正则表达式和java解析csv文件 作者:弹着钢琴设计  来源:博客园  发布时间:2009-06-15 18:31  阅读:337 次  原文链接   [收藏]   在解析csv文件之前,先来看看 ...

最新文章

  1. 隐私全无!错发1700多条Alexa录音,上报后亚马逊淡定回应是“个人错误”
  2. TFS 2008 中文版下载及安装完整图解
  3. uoj22 外星人(dp)
  4. C语言入门(4)——常量、变量与赋值
  5. 一键托管,阿里云全链路追踪服务正式商用:成本仅自建1/5或更少
  6. 使用正则表达式小心换行和回车
  7. 一千万条数据去重_simhash算法:海量千万级的数据去重
  8. C++ static、const和static const类型成员变量声明及其初始化
  9. 黑苹果Mojave下驱动高通模块Atheros DHXA-195(AR9285无线网卡和AR3011 蓝牙3.0)
  10. android mediaplayer单曲循环播放,android mediaplayer永远在ICS上循环播放
  11. 【JZOJ A组】东风谷早苗
  12. Python3 语音识别谷歌验证码
  13. android+otg+apk,android手机 OTG功能调试usb串口的demo程序
  14. 玩寻仙一个月之我感受
  15. Servlet入门学习(二)
  16. linux运维工程师 pdf下载,linux运维工程师命令.pdf
  17. Druid SQL和Security在美团点评的实践
  18. selenium闪退
  19. 《JavaWeb视频教程》(p34)
  20. 局域网网上邻居无法访问问题的解决

热门文章

  1. 【优化求解】基于天牛须算法PID控制器优化设计matlab代码
  2. 训练好的神经网络 如何预测_【家长必看】如何帮助孩子训练好口才?
  3. 数据结构与算法-链表实现
  4. 201871010133-赵永军《面向对象程序设计(java)》第二周学习总结
  5. 【金融财经】金融市场一周简报(2018-03-30)
  6. 杰克·伦敦: 一块牛排
  7. 加快5G视频客服能力构建PPT
  8. Beacon技术相关介绍及应用
  9. 【目标检测】58、目标检测中的正负样本分配策略总结
  10. 结构光三维重建(一)条纹结构光三维重建