So Here’s How I Accidentally Made Martin’s Life Harder
Martin Owen-Williams discussing the feDropShadow contribution • Watch full video on YouTube
“It’s been 13 years since anybody added anything to the filters in Inkscape. Then along comes Ryan to add a perfectly agreeable, perfectly nice addition to support drop shadows in Inkscape. Right at the time when I’m re-engineering the the the engine, god damn it, Ryan.”
— Martin Owen-Williams, Inkscape Core Maintainer
Yeah. That’s how one of Inkscape’s core maintainers introduced my contribution to the community. “God damn it, Ryan.”
But let me back up.
The Problem I Didn’t Know I Had
September 2025. I’m working on an SVG with a gradient fill and a drop shadow. Standard stuff. Open it in the browser? Perfect. Open it in Inkscape?
Gone. Completely invisible.
Not broken. Not malformed. Just… not there. But the weird thing? The properties panel showed everything correctly. The gradient was there. The shadow was there. Inkscape just refused to render it.
So I dug in and found the issue: Inkscape didn’t support the SVG 2.0 feDropShadow filter primitive. At all. It would throw a warning (unknown type: svg:feDropShadow) and give up.
I opened issue #5939 on September 22nd. Then I figured… you know what? I can probably fix this myself.
The Perfectly Terrible Timing
Here’s what I didn’t know: Martin Owen-Williams was in the middle of re-engineering Inkscape’s entire rendering engine. Major architectural changes. New APIs. The whole filter system was being touched.
And I’m over here implementing a new filter primitive.
In most projects, this would be a disaster. Someone’s code gets thrown out. Or we sit in endless meetings trying to coordinate. Or worse, we both merge and everything explodes.
But here’s what actually happened: Martin posted that video. And yeah, he said “god damn it, Ryan” - but then he kept talking:
“Of course, Ryan is actually been really sweet about it because he’s uh reviewed my um access code, the stuff that I talked about last week, and um has committed to making sure that his new drop shadow code will actually work with the new stuff. And it’s always great to see a new face who’s contributing to Inkscape.”
We coordinated. I reviewed his engine refactoring code. He guided me through the filter architecture. We made sure my implementation would work with his new rendering API, including proper CMYK color space support.
Three weeks from issue to merge. First new filter primitive in 13 years.
How Filter Primitives Actually Work in Inkscape
So here’s the thing about Inkscape’s filter system: you can’t just add a filter. You need to implement it in two completely separate layers, and they both have to work perfectly together.
Layer 1: The Object Model (SPFeDropShadow)
First, there’s the document object model layer. This is the SPFeDropShadow class that lives in src/object/filters/dropshadow.{h,cpp}. It handles all the SVG stuff:
- Parsing the
<feDropShadow>attributes when you open a file - Managing the parameters (offset, blur, opacity, color)
- Writing it back out to SVG format
- Playing nice with undo/redo and the rest of the document lifecycle
Layer 2: The Rendering Pipeline (FilterDropShadow)
Then there’s the rendering layer - FilterDropShadow in src/display/nr-filter-dropshadow.{h,cpp}. This is where the actual graphics work happens:
- Taking those parameters and turning them into Cairo rendering commands
- Compositing the shadow behind the original graphic
- Handling color space transformations (including CMYK)
- Integrating with Inkscape’s rendering backend
Why split it like this? Because you want the document model to be independent from how things render. Someone can edit the filter properties, undo changes, or modify attributes without touching the rendering code. And the rendering engine can be optimized or refactored without affecting the SVG representation.
Good architecture. Especially important when someone else is simultaneously refactoring the rendering engine.
Following the Spec (The Boring But Important Part)
The SVG 2.0 spec defines feDropShadow in the Filter Effects specification. Before this, if you wanted a drop shadow, you had to chain together three separate filter primitives: feGaussianBlur + feOffset + feComposite. It worked, but it was verbose and slower.
feDropShadow does it all in one shot. Here’s what I implemented:
dx,dy— Shadow offset (defaults to 2, 2)stdDeviation— Blur amount (defaults to 2)flood-color— Shadow color (defaults to black)flood-opacity— Shadow opacity (defaults to 1)
The rendering algorithm is straightforward:
- Extract the alpha channel from the source graphic
- Blur it using Gaussian blur
- Offset the blurred shadow by dx, dy
- Apply the color and opacity
- Composite it beneath the original graphic
Why does spec compliance matter? Because an SVG created in Inkscape should look identical in Chrome, Firefox, Safari, or any other modern browser. No proprietary extensions. No “works better in Inkscape” nonsense. Just standard SVG 2.0.
Why This Took 13 Years (But I Fixed It in 3 Weeks)
The last time anyone added a filter primitive to Inkscape was 13 years ago. Not because nobody cared. Not because it wasn’t important. But because filter primitives are genuinely complex, and they require someone willing to dig into both the SVG specification AND Inkscape’s architecture.
When I opened issue #5939 on September 22, 2025, the problem was pretty clear: SVG files with gradient fills and drop shadows would just… disappear in Inkscape. Meanwhile, they rendered perfectly fine in every modern web browser.
October 16, 2025: Merge request #7506 submitted.
October 23, 2025: Merged.
Three weeks from problem to solution. First new filter primitive in 13 years.
What I Actually Learned From This
Here’s the thing Martin said later in that video that really stuck with me:
“I am uh overjoyed actually that Ryan has agreed to keep on participating in the project to make sure that his work with the new interface and the new API will actually be tested including CMYK like he fully understands what the new access API does which is great.”
The code wasn’t the hard part. The hard part was the coordination. Making sure my implementation would work with his refactoring. Reviewing his access layer code. Understanding the new API. Testing with CMYK color spaces.
That’s what open source collaboration actually looks like. Not everyone working in silos until merge day. Not territorial protection of “my code.” It’s: “We’re both touching the same system—let’s make our work fit together.”
Martin shared his refactoring plans early. I reviewed his code. We coordinated on implementation details. Both of us cared more about making it work than taking credit.
Then Martin Asked Me to Review His Code
So after feDropShadow merged, Martin asked if I’d review his pixel access layer refactoring - merge request #7476. This was the rendering engine work he’d been doing during my feDropShadow implementation. A new template-based API for accessing pixel data in Cairo surfaces. Multi-channel color support (including CMYKA/5-channel). Edge mode handling. The foundation for making Inkscape’s rendering more flexible.
Here’s the thing about code review: reading code tells you what it does. Writing code against it tells you how it feels to use.
So instead of just reading through the MR, I implemented two real filters against the API.
Floyd-Steinberg Dithering (a point operation): I wrote a 245-line implementation in about 90 minutes. Zero prior knowledge of the API. Just the code and the compiler. It worked. Hit 6.6 megapixels per second. The template system’s type deduction worked perfectly - no hacks, no workarounds, no “how do I make this compile” gymnastics.
That validated the API design. Nice work.
Box Blur (a spatial operation): This one was different. Spatial filters need to access neighboring pixels, not just process one pixel at a time. And this is where things got interesting.
The WRAP Mode Bug (Critical)
The blur filter crashed. Hard. Heap-buffer-overflow when accessing pixels near the image edges.
The problem? C++ modulo doesn’t work the way your brain thinks it does.
When you’re wrapping coordinates around an image, you expect -1 % 5 to give you 4 (wrap to the right edge). But C++ gives you -1. Which means when you try to access pixels near the left edge of an image, you’re reading from completely wrong memory addresses.
The fix is simple once you know it:
((x % width) + width) % width
Double modulo. The second one handles the negative case. Three lines of code. Critical bug.
The Default Edge Mode Bug (Medium)
The other issue was developer experience. When you create a spatial filter without explicitly setting an edge mode, the API throws a generic exception. No helpful error message. No “hey, you forgot to set the edge mode” guidance. Just a crash.
Trial and error to figure out what’s wrong. Bad developer experience.
The fix: set a sensible default. EXTEND mode matches what most image processing libraries use. Make it work by default, let people override if they need different behavior.
What I Validated as Excellent
The template architecture? Solid. 5/5. Compiler type deduction worked flawlessly. The learning curve was impressively gentle - I went from zero API knowledge to a working 245-line dithering filter in 90 minutes. Exception handling integrated cleanly with RAII patterns. The colorAt()/colorTo() interface was intuitive.
This was good code. It just needed those two fixes.
The Outcome
My assessment: API ready for merge after the fixes (about 45 minutes of work, plus documentation updates). The comprehensive evaluation approach - testing both point AND spatial operations - revealed bugs that narrow testing would have missed.
And honestly? The fact that Martin trusted me to review infrastructure code after a single contribution says something about how open source collaboration actually works. It’s not about gatekeeping or “paying your dues.” It’s about demonstrating that you understand the system and care about making it better.
You can see my evaluation branch with both filter implementations if you’re curious about the technical details.
The Detail That Matters
Thirteen years is a long time to wait for a feature. But the real story here isn’t about the wait—it’s about what happened afterward.
October 2025: feDropShadow merged. First new filter primitive in 13 years. Three weeks from problem to solution.
November 2025 - January 2026: Reviewed Martin’s DrawingAccess API refactoring. Found two critical bugs through implementation testing. Validated that the template architecture worked as designed.
This wasn’t a one-time contribution. It was the start of an ongoing technical partnership on Inkscape’s rendering infrastructure.
The collaborative arc: Concurrent development on overlapping systems → Perfect timing collision with a major refactor → Instead of conflict, coordination → Trust earned through quality work → Infrastructure review responsibility → Bugs found that would have been missed → Better code shipped.
That’s what open source collaboration looks like when it works right. Not gatekeeping. Not “paying your dues.” Just: demonstrate you understand the system, care about making it better, and people will trust you with more responsibility.
Issue #5939 closed. MR #7476 improved. Inkscape now has native SVG 2.0 drop shadow support AND a more robust rendering API.
And somewhere, someone’s gradient-filled graphic with a drop shadow renders correctly in Inkscape, while spatial filters work reliably near image edges because C++ modulo got handled properly.
Watch Martin’s full development update (the whole video is worth it): Inkscape Development Update, October 2025