SVG bounding box


#1

Consider this SVG file which is a 100×100 image with a circle in teh top left corner:

<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
  <circle cx="20" cy="20" r="10"/>
</svg>

If you load this into a drawable the bounds are wrong:

ScopedPointer<Drawable>  ourDrawable = 
     Drawable::createFromImageData(data, size);
ourDrawable->getDrawableBounds(); // returns 10 10 20 20    
ourDrawable->getBounds(); // also returns 10 10 20 20
// this appears to be the bounding box of all graphic elements in the SVG file

The expected size is 100 by 100, as specified by the viewbox (or by the width and height attributes). This messes up the alignment if you use methods like Drawable::setTransformToFit or Drawable::drawWithin.

You can get the actual size via DrawableComposite::getContentArea, but this is annoying because it won’t work with drawables coming from an image file.

Can this be fixed somehow, or worked around? Maybe existing code relies on the current behaviour, but on the other hand, image files (especially icons) often have padding for a good reason.


#2

Why even use Drawable? Why not just use the Image class directly?

Image splash;
splash.setImage(ImageFileFormat::loadFrom(BinaryData::splash_png, BinaryData::Splash_pngSize));

//////////////////
void paint(Graphics& g)
{
    g.drawImage(splash, Rectangle<int>(x,y,w,h) );
}


#3

Try calling DrawableComposite::resetBoundingBoxToContentArea() before using it?

@matkatmusic It’s always better to prefer SVG rather than bitmaps wherever possible.


#4

same issue:


#5

No, that doesn’t change anything.

To be clear:

Here’s the SVG file as displayed by Inkscape:
image

The expected result is that methods like drawWithin will take the viewbox in the SVG file into account: so drawing the SVG file results in the following [2]:

image

but the actual result is [1]:

image

Calling DrawableComposite::resetBoundingBoxToContentArea() doesn’t appear to change anything: the result of painting the SVG file is the same, and the return values of ourDrawable->getDrawableBounds() and ourDrawable->getBounds() stay the same.

I can also call setBoundingBox or setBounds with the original view box, but drawWithin will still stretch the circle to the destination area. The latter does change the return value of getBounds.

So I’m not sure whether or not this is considered a bug. Compare this to Graphics::drawImageWithin which doesn’t automatically crop images with transparent borders.


[1] Put this code fragment in MainComponent::paint in the Hello World example:

// load:
const char data[] =
    "<svg viewBox=\"0 0 100 100\" xmlns=\"http://www.w3.org/2000/svg\">"
    "  <circle cx=\"20\" cy=\"20\" r=\"10\"/>"
    "</svg>";
ScopedPointer<Drawable> ourDrawable = Drawable::createFromImageData(data, sizeof(data));
// box:
 juce::Rectangle<float> picBounds(10, 10, 100, 100);
g.drawRect(picBounds.expanded(1.f), 1.f);
// svg file:
ourDrawable->drawWithin(g, picBounds, RectanglePlacement::stretchToFit, 1.f);

[2] As a workaround here’s an alternative fragment which gives the desired result:

// svg file:
juce::Rectangle<float> svgBounds =
      ((DrawableComposite*) ourDrawable.get())->getContentArea().resolve(nullptr);
juce::RectanglePlacement ourPlacement(RectanglePlacement::stretchToFit);
juce::AffineTransform xform = ourPlacement.getTransformToFit (svgBounds, picBounds);
ourDrawable->draw(g, 1.f, xform);

How do you use vector file with Graphics class?
#6

OK, but that’s not really a bug, it’s just the default behaviour of drawWithin, and I can’t change it without changing the behaviour of people’s existing code.

What you call a workaround there is actually the best way to do what you want… It looks like a lot of code the way you wrote it, but isn’t so scary if you were to simplify it, e.g. by creating a function to do it:

static void drawToFit (juce::Graphics& g, juce::DrawableComposite& svg, juce::Rectangle<float> targetArea)
{
    svg.draw (g, 1.0f,
              juce::RectanglePlacement (juce::RectanglePlacement::stretchToFit)
                  .getTransformToFit (svg.getContentArea().resolve ({}), targetArea));
}

#7

Allright, thanks.


#8

jules, your code doesn’t seem to work while roeland’s longer version does work.

or I just can’t figure it out.


#9

The code in my post is just a tidied-up version of what he wrote… (But I probably didn’t actually test it, it could have typos in it)


#10

Wowza, after struggling with this for two days, this thread saved me, can finally trust the bounds in an SVG again lol


#11

resolve function is no longer going to compile because getContentArea returns Rectangle instead of RelativeRectangle with the latest JUCE… being a noob, no idea what’s going on.

Edit: no longer able to draw a vector at my desired size.

Edit: Plz someone explain how to use an svg file like one would expect, where resizing it does not cause it to become pixelated.


#12

Just don’t call resolve()!


#13

when I try to resize the vector with affine transformation it resamples the vector! I get a pixelated mess where a smooth vector should be.


#14

Maybe check that your SVG hasn’t been rendered with an embedded bitmap?


#15

omg… you are right, it was an embedded image.