batik是apache的一个开源项目,能够实现svg的渲染,后端借助它能够比较简单的实现图片渲染,固然和java一向处理图片不太方便同样,使用起来也有很多坑java
下面记录一个bug的修复过程apache
svg文件:后端
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <image y="0" width="100%" height="100%" x="0" xlink:href="http://image.uc.cn/o/wemedia/s/upload/2017/39c53604fe3587a4876396cf3785b801x200x200x13.png"/> <!--xlink:href="https://s17.mogucdn.com/mlcdn/c45406/180119_46ld8kkb54d3el06hela5d61e18f5_1024x966.png"/>--> <!--xlink:href="http://avatar.csdn.net/A/8/B/3_u010889145.jpg"/>--> </svg>
依次测试了三个图片,两个png,一个jpg,很不幸第一个png会抛异常app
输出的堆栈信息如ide
The URI "http://image.uc.cn/o/wemedia/s/upload/2017/39c53604fe3587a4876396cf3785b801x200x200x13.png" on element <image> can't be opened because: PNG URL is corrupt or unsupported variant at org.apache.batik.bridge.UserAgentAdapter.getBrokenLinkDocument(UserAgentAdapter.java:448) at org.apache.batik.bridge.SVGImageElementBridge.createRasterImageNode(SVGImageElementBridge.java:642) at org.apache.batik.bridge.SVGImageElementBridge.createImageGraphicsNode(SVGImageElementBridge.java:340) at org.apache.batik.bridge.SVGImageElementBridge.buildImageGraphicsNode(SVGImageElementBridge.java:180) at org.apache.batik.bridge.SVGImageElementBridge.createGraphicsNode(SVGImageElementBridge.java:122) at org.apache.batik.bridge.GVTBuilder.buildGraphicsNode(GVTBuilder.java:213) at org.apache.batik.bridge.GVTBuilder.buildComposite(GVTBuilder.java:171) at org.apache.batik.bridge.GVTBuilder.build(GVTBuilder.java:82) at org.apache.batik.transcoder.SVGAbstractTranscoder.transcode(SVGAbstractTranscoder.java:208) at org.apache.batik.transcoder.image.ImageTranscoder.transcode(ImageTranscoder.java:92) at org.apache.batik.transcoder.XMLAbstractTranscoder.transcode(XMLAbstractTranscoder.java:142) at org.apache.batik.transcoder.SVGAbstractTranscoder.transcode(SVGAbstractTranscoder.java:156) ...
既然出现了这个问题,那么就要去修复解决了,固然遇到这么鬼畜的问题,最多见的几个步骤:svg
经过上面的堆栈信息,能够想见,debug的几个地方也和明确了,首先定位到下面这一行测试
at org.apache.batik.bridge.UserAgentAdapter.getBrokenLinkDocument(UserAgentAdapter.java:448)
为何这么干?由于首先得确认下这个异常是怎么抛出来的,逆向推,直接看源码,发现直接抛出异常ui
再往上走this
at org.apache.batik.bridge.SVGImageElementBridge.createRasterImageNode(SVGImageElementBridge.java:642)
因此说由于这个if条件判断成立,致使进入了这个异常逻辑,判断的逻辑也没啥好说的,如今的关键是这个参数对象img是怎么来的.net
at org.apache.batik.bridge.SVGImageElementBridge.createImageGraphicsNode(SVGImageElementBridge.java:340)
而后就稍微清晰一点了,直接将火力放在下面的方法中
org.apache.batik.ext.awt.image.spi.ImageTagRegistry#readURL(java.io.InputStream, org.apache.batik.util.ParsedURL, org.apache.xmlgraphics.java2d.color.ICCColorSpaceWithIntent, boolean, boolean)
在这个方法内部,也没什么好说的,单步多调几回,就能发现异常的case是怎么来的了,省略掉中间各类单步debug的过程,下面直接进入关键链路
org.apache.batik.ext.awt.image.codec.imageio.AbstractImageIORegistryEntry
经过上面的一路以后,发现最终的关键就是上面这个抽象类,顺带也能够看下这个抽象类的几个子类,有JPEGxxx, PNGxxx, TIFFxxx,而后问题来了,都已经有相关实现了,因此png讲道理应该是会支持的才对吧,但和实际的表现太不同了吧,因此有必要撸一把源码了
public Filter handleStream(InputStream inIS, ParsedURL origURL, boolean needRawData) { final DeferRable dr = new DeferRable(); final InputStream is = inIS; final String errCode; final Object [] errParam; if (origURL != null) { errCode = ERR_URL_FORMAT_UNREADABLE; errParam = new Object[] {getFormatName(), origURL}; } else { errCode = ERR_STREAM_FORMAT_UNREADABLE; errParam = new Object[] {getFormatName()}; } Thread t = new Thread() { @Override public void run() { Filter filt; try{ Iterator<ImageReader> iter = ImageIO.getImageReadersByMIMEType( getMimeTypes().get(0).toString()); if (!iter.hasNext()) { throw new UnsupportedOperationException( "No image reader for " + getFormatName() + " available!"); } ImageReader reader = iter.next(); ImageInputStream imageIn = ImageIO.createImageInputStream(is); reader.setInput(imageIn, true); int imageIndex = 0; dr.setBounds(new Rectangle2D.Double (0, 0, reader.getWidth(imageIndex), reader.getHeight(imageIndex))); CachableRed cr; //Naive approach possibly wasting lots of memory //and ignoring the gamma correction done by PNGRed :-( //Matches the code used by the former JPEGRegistryEntry, though. BufferedImage bi = reader.read(imageIndex); cr = GraphicsUtil.wrap(bi); cr = new Any2sRGBRed(cr); cr = new FormatRed(cr, GraphicsUtil.sRGB_Unpre); WritableRaster wr = (WritableRaster)cr.getData(); ColorModel cm = cr.getColorModel(); BufferedImage image = new BufferedImage (cm, wr, cm.isAlphaPremultiplied(), null); cr = GraphicsUtil.wrap(image); filt = new RedRable(cr); } catch (IOException ioe) { // Something bad happened here... filt = ImageTagRegistry.getBrokenLinkImage (AbstractImageIORegistryEntry.this, errCode, errParam); } catch (ThreadDeath td) { filt = ImageTagRegistry.getBrokenLinkImage (AbstractImageIORegistryEntry.this, errCode, errParam); dr.setSource(filt); throw td; } catch (Throwable t) { filt = ImageTagRegistry.getBrokenLinkImage (AbstractImageIORegistryEntry.this, errCode, errParam); } dr.setSource(filt); } }; t.start(); return dr; }
看上面的实现是一个很是有意思的事情, 开了一个线程作事情,并且直接就返回了,至关于给了别人一个储物箱的钥匙,虽然如今储物箱是空的,可是回头我会填满的
言归正传,主要的业务逻辑就在这个线程里了,核心的几行代码就是
// 加载图片,转为BufferedImage对象 BufferedImage bi = reader.read(imageIndex); cr = GraphicsUtil.wrap(bi); // 下面实现对图片的ARGB进行修改 cr = new Any2sRGBRed(cr); cr = new FormatRed(cr, GraphicsUtil.sRGB_Unpre); WritableRaster wr = (WritableRaster)cr.getData(); ColorModel cm = cr.getColorModel(); BufferedImage image = new BufferedImage (cm, wr, cm.isAlphaPremultiplied(), null); cr = GraphicsUtil.wrap(image); filt = new RedRable(cr);
debug上面的几行代码,发现问题比较明显了,就是这个图片的转换跪了,至于为啥? java的图片各类蛋疼至极,这里面的逻辑,真心搞不进去,so深挖到此为止
问题定位到了,固然就是想办法来修复了,简单来讲,须要兼容的就是图片的类型转换上了,直接用原来的可能会抛异常,因此作了一个简单的兼容逻辑
if(bi.getType() == BufferedImage.TYPE_BYTE_INDEXED) { BufferedImage image = new BufferedImage(bi.getWidth(), bi.getHeight(), BufferedImage.TYPE_INT_ARGB); Graphics2D g2d = image.createGraphics(); g2d.drawImage(bi, 0, 0, null); g2d.dispose(); cr = GraphicsUtil.wrap(image); } else { cr = GraphicsUtil.wrap(bi); cr = new Any2sRGBRed(cr); cr = new FormatRed(cr, GraphicsUtil.sRGB_Unpre); WritableRaster wr = (WritableRaster)cr.getData(); ColorModel cm = cr.getColorModel(); BufferedImage image = new BufferedImage (cm, wr, cm.isAlphaPremultiplied(), null); cr = GraphicsUtil.wrap(image); }
再次验证,ok
注意:
一个问题来了,上面的兼容是须要修改源码的,咱们能够怎么办?有几种解决方法
至于个人选择,就是使用了猥琐方法二
尽信书则不如,已上内容,纯属一家之言,因本人能力通常,看法不全,若有问题,欢迎批评指正