[SOLVED] Can JME use SVG files?

I want to know If I can use SVGs so I don’t have to make really high resolution graphics. Can you?

SVG’s are vector graphics - they describe an image in terms of colored areas and lines instead of in pixels, and turning them into a pixel image for display on a screen is a long and complicated process. Graphics cards require textures to be in pixels. So no, you can’t directly use SVGs in jME - there’s nothing preventing you from turning them into a PNG (Inkscape works great for this) and using that, however.

2 Likes

Thanks

Open your SVGs in a program like Photoshop, of Gimp, and just scale then up to a really high resolution, then save as a PNG, so they will still retain most of the quality and crispness that a vector graphic would have.

You don’t necessarily want the resolution to be too high - it uses lots of extra texture memory that you don’t need unless your image takes up many pixels on-screen (i.e., UI elements). At best it’s a waste of memory. At worst you’ll get scaling artifacts if it doesn’t take up many pixels on-screen.

It’s generally best to convert assets to JME’s preferred formats before deploying your game.

However, if you really need to rasterize SVGs at runtime, there are open-source libraries (such as svgSalamander and Apache Batik you can use.

1 Like

How can I refrence the SVG images in Textures/<image>.svg?

AssetInfo info = assetManager.locateAsset(new AssetKey("Textures/<image>.svg"));

I looked at the assetKey, and the location only led back to Textures/<image>.svg

In order to load an SVG asset in that manner, you’d first need to write a loader (importer) for the SVG format and associate it with the .svg extension, like so:

assetManager.registerLoader(SvgLoader.class, "svg");

Salamander or Batik might save you a lot of effort toward writing an SVG loader, but they won’t get you 100% of the way.

I’m doing something wrong…

I get this when I try to use the texture.
I am using imageToAWT from jme3tools and svgSalamander.
This is what the image should be:

My AssetLoader:

public class SVGTextureLoader implements AssetLoader {
    public Image load(AssetInfo assetInfo) throws IOException {
        System.out.print(assetInfo.toString());
        InputStream stream = assetInfo.openStream();
        try {
            return SVGUtil.instance.createJMEImage(SVGUtil.instance.getSVGDiagram(stream), 100, 100);
            //return new Texture2D(image);
        } catch (SVGException ex) {
            Logger.getLogger(SVGTextureLoader.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }
}

My AssetKey:

public class SVGTextureKey extends TextureKey {
    public int width, hight;
    public SVGTextureKey(String name, int width, int height) {
        super(name);
        this.width = width;
        this.hight = height;
    }
    public Class<? extends AssetProcessor> getProcessorType(){
        return TextureProcessor.class;
    }
}

and my deal with everything else class (SVGUtil)

public class SVGUtil {
    public static SVGUtil instance = new SVGUtil();
    protected SVGUniverse universe;
    int svgn = 0;
    public SVGUtil() {
        universe = new SVGUniverse();
    }
    public SVGDiagram getSVGDiagram(InputStream stream) throws IOException {
        return universe.getDiagram(universe.loadSVG(stream, "SVG_IMAGE-" + (svgn++), false));
    }
    public BufferedImage createBufferedImage(SVGDiagram diagram, int width, int height) throws SVGException {
        diagram.setIgnoringClipHeuristic(true);
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D graphics2D = image.createGraphics();
        diagram.render(graphics2D);
        return image;
    }
    public Image createJMEImage(BufferedImage image) {
        ByteBuffer buf = ByteBuffer.allocateDirect(image.getHeight() * image.getWidth() * 4);
        ImageToAwt.convert(image, Image.Format.ABGR8, buf);
        return new Image(Image.Format.ABGR8, 640, 480, buf);
    }
    public Image createJMEImage(SVGDiagram diagram, int width, int height) throws SVGException {
        return createJMEImage(createBufferedImage(diagram, width, height));
    }
    public static void addSVGSupportToAssetManager(AssetManager manager) {
        manager.registerLoader(SVGTextureLoader.class, "svg");
    }
    public static Texture loadSVGTexture(AssetManager assets, String path, int width, int height, boolean repeat, boolean generateMips ) {
        System.out.println("inl");
        SVGTextureKey key = new SVGTextureKey(path, width, height);
        //key.setGenerateMips(generateMips);

        Texture t = assets.loadAsset(key);
        
        if( t == null ) {
            throw new RuntimeException("Error loading SVG texture:" + path);
        }

        if( repeat ) {
            t.setWrap(Texture.WrapMode.Repeat);
        } else {
            // JME has deprecated Clamp and defaults to EdgeClamp.
            // I think the WrapMode.EdgeClamp javadoc is totally bonkers, though.
            t.setWrap(Texture.WrapMode.EdgeClamp);
        }

        return t;
    }
}

If it’s obvious what i’m doing wrong, please tell me.

I don’t have time to dig into your code right now, but I can tell you that you’ll have a much easier time if you convert your SVG files to PNG. If you do that, all your code above turns into a simple

assetManager.loadTexture(...);
1 Like

If you want to convert a BufferedImage to a JME Image then the easiest way is with the method on AWTLoader.

1 Like

Ok, this is going to sound kind of dumb. I realized last night, while running my android compilation, that “Oh yea… Android dosen’t have java.awt” And SvgSalamander is full of Graphics2D BufferedImage, etc… I haven’t looked yet, but I’ guessing Batic has it too. Should I just copy over all the required files from java.awt?

That’s likely to be very difficult to do - AWT often ends up working with a native layer, so it’s definitely not as simple as copying over a few class files.

You’re far better off avoiding loading SVGs by converting them to PNG ahead of time if that’s an option for you. Is there a specific reason you want to load SVG?

I just wanted to have images that will scale well. Vector graphics solves that problem. I figured out awt was a lost cause when I ran into this.

static void loadLibraries() {
    if (!loaded) {
        java.security.AccessController.doPrivileged(
            new java.security.PrivilegedAction<Void>() {
                public Void run() {
                    System.loadLibrary("awt");
                    return null;
                }
            });
        loaded = true;
    }
}

I figured out how to do it. Use the androidSVG library to get an android Picture object

https://bigbadaboom.github.io/androidsvg

Write the picture into a bitmap, and use it in a modified AndroidBufferImageLoader.load
It works.

1 Like

Do you know how to scale a svgSalamander document?

Here is the code I have. you can scale down, but not up with this.

public BufferedImage createBufferedImage(InputStream stream, int width, int height) throws SVGException, IOException {
    SVGDiagram diagram = getSVGDiagram(stream);
    diagram.setIgnoringClipHeuristic(true);
    //width = 100; for testing
    //height = 100;
    BufferedImage usimage = new BufferedImage((int)diagram.getWidth(), (int)diagram.getHeight(), BufferedImage.TYPE_INT_ARGB);
    Graphics2D usg = usimage.createGraphics();
    System.out.println(diagram.getWidth());
    usg.scale(width / diagram.getWidth(), height / diagram.getHeight());
    diagram.render(usg);
    BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
    Graphics2D g = image.createGraphics();
    g.drawImage(usimage, 0, 0, null);
    return image;
}

This is true, but 99% of the time vector graphics won’t actually help you with runtime art in game dev. When you’re converting them to bitmaps in code you lose the scalability - exactly what would happen if you convert to PNG before loading them. When you’re using the texture in your game, it will pixelate identically to a plain ol’ raster texture because by the time it gets to the GPU it is one. The one and only exception to this would be if, for example, you re-render the SVG at exactly the resolution it will appear on the viewport (you could use this for a HUD, for example), but there are far easier (and dramatically less computationally expensive) methods to achieve the same thing, such as TbT Quads (“nine patches”).

The only situation I can think of where an SVG would arguably help would be GUI icons that you need in multiple resolutions - and in that case, I’d still argue in favor of just converting them offline to the sizes you need. As you’ve discovered, SVG renderers are a minefield.

Edit: Solved

I’m trying to find a way to convert them before turning them into bitmaps. I was just asking if there was a builtin method to scaling them using svgSalamander, or should I write my own.
Besides, the android version actually does scale properly, and before it is turned into a bitmap

1 Like

A bitmap is just a raster/pixel image. Turning your vector graphic into pixels is the conversion. An SVG is just a 2D scene graph (like jME you can even group things under nodes). Also like jME, 1.0 in this scenegraph’s coordinates is whatever you want it to be - it could be a meter, it could be a pixel, it could be 0.5 miles. That’s why “scaling” an SVG is essentially meaningless (and why they’re so handy for scalable graphics) - you’re just making meaningless relative coordinates bigger. When you convert to a pixel image, then the coordinates are meaningful because they determine which features of the SVG fall on which pixels. I can’t speak for how svgSalamander or Batik handle expressing pixel sizes, but in an offline renderer like Inkscape you typically express something like “here’s a drawing, and I want a 64x64 pixel PNG.” Those APIs will have something similar (as it seems you’ve discovered). Your SVGs are displaying nicely because you’re displaying them at the same pixel resolution they were rendered from SVG → image at. If you vary from that, they’ll have the same scaling problems raster images do unless you re-render from SVG to pixels. That’s why I personally don’t see the value of embedded SVG renderers for this use - I’d convert to a set of the sizes you need beforehand or use some mesh/uv trickery like a TbT Quad to get the desired effect for a fraction of the trouble and runtime overhead.

At any rate, glad you’ve solved your problem.

Because my game window doesn’t change size, and neither does my phone’s screen, I can reuse the textures generated at startup. I am now using Batik, and it provides an easy way to convert at the requested resolution.