使用Apache Batik将SVG图像转换为JPEG图像

3
我正在尝试将SVG图像转换为JPEG,如https://xmlgraphics.apache.org/batik/using/transcoder.html#createImage示例所示。以下是代码:
public void saveAsjpeg() throws Exception {

    // Create a JPEG transcoder
    JPEGTranscoder t = new JPEGTranscoder();

    // Set the transcoding hints.
    t.addTranscodingHint(JPEGTranscoder.KEY_QUALITY, new Float(.8));

    // Create the transcoder input.
    String svgURI = new File(inputFilePath).toURL().toString();
    TranscoderInput input = new TranscoderInput(svgURI);

    // Create the transcoder output.
    OutputStream ostream = new FileOutputStream(outputFilePath);
    TranscoderOutput output = new TranscoderOutput(ostream);

    // Save the image.
    t.transcode(input, output);

    // Flush and close the stream.
    ostream.flush();
    ostream.close();
    System.exit(0);
}

以下是我的pom.xml文件。我正在尝试使用Spring Boot项目:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.4.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.xmlgraphics</groupId>
            <artifactId>batik-transcoder</artifactId>
            <version>1.8</version>
        </dependency>

        <dependency>
            <groupId>org.apache.xmlgraphics</groupId>
            <artifactId>batik-codec</artifactId>
            <version>1.8</version>
        </dependency>

        <dependency>
            <groupId>org.apache.xmlgraphics</groupId>
            <artifactId>batik-svgpp</artifactId>
            <version>1.8</version>
        </dependency>

        <dependency>
            <groupId>org.apache.xmlgraphics</groupId>
            <artifactId>batik-rasterizer</artifactId>
            <version>1.8</version>
        </dependency>

        <dependency>
            <groupId>org.apache.xmlgraphics</groupId>
            <artifactId>batik-squiggle</artifactId>
            <version>1.8</version>
        </dependency>

        <dependency>
            <groupId>org.apache.xmlgraphics</groupId>
            <artifactId>xmlgraphics-commons</artifactId>
            <version>2.1</version>
        </dependency>

        <dependency>
            <groupId>org.apache.xmlgraphics</groupId>
            <artifactId>batik-ttf2svg</artifactId>
            <version>1.8</version>
        </dependency>


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-javadoc-plugin</artifactId>
                <executions>
                    <execution>
                        <id>attach-javadocs</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>


</project>

我遇到了以下异常:

org.apache.batik.transcoder.TranscoderException: null
Enclosed Exception:
null
    at org.apache.batik.transcoder.image.ImageTranscoder.transcode(Unknown Source)
    at org.apache.batik.transcoder.XMLAbstractTranscoder.transcode(Unknown Source)
    at org.apache.batik.transcoder.SVGAbstractTranscoder.transcode(Unknown Source)
    at SaveToJPEG.saveAsjpeg(SaveToJPEG.java:31)
    at SaveToJPEG.main(SaveToJPEG.java:42)

我这里有几个问题:

  1. 为什么异常堆栈跟踪说“未知来源”,而且异常信息不够详细?我在谷歌上搜索过,看到如果JAR包没有附带源代码,异常信息可能不够详细。我已经在POM文件中加入插件代码以添加源代码,但这似乎没有起作用。
  2. batik代码有什么问题导致它无法将SVG图像转换为JPEG格式?

1
https://bz.apache.org/bugzilla/show_bug.cgi?id=44682 - Brett Okken
1
它显示未知来源,是因为您使用的Batik jar文件中没有调试信息(例如行号)。这与您是否为项目生成源代码无关。 - Brett Okken
2个回答

7

我不知道是谁发布了这个构件,但它解决了我的问题。

<dependency>
    <groupId>fr.avianey.apache-xmlgraphics</groupId>
    <artifactId>batik</artifactId>
    <version>1.8</version>
</dependency>

这个组件包含以下类

  • org.apache.batik.ext.awt.image.codec.imageio.ImageIOPNGImageWriter
  • org.apache.batik.ext.awt.image.codec.imageio.ImageIOTIFFImageWriter
  • org.apache.batik.ext.awt.image.codec.imageio.ImageIOJPEGImageWriter
这些类可以用于处理图片文件的读写。

