Swing & Batik:如何从SVG文件创建一个ImageIcon?

21

简而言之,我正在寻求使用batik库从SVG文件创建ImageIcon的方法。我不想先将SVG栅格化到磁盘上,我只想能够从jar文件中提取svg并将其作为UI元素使用。

我觉得这应该相当简单,但是batik javadocs没有给我需要知道的信息。

(为什么选择batik?因为我们已经在使用它了,所以我们不必再向法律部门提交另一个库的使用申请。)

5个回答

24

这其实很简单,只是不是非常直观。

您需要扩展ImageTranscoder。在createImage方法中,您需要分配一个BufferedImage,将其缓存为成员变量,并返回它。writeImage方法为空。您还需要添加一个getter方法来检索BufferedImage

代码大致如下:

    class MyTranscoder extends ImageTranscoder {
        private BufferedImage image = null;
        public BufferedImage createImage(int w, int h) {
            image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
            return image;
        }
        public void writeImage(BufferedImage img, TranscoderOutput out) {
        }
        public BufferedImage getImage() {
            return image;
        }
    }

要创建一张图片,你需要创建一个转码器的实例,并设置 TranscodingHints 来指定所需的宽度和高度。最后通过从 TranscoderInput 转码到 null 目标来进行转码。然后调用你的转码器上的 getter 方法来获取图像。

调用看起来大致如下:

    MyTranscoder transcoder = new MyTranscoder();
    TranscodingHints hints = new TranscodingHints();
    hints.put(ImageTranscoder.KEY_WIDTH, width);
    hints.put(ImageTranscoder.KEY_HEIGHT, height);
    transcoder.setTranscodingHints(hints);
    transcoder.transcode(new TranscoderInput(url), null);
    BufferedImage image = transcoder.getImage();

简单吧?(是啊,简单。只用了我两周的时间才明白。叹气。)


5
没错,就是Devon说的那样 :) 这是我的SVGIcon类,基本上可以实现这个功能:http://mcc.id.au/2005/04/SVGIcon.java - heycam
知道这些类应该在哪个包里会很好。 - mjs
@mmm 好久不见了,但应该是类似于 org.apache.batik.transcoder 或其子包。 - Devon_C_Miller
我的程序报告说:“org.apache.batik.transcoder.TranscoderException: Unspecified transcoding hints: KEY_DOM_IMPLEMENTATION”,提示来自于hints.put(ImageTranscoder.KEY_WIDTH, (float)1); 我不知道原因。 - M_L_Sing_Jump_Rap
@M_L_Sing_Jump_Rap 这是一个13年前的答案,所以肯定有一些变化。请参考John Doppelmann和Dov Wasserman的答案,其中包括了一些额外的提示。但请注意,那还是7年前的事情,所以可能需要更多的变化。 - Devon_C_Miller

4

我刚刚按照Devon的方法使用了Batik-1.7。

然而,为了使其正常工作,我不得不向hints对象添加以下内容:

MyTranscoder transcoder =new MyTranscoder()

DOMImplementation impl = SVGDOMImplementation.getDOMImplementation();
TranscodingHints hints = new TranscodingHints();
hints.put(ImageTranscoder.KEY_WIDTH, width); // e.g. width=new Float(300)
hints.put(ImageTranscoder.KEY_HEIGHT,height);// e.g. height=new Float(75)
hints.put(ImageTranscoder.KEY_DOM_IMPLEMENTATION, impl.getDOMImplementation());
hints.put(ImageTranscoder.KEY_DOCUMENT_ELEMENT_NAMESPACE_URI,SVGConstants.SVG_NAMESPACE_URI);
hints.put(ImageTranscoder.KEY_DOCUMENT_ELEMENT_NAMESPACE_URI,SVGConstants.SVG_NAMESPACE_URI);
hints.put(ImageTranscoder.KEY_DOCUMENT_ELEMENT, SVGConstants.SVG_SVG_TAG);
hints.put(ImageTranscoder.KEY_XML_PARSER_VALIDATING, false);

transcoder.setTranscodingHints(hints);
TranscoderInput ti=new TranscoderInput(uri)
transcoder.transcode(ti, null);
BufferedImage image = transcoder.getImage();

