ImageHandler.java

package io.brunoborges.jairosvg.draw;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;

import javax.imageio.ImageIO;

import io.brunoborges.jairosvg.dom.Node;
import io.brunoborges.jairosvg.surface.Surface;
import io.brunoborges.jairosvg.util.UrlHelper;

import static io.brunoborges.jairosvg.util.Helpers.*;

/**
 * SVG image element handler. Port of CairoSVG image.py
 */
public final class ImageHandler {

    private ImageHandler() {
    }

    /** Draw an image node. */
    public static void image(Surface surface, Node node) {
        String href = node.getHref();
        if (href == null || href.isEmpty())
            return;

        String baseUrl = node.get("{http://www.w3.org/XML/1998/namespace}base");
        if (baseUrl == null && node.url != null) {
            int lastSlash = node.url.lastIndexOf('/');
            baseUrl = lastSlash >= 0 ? node.url.substring(0, lastSlash + 1) : null;
        }

        UrlHelper.ParsedUrl url = UrlHelper.parseUrl(href, baseUrl);
        byte[] imageBytes;
        try {
            imageBytes = node.fetchUrl(url, "image/*");
        } catch (IOException e) {
            return;
        }

        if (imageBytes == null || imageBytes.length < 5)
            return;

        double x = size(surface, node.get("x"), "x");
        double y = size(surface, node.get("y"), "y");
        double width = size(surface, node.get("width"), "x");
        double height = size(surface, node.get("height"), "y");
        double opacity = 1;
        String opacityStr = node.get("opacity");
        if (opacityStr != null)
            opacity = Double.parseDouble(opacityStr);

        // Check if it's an SVG image
        if (isSvgContent(imageBytes)) {
            try {
                Node tree = Node.parseTree(imageBytes, url.getUrl(), node.urlFetcher, node.unsafe);
                double[] nf = nodeFormat(surface, tree, false);
                double treeWidth = nf[0], treeHeight = nf[1];
                if (treeWidth == 0)
                    treeWidth = width;
                if (treeHeight == 0)
                    treeHeight = height;

                node.imageWidth = treeWidth;
                node.imageHeight = treeHeight;
                double[] ratio = preserveRatio(surface, node, width, height);

                var savedTransform = surface.context.getTransform();
                var savedComposite = surface.context.getComposite();
                surface.context.translate(x, y);
                surface.context.scale(ratio[0], ratio[1]);
                surface.context.translate(ratio[2], ratio[3]);
                if (opacity < 1) {
                    surface.context.setComposite(
                            java.awt.AlphaComposite.getInstance(java.awt.AlphaComposite.SRC_OVER, (float) opacity));
                }
                surface.draw(tree);
                surface.context.setComposite(savedComposite);
                surface.context.setTransform(savedTransform);
            } catch (Exception e) {
                // Skip invalid SVG images
            }
            return;
        }

        // Raster image
        try {
            BufferedImage img = ImageIO.read(new ByteArrayInputStream(imageBytes));
            if (img == null)
                return;

            node.imageWidth = img.getWidth();
            node.imageHeight = img.getHeight();
            if (width == 0)
                width = node.imageWidth;
            if (height == 0)
                height = node.imageHeight;

            double[] ratio = preserveRatio(surface, node, width, height);
            var savedTransform = surface.context.getTransform();
            var savedComposite = surface.context.getComposite();
            var savedInterpolation = surface.context.getRenderingHint(java.awt.RenderingHints.KEY_INTERPOLATION);

            surface.context.translate(x, y);
            surface.context.scale(ratio[0], ratio[1]);
            surface.context.translate(ratio[2], ratio[3]);

            if (opacity < 1) {
                surface.context.setComposite(
                        java.awt.AlphaComposite.getInstance(java.awt.AlphaComposite.SRC_OVER, (float) opacity));
            }
            surface.context.setRenderingHint(java.awt.RenderingHints.KEY_INTERPOLATION,
                    java.awt.RenderingHints.VALUE_INTERPOLATION_BILINEAR);

            BufferedImage imageToDraw = img;
            int drawX = 0;
            int drawY = 0;
            if (opacity < 1) {
                imageToDraw = new BufferedImage(img.getWidth() + 2, img.getHeight() + 2, BufferedImage.TYPE_INT_ARGB);
                var g = imageToDraw.createGraphics();
                g.setRenderingHints(surface.context.getRenderingHints());
                g.drawImage(img, 1, 1, null);
                g.dispose();
                drawX = -1;
                drawY = -1;
            }

            surface.context.drawImage(imageToDraw, drawX, drawY, null);

            surface.context.setRenderingHint(java.awt.RenderingHints.KEY_INTERPOLATION,
                    savedInterpolation != null
                            ? savedInterpolation
                            : java.awt.RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
            surface.context.setComposite(savedComposite);
            surface.context.setTransform(savedTransform);
        } catch (IOException e) {
            // Skip unreadable images
        }
    }

    private static boolean isSvgContent(byte[] data) {
        if (data.length < 5)
            return false;
        String start = new String(data, 0, Math.min(data.length, 256));
        return start.contains("<svg") || start.startsWith("<?xml") || start.startsWith("<!DOC")
                || (data[0] == 0x1f && data[1] == (byte) 0x8b); // gzip
    }
}