这个对我在batik 1.8版本上有效:) :) 非常感谢:) - Namila

3

这是Apache Batik 1.8中的一个错误,参考BATIK-1136

问题如下: JPEGTranscoder使用服务提供程序API获取处理"image/jpeg"格式的ImageWriter实例。然而,在META-INF/services内配置的batik-codec工件中,指向一个类org.apache.batik.ext.awt.image.codec.imageio.ImageIOJPEGImageWriter,但显然未在最终软件包中发布(因为它存在于源代码中)。

因此,有两个解决方案。

降级

使用以下命令将版本降级到1.7:

<dependency>
    <groupId>org.apache.xmlgraphics</groupId>
    <artifactId>batik-transcoder</artifactId>
    <version>1.7</version>
</dependency>
<dependency>
    <groupId>org.apache.xmlgraphics</groupId>
    <artifactId>batik-codec</artifactId>
    <version>1.7</version>
</dependency>
<dependency>
    <groupId>org.apache.xmlgraphics</groupId>
    <artifactId>xmlgraphics-commons</artifactId>
    <version>2.1</version>
</dependency>

这个版本中,类已经正确发布。

复制必要的类

从Apache Batik中复制必要的类到你自己的源代码中。在一个名为org.apache.batik.ext.awt.image.codec.imageio的包内,创建以下两个类。

首先是ImageIOImageWriter

package org.apache.batik.ext.awt.image.codec.imageio;

import java.awt.image.RenderedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.event.IIOWriteWarningListener;
import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.ImageOutputStream;

