blackears / svgSalamander

154 stars 56 forks source link

can't easily get element's bounding box in device coordinates #68

Closed medovina closed 3 years ago

medovina commented 3 years ago

I've been experimenting with using svgSalamander for rendering in a game written in Java. Overall it seems to work quite well.

However, I ran into one issue that caused me some trouble. The SVG I'm rendering in the game is fairly complex, so I can't afford to rerender the entire image every time anything changes. So when I update an SVG element (e.g. by changing its color), I'd like to call the Swing method repaint() with the element's bounding box, so that only the region around the element will be repainted. In my experiments I've found that's much faster.

The problem is getting the element's bounding box in device coordinates. svgSalamander does provide a method RenderableElement.getBoundingBox(), which at first looked like exactly what I needed. However, after a bunch of experimentation and reading the svgSalamander source code, I figured out that if I call getBoundingBox() on an SVG element such as a Polygon, I get a bounding box in image coordinates (not device coordinates) in the coordinate system of the element's parent, not the entire SVG image. In other words, even if elements that are higher up in the element hierarchy have their own transforms defined in the SVG file, getBoundingBox() does not apply those transforms. It only applies the transform (if any) defined by the element's immediate parent.

Now, because the documentation is sparse, I'm not sure whether this behavior is by design or a bug. In any case, I worked around it in my application as follows. After I update an SVG element, I call getBoundingBox() to get its bounding box. I then walk up the hierarchy of SVG elements by calling the getParent() method repeatedly. For each ancestor, I call getXForm() to get its transformation, if any. If there is one, I apply it to the bounding box.

When I'm done, I have a bounding box in the coordinates of the entire image. I now need to transform this to device coordinates. The transform viewXform in the SVGRoot object is exactly what I need, but I can't use it because as far as I can tell there is no publicly accessible method that will retrieve this transform or apply it for me. So instead I have to do the work myself. I know the image size (I can get it by calling getBoundingBox() on the SVGRoot object before I specify a viewport) and I know the viewport size, which is defined by my application. So I can construct a transformation between these.

Still, this is a bit inconvenient, and it took me quite a while to figure this out. I'd think this would be a common use case, so it would be great to have some way to get an element's bounding box in device coordinates directly. If there's some easy way to do this today which I haven't noticed, I'd be happy to hear about it.

blackears commented 3 years ago

The coordinates you fetch will be in local coordinate space, so you are doing the right thing to calculate the coordinates in world space. I've added a new method to SVGRoot called calcViewportTransform() that you should be able to use to calculate the viewport transform.

medovina commented 3 years ago

Great, thanks for the fix! I don't see any new commits, however - will your change be in the repository soon?

blackears commented 3 years ago

Should be there now. Sorry.

medovina commented 3 years ago

Great, thanks!