Dropshadow behind SVG Drawable?

gui

#1

Hey all. I’m lost on how to draw a simple drop shadow behind an SVG Drawable. I’ve tried all sorts of things, and the only one with which I’ve had success is to draw the Drawable to a background Image object, then use the DropShadow::drawForImage method to draw a dropshadow for that image onto my graphics context.

I’m not a fan of that approach because it means I have to render my drawable twice: once to the background image, and once to the actual graphics context, though I can optimize it somewhat by holding the background image as a member variable and only redrawing if I need a new size. You might say I should just paste the background image onto the graphics context as well when I need to, but I’ve found that the result looks very pixelated as opposed to rendering my Drawable directly. (And if I’m going to draw larger res and rescale to try to fix that, why not just use pngs in the first place? Feels to me like that would defeat the point of a vector interface.)

It seems to me, from the docs, that I should be able to do something like this:

    DrawablePath* knobCap = static_cast<DrawablePath*>(DrawablePath::createFromImageData(BinaryData::KnobCap_svg, BinaryData::KnobCap_svgSize));

    ColourGradient shadow(Colours::black, x + (width / 2), y + (height / 2), Colours::transparentBlack, width, height, true);

    knobCap->setTransformToFit(dest.reduced(10.f, 10.f), RectanglePlacement::centred);
    knobCap->drawWithin(g, dest, RectanglePlacement::centred, 1.f);
    knobCap->setFill(shadow);
    knobCap->drawWithin(g, dest, RectanglePlacement::centred, 1.f);

The approach here (buggy, I know) being to draw the SVG and also overwrite the SVG’s fill type with a radial gradient representing the shadow, and draw the shadow that way. I’d have to reverse the order in this example and add an offset to the shadow, but this approach quickly hits an error having to do with deleting a non-existant object somewhere underneath the knobCap->setFill call. Perhaps that’s just a bug in JUCE?

I’ve similarly tried using a shadow.drawForPath(g, knobCap->getOutlineAsPath()) approach, but in my case where the SVG is a knob, the outline returned is a ring, and thus the shadow that is drawn is the shadow of a ring, not of a knob (circle).

So at this point I’m kinda just lost. What’s the right way to do this? Thanks


#2

Here’s another attempt, thought I was onto something here:

Rectangle<float> dest(x, y, width, height);

DrawablePath* knobCap = static_cast<DrawablePath*>(DrawablePath::createFromImageData(BinaryData::KnobCap_svg, BinaryData::KnobCap_svgSize));
knobCap->setTransformToFit(dest.reduced(10.f, 10.f), RectanglePlacement::centred);
auto& knobCapPath = knobCap->getPath();
DropShadow shadow(Colours::black, 4, Point<int>(4, 4));

shadow.drawForPath(g, knobCapPath);
knobCap->drawWithin(g, dest, RectanglePlacement::centred, 1.f);

Now this approach fails underneath shadow.drawForPath wherein there’s a call to fillPath with the provided path. At that point, we drop into a Path::Iterator which hits a BAD_ACCESS error trying to iterate the path elements. It seems like path.numElements is totally lost in the Path::Iterator constructor maybe??

For reference, here’s the SVG I’m trying to draw:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="118" height="117" viewBox="0 0 118 117">
  <defs>
    <style>
      .cls-1 {
        stroke: #efefef;
        stroke-linejoin: round;
        stroke-width: 2px;
        fill-rule: evenodd;
        fill: url(#linear-gradient);
      }
    </style>
    <linearGradient id="linear-gradient" x1="651.843" y1="1185.78" x2="651.843" y2="1075.16" gradientUnits="userSpaceOnUse">
      <stop offset="-0.035" stop-color="#bec4c3"/>
      <stop offset="1.035" stop-color="#fff"/>
    </linearGradient>
  </defs>
  <path class="cls-1" d="M651.851,1075.14a55.325,55.325,0,1,1-55.217,55.32A55.27,55.27,0,0,1,651.851,1075.14Z" transform="translate(-593 -1072)"/>
</svg>

This feels to me like the right approach, and the knobCapPath that I get with knobCap->getPath() looks good. Something just breaks in the iterator and I haven’t yet figured that out.

Anybody have any experience/insight with this?


#3

Did you ever get this to work right? If so, what was your solution?


#4

I got close, but ended up abandoning this direction. One big issue I found with my own code above was the static cast to a DrawablePath– the createFromImageData is a method on Drawable which returns a Drawable* and the cast there is not the right way to work with it.

Here’s a snippet from a different part of my codebase that shows what may be the right way to go about it:

    std::unique_ptr<Drawable> logoDrawable (Drawable::createFromImageData(BinaryData::company_logo_svg,
                                                                          BinaryData::company_logo_svgSize));

    m_companyLogo = std::make_unique<DrawablePath>();
    m_companyLogo->setPath(logoDrawable->getOutlineAsPath());

with that you can use something like

auto& whatever = companyLogo->getPath();
DropShadow shadow(Colours::black, 4, Point<int>(4, 4));

shadow.drawForPath(g, whatever);

And it draws successfully. I wasn’t quite satisfied with the way the dropshadow looked still, and couldn’t find the right combination of parameters to get it close to my design, which is why I abandoned.

I have read elsewhere on these forums that Affinity Designer exports an SVG format that includes dropshadows in a way that JUCE can handle currently. I anticipate when I come back around to this problem I will either take that approach or consider moving to a strictly flat (no shadows) design.