A VectorImage
contains vector-based rendering instructions to represent/recreate an image.
Some possible use cases include storing painting instructions:
VectorImage myImage = new VectorImage(); Graphics2D g = myImage.createGraphics(); myComponent.paint(g); g.dispose();
Or replaying those instructions:
VectorImage myImage = ...; myImage.paint(otherGraphics2D);
Or inspecting painting instructions:
VectorImage myImage = ...; Listops = myImage.getOperations(); for(Operation op : ops) { // alter or examine certain operations }
Or serialization:
VectorImage myImage = new VectorImage(inputStream); myImage.save(outputStream);
There are com.pump.graphics.vector.Operation
objects that can represent all the methods in Graphics2D
that render data. And the VectorGraphics2D
stores a running list of these Operations
.
The VectorImage
is just a wrapper for that List
of operations.
Also this architecture introduces the Graphics2DContext
object to document the state (stroke, clipping, transform, etc.) of any Graphics2D
and its operations.
The original intended use case for this class was for debugging. Have you ever had a stray line or artifact in your UI? This object lets you diagram every rendering operation. You can even add the stack trace that triggered each call, so you can pinpoint exactly what code is responsible for rendering each pixel.
(If you take a screenshot in the demo by pressing F6, then as you adjust the demo's slider you see a tooltip showing exactly what line of source code each rendering operation originated from.)
But once I had the VectorImage
in my toolbelt I found lots of other uses for it. For example: most of the sliders in this showcase app include a popover that points directly at the thumb. How do I know where the thumb is? As long as the SliderUI
is a BasicSliderUI
, I can call:
VectorImage img = new VectorImage(); myBasicSliderUI.paintThumb(img.createGraphics()); Rectangle2D thumbBounds = img.getBounds();
Also this architecture is used in the QHTML classes in a few places. For example: we use it to apply shadows to text. We render everything using Swing's default renderer to a VectorGraphics2D
, and then (with the help of some well-placed RenderingHints
) we isolate each Operation
and apply a shadow as needed.
Making the VectorImage
serializable was a lot more work than expected.
Many java.awt classes are not Serializable: java.awt.BasicStroke
, java.awt.AlphaComposite
, java.awt.Shape
, java.awt.RenderingHints
, etc.
I ended up creating a whole separate architecture just to address this. Its core usage boils down to:
Object obj = ConverterUtils.readObject(objectInputStream);
... and ...
ConverterUtils.writeObject(objectOutputStream, obj);
The ConverterUtils
class includes over a dozen filters (BeanMapConverters
) that break up unserializable classes into named key/value pairs. So in addition to serialization it also includes helper methods like:
String str = ConverterUtils.toString(obj);
... and ...
ConverterUtils.equals(obj1, obj2);
If you're familiar with vector graphics then you're probably familiar with the SVG file format.
It seemed like a natural extension of this project to try to serialize the VectorImage
too. I decided to use the file extension JVG ("Java Vector Graphics").
Obviously there are a couple of similarities between SVG and JVG, but they are also radically different. In summary: SVG is complex and powerful, and JVG is just a serialized wrapper for what the Graphics2D
supports. SVG supports animation, scripts, and filters. And it is written in XML, which makes it slightly human-readable.
(It's also worth mentioning that the VectorImage
doesn't even promise to perfectly replicate all Graphics2D
calls. It works with simple BufferedImages
, but if you use a more complex or abstract image (like an animated GIF loaded from the AWT toolkit, or like a MultiResolutionImage
), then it will only serialize a frozen snapshot (BufferedImage
) for one-time use.)
What they have in common is vector scalability. Vector data (esp text and shapes) can be printed at high resolutions without being pixelated. So this is especially useful as we migrate to 4K monitors. And although I haven't formally set up any benchmark comparisons, I like to think that "deserialized rendering operations" will generally outperform most "SVG parsers" in the real world. (At least a little.)
The demo in this showcase app includes 4 JVG files. These were created by grabbing SVG files from openclipart.org and converting them to Java source code with the Flamingo SVG transcoder.