import org.apache.batik.ext.awt.image.spi.ImageWriter;
import org.apache.batik.ext.awt.image.spi.ImageWriterParams;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class ImageIOImageWriter implements ImageWriter, IIOWriteWarningListener {

    private String targetMIME;
    
    /**
     * Main constructor.
     * @param mime the MIME type of the image format
     */
    public ImageIOImageWriter(String mime) {
        this.targetMIME = mime;
    }
    
    /**
     * @see ImageWriter#writeImage(java.awt.image.RenderedImage, java.io.OutputStream)
     */
    public void writeImage(RenderedImage image, OutputStream out) throws IOException {
        writeImage(image, out, null);
    }

    /**
     * @see ImageWriter#writeImage(java.awt.image.RenderedImage, java.io.OutputStream, ImageWriterParams)
     */
    public void writeImage(RenderedImage image, OutputStream out, 
            ImageWriterParams params) 
                throws IOException {
        Iterator iter;
        iter = ImageIO.getImageWritersByMIMEType(getMIMEType());
        javax.imageio.ImageWriter iiowriter = null;
        try {
            iiowriter = (javax.imageio.ImageWriter)iter.next();
            if (iiowriter != null) {
                iiowriter.addIIOWriteWarningListener(this);

                ImageOutputStream imgout = null;
                try {
                    imgout = ImageIO.createImageOutputStream(out);
                    ImageWriteParam iwParam = getDefaultWriteParam(iiowriter, image, params);

                    ImageTypeSpecifier type;
                    if (iwParam.getDestinationType() != null) {
                        type = iwParam.getDestinationType();
                    } else {
                        type = ImageTypeSpecifier.createFromRenderedImage(image);
                    }

                    //Handle metadata
                    IIOMetadata meta = iiowriter.getDefaultImageMetadata(
                            type, iwParam);
                    //meta might be null for some JAI codecs as they don't support metadata
                    if (params != null && meta != null) {
                        meta = updateMetadata(meta, params); 
                    }

                    //Write image
                    iiowriter.setOutput(imgout);
                    IIOImage iioimg = new IIOImage(image, null, meta);
                    iiowriter.write(null, iioimg, iwParam);
                } finally {
                    if (imgout != null) {
                        imgout.close();
                    }
                }
            } else {
                throw new UnsupportedOperationException("No ImageIO codec for writing " 
                        + getMIMEType() + " is available!");
            }
        } finally {
            if (iiowriter != null) {
                iiowriter.dispose();
            }
        }
    }
    
    /**
     * Returns the default write parameters for encoding the image.
     * @param iiowriter The IIO ImageWriter that will be used
     * @param image the image to be encoded
     * @param params the parameters for this writer instance
     * @return the IIO ImageWriteParam instance
     */
    protected ImageWriteParam getDefaultWriteParam(
            javax.imageio.ImageWriter iiowriter, RenderedImage image, 
            ImageWriterParams params) {
        ImageWriteParam param = iiowriter.getDefaultWriteParam();
        if ((params != null) && (params.getCompressionMethod() != null)) {
            param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
            param.setCompressionType(params.getCompressionMethod());
        }
        return param; 
    }
    
    /**
     * Updates the metadata information based on the parameters to this writer.
     * @param meta the metadata
     * @param params the parameters
     * @return the updated metadata
     */
    protected IIOMetadata updateMetadata(IIOMetadata meta, ImageWriterParams params) {
        final String stdmeta = "javax_imageio_1.0";
        if (meta.isStandardMetadataFormatSupported()) {
            IIOMetadataNode root = (IIOMetadataNode)meta.getAsTree(stdmeta);
            IIOMetadataNode dim = getChildNode(root, "Dimension");
            IIOMetadataNode child;
            if (params.getResolution() != null) {
                child = getChildNode(dim, "HorizontalPixelSize");
                if (child == null) {
                    child = new IIOMetadataNode("HorizontalPixelSize");
                    dim.appendChild(child);
                }
                child.setAttribute("value", 
                        Double.toString(params.getResolution().doubleValue() / 25.4));
                child = getChildNode(dim, "VerticalPixelSize");
                if (child == null) {
                    child = new IIOMetadataNode("VerticalPixelSize");
                    dim.appendChild(child);
                }
                child.setAttribute("value", 
                        Double.toString(params.getResolution().doubleValue() / 25.4));
            }
            try {
                meta.mergeTree(stdmeta, root);
            } catch (IIOInvalidTreeException e) {
                throw new RuntimeException("Cannot update image metadata: " 
                            + e.getMessage());
            }
        }
        return meta;
    }
    
    /**
     * Returns a specific metadata child node
     * @param n the base node
     * @param name the name of the child
     * @return the requested child node
     */
    protected static IIOMetadataNode getChildNode(Node n, String name) {
        NodeList nodes = n.getChildNodes();
        for (int i = 0; i < nodes.getLength(); i++) {
            Node child = nodes.item(i);
            if (name.equals(child.getNodeName())) {
                return (IIOMetadataNode)child;
            }
        }
        return null;
    }

    /**
     * @see ImageWriter#getMIMEType()
     */
    public String getMIMEType() {
        return this.targetMIME;
    }

    /**
     * @see javax.imageio.event.IIOWriteWarningListener#warningOccurred(javax.imageio.ImageWriter, int, java.lang.String)
     */
    public void warningOccurred(javax.imageio.ImageWriter source, 
            int imageIndex, String warning) {
        System.err.println("Problem while writing image using ImageI/O: " 
                + warning);
    }
}

然后是ImageIOJPEGImageWriter:
package org.apache.batik.ext.awt.image.codec.imageio;

import java.awt.image.RenderedImage;

import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;

import org.apache.batik.ext.awt.image.codec.imageio.ImageIOImageWriter;
import org.apache.batik.ext.awt.image.spi.ImageWriterParams;

public class ImageIOJPEGImageWriter extends ImageIOImageWriter {

    private static final String JPEG_NATIVE_FORMAT = "javax_imageio_jpeg_image_1.0";

    /**
     * Main constructor.
     */
    public ImageIOJPEGImageWriter() {
        super("image/jpeg");
    }

