Batik渲染png圖片異常的bug修復

Batik渲染png圖片異常的bug修復

batik是apache的一個開源項目,能夠實現svg的渲染,後端藉助它能夠比較簡單的實現圖片渲染,固然和java一向處理圖片不太方便同樣,使用起來也有很多坑java

下面記錄一個bug的修復過程apache

I. 問題重現

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)
	...

II. 問題定位及分析

既然出現了這個問題,那麼就要去修復解決了,固然遇到這麼鬼畜的問題,最多見的幾個步驟:svg

  1. 其餘人遇到過麼 (問百度) -- 結果度娘沒有給出任何有效的建議,也沒有搜到任何有用的信息
  2. 而後問谷歌,靠譜了一點,至少有些相關的主題了,但建設性的意見也沒收到
  3. 外援實在找不到,只能debug查問題了

1. DEBUG的一路

經過上面的堆棧信息,能夠想見,debug的幾個地方也和明確了,首先定位到下面這一行測試

at org.apache.batik.bridge.UserAgentAdapter.getBrokenLinkDocument(UserAgentAdapter.java:448)

爲何這麼幹?由於首先得確認下這個異常是怎麼拋出來的,逆向推,直接看源碼,發現直接拋出異常ui

2A02AB38-25ED-4B71-8CE7-76460623FE08.png

再往上走this

at org.apache.batik.bridge.SVGImageElementBridge.createRasterImageNode(SVGImageElementBridge.java:642)

D1CECFCF-D940-4A17-87F9-7E12B00517D9.png

因此說由於這個if條件判斷成立,致使進入了這個異常邏輯,判斷的邏輯也沒啥好說的,如今的關鍵是這個參數對象img是怎麼來的.net

at org.apache.batik.bridge.SVGImageElementBridge.createImageGraphicsNode(SVGImageElementBridge.java:340)

IMAGE

而後就稍微清晰一點了,直接將火力放在下面的方法中

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的過程,下面直接進入關鍵鏈路

2. 火力全開,問題定位

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深挖到此爲止


III. 兼容邏輯

問題定位到了,固然就是想辦法來修復了,簡單來講,須要兼容的就是圖片的類型轉換上了,直接用原來的可能會拋異常,因此作了一個簡單的兼容邏輯

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

注意:

一個問題來了,上面的兼容是須要修改源碼的,咱們能夠怎麼辦?有幾種解決方法

  • 猥瑣方法一:down下源碼,修改版本,而後傳到本身的私服,使用本身的vip包
  • 猥瑣方法二:把 batik-codec 工程原樣拷貝到本身的項目中,就能夠隨意的使用改了
  • 猥瑣方法三:寫一個徹底相同的類(包路徑徹底相同),而後構造一個自定義類加載器,加載這個本身的這個兼容版本的,替換原來的

至於個人選擇,就是使用了猥瑣方法二

IV. 其餘

聲明

盡信書則不如,已上內容,純屬一家之言,因本人能力通常,看法不全,若有問題,歡迎批評指正

掃描關注,java分享

QrCode

相關文章
相關標籤/搜索