看起来在batik的XMLAbstractTranscoder(http://svn.apache.org/repos/asf/xmlgraphics/batik/tags/batik-1_7/sources/org/apache/batik/transcoder/XMLAbstractTranscoder.java)中有更新,版本为1.7。


3
如果您不再希望在应用程序中包含Batik库的依赖项,可以使用Flamingo SVG Transcoder直接将SVG文件转换为Java2D:它会生成与压缩后的SVG文件大小大致相当的图标类。生成的代码没有外部依赖关系。 http://ebourg.github.com/flamingo-svg-transcoder

验证您的答案是,与其在运行时尝试读取和呈现SVG(并包含所有依赖项),另一种方法是在编译时将SVG转码为类文件(只有构建系统需要batik)。然后将类文件与应用程序打包。 - Ryan

1

我尝试使用Devon和John的建议,这对我几乎有用。但我还需要进行一些微调,具体如下,请随意使用:

package com.corp.util;

import static org.apache.batik.transcoder.SVGAbstractTranscoder.KEY_WIDTH;
import static org.apache.batik.transcoder.XMLAbstractTranscoder.KEY_DOCUMENT_ELEMENT;
import static org.apache.batik.transcoder.XMLAbstractTranscoder.KEY_DOCUMENT_ELEMENT_NAMESPACE_URI;
import static org.apache.batik.transcoder.XMLAbstractTranscoder.KEY_DOM_IMPLEMENTATION;
import static org.apache.batik.util.SVGConstants.SVG_NAMESPACE_URI;
import static org.apache.batik.util.SVGConstants.SVG_SVG_TAG;

import com.google.common.flogger.GoogleLogger;

import org.apache.batik.anim.dom.SVGDOMImplementation;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.TranscodingHints;
import org.apache.batik.transcoder.image.ImageTranscoder;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;

import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Singleton;

/** Loads SVG images from disk. See https://en.wikipedia.org/wiki/Scalable_Vector_Graphics. */
@Singleton
@ThreadSafe
public class SvgImageLoader {

  private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();

  /**
   * Reads in an SVG image file and return it as a BufferedImage with the given width and a height
   * where the original aspect ratio is preserved.
   *
   * @param url URL referencing the SVG image file, which is typically an XML file
   * @param width width in pixels the returned BufferedImage should be
   *
   * @return a valid image representing the SVG file
   * @throws IOException if the file cannot be parsed as valid SVG
   */
  public static BufferedImage loadSvg(URL url, float width) throws IOException {
    SvgTranscoder transcoder = new SvgTranscoder();
    transcoder.setTranscodingHints(getHints(width));
    try {
      TranscoderInput input = new TranscoderInput(url.openStream());
      transcoder.transcode(input, null);
    } catch (TranscoderException e) {
      throw new IOException("Error parsing SVG file " + url, e);
    }
    BufferedImage image = transcoder.getImage();
    logger.atInfo().log("Read '%s' SVG image from disk requested with width=%.1f, sized as %dx%d pixels.",
        new File(url.getFile()).getName(), width, image.getWidth(), image.getHeight());
    return image;
  }

  private static TranscodingHints getHints(float width) {
    TranscodingHints hints = new TranscodingHints();
    hints.put(KEY_DOM_IMPLEMENTATION, SVGDOMImplementation.getDOMImplementation());
    hints.put(KEY_DOCUMENT_ELEMENT_NAMESPACE_URI, SVG_NAMESPACE_URI);
    hints.put(KEY_DOCUMENT_ELEMENT, SVG_SVG_TAG);
    hints.put(KEY_WIDTH, width);
    return hints;
  }

  private static class SvgTranscoder extends ImageTranscoder {

    private BufferedImage image = null;

    @Override
    public BufferedImage createImage(int width, int height) {
      image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
      return image;
    }

    @Override
    public void writeImage(BufferedImage img, TranscoderOutput out) {}

    BufferedImage getImage() {
      return image;
    }
  }
}

0
为了避免传递 DOM 参数:transcoder.setTranscodingHints((Map<?, ?>) hints);

1
那应该是给@John Doppelmann的评论,对吧?那么请不要“回答”它,请尽可能使用评论。 - Manuel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接