    /** {@inheritDoc} */
    @Override
    protected IIOMetadata updateMetadata(IIOMetadata meta, ImageWriterParams params) {
        //ImageIODebugUtil.dumpMetadata(meta);
        if (JPEG_NATIVE_FORMAT.equals(meta.getNativeMetadataFormatName())) {
            meta = addAdobeTransform(meta);

            IIOMetadataNode root = (IIOMetadataNode)meta.getAsTree(JPEG_NATIVE_FORMAT);

            IIOMetadataNode jv = getChildNode(root, "JPEGvariety");
            if (jv == null) {
                jv = new IIOMetadataNode("JPEGvariety");
                root.appendChild(jv);
            }
            IIOMetadataNode child;
            if (params.getResolution() != null) {
                child = getChildNode(jv, "app0JFIF");
                if (child == null) {
                    child = new IIOMetadataNode("app0JFIF");
                    jv.appendChild(child);
                }
                //JPEG gets special treatment because there seems to be a bug in
                //the JPEG codec in ImageIO converting the pixel size incorrectly
                //(or not at all) when using standard metadata format.
                child.setAttribute("majorVersion", null);
                child.setAttribute("minorVersion", null);
                child.setAttribute("resUnits", "1"); //dots per inch
                child.setAttribute("Xdensity", params.getResolution().toString());
                child.setAttribute("Ydensity", params.getResolution().toString());
                child.setAttribute("thumbWidth", null);
                child.setAttribute("thumbHeight", null);

            }

            try {
                meta.setFromTree(JPEG_NATIVE_FORMAT, root);
            } catch (IIOInvalidTreeException e) {
                throw new RuntimeException("Cannot update image metadata: "
                            + e.getMessage(), e);
            }

            //ImageIODebugUtil.dumpMetadata(meta);
        }

        return meta;
    }

    private static IIOMetadata addAdobeTransform(IIOMetadata meta) {
        // add the adobe transformation (transform 1 -> to YCbCr)
        IIOMetadataNode root = (IIOMetadataNode)meta.getAsTree(JPEG_NATIVE_FORMAT);

        IIOMetadataNode markerSequence = getChildNode(root, "markerSequence");
        if (markerSequence == null) {
            throw new RuntimeException("Invalid metadata!");
        }

        IIOMetadataNode adobeTransform = getChildNode(markerSequence, "app14Adobe");
        if (adobeTransform == null) {
            adobeTransform = new IIOMetadataNode("app14Adobe");
            adobeTransform.setAttribute("transform" , "1"); // convert RGB to YCbCr
            adobeTransform.setAttribute("version", "101");
            adobeTransform.setAttribute("flags0", "0");
            adobeTransform.setAttribute("flags1", "0");

            markerSequence.appendChild(adobeTransform);
        } else {
            adobeTransform.setAttribute("transform" , "1");
        }

        try {
            meta.setFromTree(JPEG_NATIVE_FORMAT, root);
        } catch (IIOInvalidTreeException e) {
            throw new RuntimeException("Cannot update image metadata: "
                        + e.getMessage(), e);
        }
        return meta;
    }

    /** {@inheritDoc} */
    @Override
    protected ImageWriteParam getDefaultWriteParam(
            ImageWriter iiowriter, RenderedImage image,
            ImageWriterParams params) {
        JPEGImageWriteParam param = new JPEGImageWriteParam(iiowriter.getLocale());
        param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
        param.setCompressionQuality(params.getJPEGQuality());
        if (params.getCompressionMethod() != null
                && !"JPEG".equals(params.getCompressionMethod())) {
            throw new IllegalArgumentException(
                    "No compression method other than JPEG is supported for JPEG output!");
        }
        if (params.getJPEGForceBaseline()) {
            param.setProgressiveMode(JPEGImageWriteParam.MODE_DISABLED);
        }
        return param;
    }


}

保留版本1.8并添加上述类,代码将按原样工作。

@MamathaVRao 嗯,那很奇怪。所以我用你提问中的代码和我回答中的Maven依赖进行了测试,它可以正常工作而没有错误。您能确保您只有那3个与Batik相关的依赖项吗?您确定类路径中没有任何1.8的东西吗? - Tunaki
你是说我只需要这三个依赖项吗?我已经按照你的建议将所有依赖项及其版本与这三个依赖项一起放置,但我仍然遇到了异常情况。 - User1230321
只有这三个依赖项,我在maven构建时遇到了http://cwiki.apache.org/confluence/display/MAVEN/PluginExecutionException。我有办法向您展示我的代码吗? - User1230321
后面的解决方法“复制必要的类”并不能完全解决问题。我尝试过,编译完美,但如果你在svg源代码中使用preserveAspectRatio="xMidYMid slice"渲染jpeg图像,则结果jpeg图像不会被切片,并且会重叠其他元素。使用第一个解决方法“降级”,可以按预期工作。 - Dieter Tremel
@DieterTremel 很有趣,也许还有其他类可以复制用于此用例... 降级是更简单的选择。 - Tunaki
显示剩余4条评论

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