SVG support based on SVG Salamander and Batik
mgarin opened this issue · 5 comments
Provide additional tools to simplify SVG graphics usage within components.
Here is a set of planned features:
-
Convenient implementation of Swing
Icon
for the SVG graphics -
Easy way to retrieve
BufferedImage
of specific size from loaded SVG image -
SVG support based on SVG Salamander free open-source library
-
SVG support based on Apache Batik free open-source free library
-
Separate
svg
module - would contain base API for providing SVG graphics loading support and won't be useful without a second module containing the actual implementation -
Separate
svg-salamander
module - would contain SVG Salamander implementation -
Separate
svg-batik
module - would contain Apache Batik implementation
Since this will certainly involve additional 3rd-party library or libraries - it should be placed into separate module. It will work perfectly along with other v1.3.0 module changes: #336
Some part of this enhancement request will be available with v1.2.9 release as SVG icons are quite convenient since they can be re-used for skins with different color schemes.
I have updated the main issue text with more information on planned stuff.
At this point it is mostly about adding Apache Batik support and separating all implementations from the API I've got in the IconManager
. That should help keeping code clean of any SVG library-related mess and to allow developers to choose which library they want SVG graphics to be loaded and modified with.
I should generally say that I'd prefer using SVG Salamander as it is lightweight and has a really convenient API, but I've encountered quite a few issues with multiple SVG files and sometimes it is hard to say what exactly goes wrong.
On the other hand Apache Batik has extensive support for almost all SVG features (which you would probably never use anyway, but still) and does succeed at loading almost all SVG files. It also has slightly higher performance at painting more complex images. But the amount of dependencies it drags along is atrocious and it is the reason why I didn't go for it initially.
Overall - there is no best solution, but there are options and I want to allow WebLaF users to have those options (and maybe more in the future as well?) instead of hardcoding a single solution I prefer.
As a side note, I once dragged Batik as a dependency in a project, and it was a nightmare; it transitively depends on zillions of other libraries which are not even consistently compatible to each other; I also found Batik's Java2D rendering extremely ugly, but perhaps I wasn't investing enough energy to understand how to tune it. For me, that project was a no-go, a good example of a failed humongous Java project that is just a big pile of spaghetti.
As a side note, I once dragged Batik as a dependency in a project, and it was a nightmare; it transitively depends on zillions of other libraries which are not even consistently compatible to each other;
Couldn't agree more. If not the dependencies I would've gone with Batik from the beginning.
I also found Batik's Java2D rendering extremely ugly, but perhaps I wasn't investing enough energy to understand how to tune it
I didn't find a good way to use Batik first time I tried it either and I don't consider their JSVGCanvas
or JSVGComponent
as a good solution at all - those are basically inconvenient/useless on practice.
After a bit more research though I found a way to do everything I did with SVG Salamander using Batik. Here is a short example of how to create WebLaF's SvgIcon
analogue:
public class BatikSvgIcon implements Icon
{
private final SVGDocument svgDocument;
private final GraphicsNode svgIcon;
public BatikSvgIcon ( URL url ) throws Exception
{
final String xmlParser = XMLResourceDescriptor.getXMLParserClassName ();
final SAXSVGDocumentFactory df = new SAXSVGDocumentFactory ( xmlParser );
svgDocument = df.createSVGDocument ( url.toString () );
final UserAgent userAgent = new UserAgentAdapter ();
final DocumentLoader loader = new DocumentLoader ( userAgent );
final BridgeContext ctx = new BridgeContext ( userAgent, loader );
ctx.setDynamicState ( BridgeContext.DYNAMIC );
final GVTBuilder builder = new GVTBuilder ();
svgIcon = builder.build ( ctx, svgDocument );
}
public void paintIcon ( Component c, Graphics g, int x, int y )
{
paintSvgIcon ( ( Graphics2D ) g, x, y );
}
private void paintSvgIcon ( Graphics2D g, int x, int y )
{
g.setRenderingHint ( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
AffineTransform transform = new AffineTransform ( 1.0, 0.0, 0.0, 1.0, x, y );
svgIcon.setTransform ( transform );
svgIcon.paint ( g );
}
public int getIconWidth ()
{
return ( int ) svgIcon.getGeometryBounds ().getWidth ();
}
public int getIconHeight ()
{
return ( int ) svgIcon.getGeometryBounds ().getHeight ();
}
}
You can also modify SVG structure through svgDocument
in a similar way you can do it using SVG Salamander and changes are reflected upon the next paint operation.
Batik's API is not too bad if you get into it, I guess it just became overly complicated after all the features that been added there.
And well, there is one good reason to choose Batik over other options - performance. Painting operations mostly take twice less time than with other libraries I've tried. Not like its a huge difference when we're talking about 4ms vs 2ms, but unfortunately it all adds up and slows down Swing UI due to its single-thread nature.
In either case, I was thinking to add Batik implementation in a separate module, so you can always throw that one away safely if you don't want to drag all the dependencies and use SVG Salamander implementation (or even write your own if needed based on WebLaF's API).