[ANN] oksvg and rasterx; SVG 2.0 path compliant renderer and rasterizer

556 views
Skip to first unread message

Steven Wiley

unread,
Apr 22, 2018, 1:41:07 PM4/22/18
to golang-nuts
Hi all,

I needed to write an SVG renderer; something that draws an SVG file onto an image (not to be confused with an SVG generator such as SVGo). I wanted to do this in native go, and not rely on wrapping pre-existing C code.

The golang 2D drawing packages  I could find were not capable of rendering stroked paths with joins like 'arc' or 'miter-clip', or specifying between ending a stroked path as an open line or a closed loop. So, in order to draw SVG 2.0 compliant stroked and dashed-stroked paths, I refactored and enhanced the raster package from  the golang translation of freetype, used by many of the 2D packages, into a new package; rasterx. More information on the refactorization is available in the readme.

The SVG renderer, oksvg, processes only a basic sub-set of the SVG specification, but it is capable of faithfully rendering many, probably most, but certainly not all, of the free and commercial SVG icons available. I have been focusing on just the basics the path functions, and many elements like defs, gradients, or animations have not been added at this point. However, the full SVG 2.0 path specification is implemented and even exceeded, since rasterx can perform additional path functions that are not described by the SVG 2.0 specification. Also, when an SVG file is parsed, oksvg can be set to ignore, log, or error-out when reading an unrecognized SVG element.

So now we can render a large set of SVG icons in native go! I hope some of you find this useful. Please check it out and let me know what you think. The image below demonstrates the effect of some of the available joining and capping functions.

Cheers!


TestShapes.png

ajstarks

unread,
Apr 23, 2018, 12:45:48 PM4/23/18
to golang-nuts
I'm very glad to see this.  FYI, [1]  has a bunch of examples for testing.
It will be nice to have a pure Go tool chain for both generation and rendering.

Daniel Theophanes

unread,
Apr 23, 2018, 2:38:13 PM4/23/18
to golang-nuts
The sample raster images look great! Right now, due to how Go statically links code I can't use GPL code in just about anything I use Go for.

I totally respect your license decision, but, it may be a poorer fit for Go...

Nigel Tao

unread,
Apr 23, 2018, 6:42:55 PM4/23/18
to Steven Wiley, golang-nuts
Nice!


On Mon, Apr 23, 2018 at 3:41 AM, Steven Wiley <steven...@gmail.com> wrote:
I refactored and enhanced the raster package from  the golang translation of freetype,

It'd be a bunch of work, but you might consider basing off of golang.org/x/image/vector instead:

1. Its technique is based on "Inside the fastest font renderer in the world" at https://medium.com/@raphlinus/inside-the-fastest-font-renderer-in-the-world-75ae5270c445 although rasterizing SVG images might perform differently than rasterizing font glyphs. Both are vector graphics, but SVG images often have multiple, overlapping shapes while font glyphs are typically simpler.

There's also https://github.com/golang/exp/blob/master/shiny/iconvg/internal/gradient/gradient.go. It's currently pretty slow, as I wanted to get the API right before optimizing the implementation, then haven't really had the free time to work on it since. But you might find it (and IconVG in general) interesting.

2. Its license is the Go license (i.e. BSD-like, https://github.com/golang/image/blob/master/LICENSE), not the Freetype-or-GPL2+ license (https://github.com/golang/freetype/blob/master/LICENSE). OTOH, if you really want GPL3, then perhaps golang/freetype is a better foundation. Disclaimer: I am not a laywer, this is not legal advice, etc.

Steven Wiley

unread,
Apr 23, 2018, 8:19:59 PM4/23/18
to golang-nuts
Thanks for the compliments, everyone.

I will check out the vector package and further test SVG examples are welcome.  If I have isolated concepts as well as I hope I have, re-writing the Scanner to use the vector package might not be too hard. Gradients are also near the top of the to-do list, because they are just so darn pretty.

As for the license part of it...(snnzz......, huh, what?) Oh yes, the rasterx package has parts that are straight copies, and parts that are somewhat modified from the freetype raster package, as well as parts that are completely new. So, I think I need to stay within the terms of the freetype license for that bit. Apparently, freetype wants derivatives  to use their license, or GPL 2.0 or higher, so I just selected  the highest GPL open on github, and also kept around a copy of their terms.

On the other hand oksvg is completely de novo, so I can slap whatever license I want on that package. I take it people here prefer the Go license? Can anyone briefly describe the difference? I will definitely change it if someone can give me a good reason.  I just want to make it as convenient and useful as possible for everyone.

Dan Kortschak

unread,
Apr 23, 2018, 11:33:40 PM4/23/18
to Steven Wiley, golang-nuts
The freetype license looks it's a BSD derivative. That license would be
more consistent with the static linking used by gc, so since you can
choose either the freetype license or the GPL 2+, if it's not against
your ideals, the FTL is probably the better choice. Note that because
oksvg imports rasterx, oksvg can not be distributed under another
license.

matthe...@gmail.com

unread,
Apr 24, 2018, 9:38:36 AM4/24/18
to golang-nuts
On the other hand oksvg is completely de novo, so I can slap whatever license I want on that package. I take it people here prefer the Go license? Can anyone briefly describe the difference? I will definitely change it if someone can give me a good reason.  I just want to make it as convenient and useful as possible for everyone.

Don’t consider golang-nuts for professional advice. Here’s my understanding:

GPL requires that if the code is distributed or included in a distributed application (source or binary) then the entirety of the source code is also distributed and the license remains. “Viral” is a term associated with GPL, if you use a GPL library then your entire project must become GPL compliant.

For Go you may want to consider the AGPL which also requires any network app to provide its source code. These GNU licenses are from the Free Software Foundation which has a traditionally radical philosophy toward computers: https://www.gnu.org/licenses/why-affero-gpl.en.html So far I personally like the AGPL approach.

My understanding is the source inclusion is only in effect in distribution. If you give your program to a person then that doesn’t require you to post it all on GitHub, but if you have a website where anybody can download your app then you have to make the source code available to all of those people. Note that GitHub also has a default license applied to your work which allows people to fork it and view it on GitHub.

I usually see the MIT, BSD, Apache licenses here. These give you permission to use the code without requiring you distribute the source code. There may be attribution requirements and usually the license says something about no warranty or support provided. If you want your library used here you’ll probably want one of these.

Matt

Zellyn

unread,
Apr 24, 2018, 10:19:58 AM4/24/18
to golang-nuts
On Tuesday, April 24, 2018 at 9:38:36 AM UTC-4, matthe...@gmail.com wrote:
For Go you may want to consider the AGPL which also requires any network app to provide its source code. These GNU licenses are from the Free Software Foundation which has a traditionally radical philosophy toward computers: https://www.gnu.org/licenses/why-affero-gpl.en.html So far I personally like the AGPL approach.

Nobody at Google (or many other companies) can touch anything AGPL.

Zellyn 

matthe...@gmail.com

unread,
Apr 24, 2018, 10:45:35 AM4/24/18
to golang-nuts
I’m curious if some companies juggle the GPL. I guess if the app is used internally only then there’s no problem with accidentally requiring a proprietary program to be released as source code to the world. I’d have thought the case would be the same with the AGPL. Do people count as individuals in a corporate license with the ability to freely redistribute?

I can understand completely avoiding the issue. Language is interpretable and only a court or whatever would decide what was really agreed to. The FSF seems to put a lot of work into building up their licenses with legal precedence.

Matt

andrey mirtchovski

unread,
Apr 24, 2018, 10:49:14 AM4/24/18
to matthe...@gmail.com, golang-nuts
> I’d have thought the case would be the same with the AGPL.

https://opensource.google.com/docs/using/agpl-policy/

David Chase

unread,
Apr 25, 2018, 11:48:41 AM4/25/18
to golang-nuts


On Tuesday, April 24, 2018 at 10:45:35 AM UTC-4, matthe...@gmail.com wrote:
I’m curious if some companies juggle the GPL. I guess if the app is used internally only then there’s no problem with accidentally requiring a proprietary program to be released as source code to the world. I’d have thought the case would be the same with the AGPL. Do people count as individuals in a corporate license with the ability to freely redistribute?

I can understand completely avoiding the issue. Language is interpretable and only a court or whatever would decide what was really agreed to. The FSF seems to put a lot of work into building up their licenses with legal precedence.

I have never worked anywhere that could touch AGPL code.
It was at the level of "just don't, and don't waste anyone's time asking.  Don't."
This is not legal advice, I am just telling you what the policy is/was at all these companies.


Andy Balholm

unread,
Apr 25, 2018, 1:25:24 PM4/25/18
to David Chase, golang-nuts
So it sounds like the AGPL is a good license to choose if you want to keep your code from being used by big companies… ;-)

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

matthe...@gmail.com

unread,
Apr 25, 2018, 4:19:56 PM4/25/18
to golang-nuts
So it sounds like the AGPL is a good license to choose if you want to keep your code from being used by big companies… ;-)

If your project is secret software with a public network interface then don’t apply the AGPL to it. That’s not the only kind of software used at big companies.

Matt

Steven Wiley

unread,
Apr 27, 2018, 10:22:19 PM4/27/18
to golang-nuts
Hey everyone,

I looked into using just the rasterizer package from the "golang.org/x/image/vector" package and tested it vs the rasterizer derived from the freetype package. I will call these the GV and FT rasterizers. One thing with the GV is that it only does non-zero-winding-rule, while the FT does that and even-odd-winding-rule. For stroking and most things you need the non-zero-winding, but just an FYI, the SVG spec asks for both.

What I did like about the GV is the flattener for Bezier curves vs the FT version, which is not recursive and hence cleaner, and more stateless to implement. I also like GV interface to the last step which is rasterizing a representation of flat lines with some version of  anti-aliasing. GV uses the Draw method with a source and a target image, and the path works as an alpha mask. FT on the other hand uses a Painter, which seems a little more mysterious to me.

So, what I did was abstract the last anti-aliasing step into an interface called Scanner. The ScannerGV struct satisfies Scanner by wrapping the Draw func from the vector package, and is included in  rasterx. ScannerFT, which is derived from the anti-aliasing code in the freetype package, is in a different repository, "github.com/srwiley/scanFT". In order to use ScannerFT, you need to include that into your project separately.

The Bezier curve flattener now in the rasterx/fill.go file is a modified version of the vector package flattener (more on that below). So, at this point the only part of rasterx that depends on any freetype code is isolated in the scanFT repository. Which brings me to a bit of a pet peeve of mine. Flattening Bezier curves had nothing to do with rasterizing and anti-aliasing, but they always seem to be thrown together in the same file. It is instructive to have a logical separation, which is one reason why I like abstracting the Scanner from everything else.

As far as performance, it was not clear cut which was better. GV seems to have the edge with more complex and small resolution images, and FT has the edge with higher resolution/lower complexity images.

Below are the results of some benchmarks performed on a sample shape (the letter Q ). The first test is the time it takes to scan the image after all the curves have been flattened. The second test is the time it takes to flatten, and scan a simple filled image. The last test is the time it takes to flatten a stroked and dashed outline of the shape and scan it. Results for three different image sizes are shown.

128x128 Image
Test                        Rep       Time
BenchmarkScanGV-16          5000      287180 ns/op
BenchmarkFillGV-16          5000      339831 ns/op
BenchmarkDashGV-16          2000      968265 ns/op

BenchmarkScanFT-16    	   20000       88118 ns/op
BenchmarkFillFT-16    	    5000      214370 ns/op
BenchmarkDashFT-16    	    1000     2063797 ns/op

256x256 Image
Test                        Rep       Time
BenchmarkScanGV-16          2000     1188452 ns/op
BenchmarkFillGV-16          1000     1277268 ns/op
BenchmarkDashGV-16          500      2238169 ns/op

BenchmarkScanFT-16    	    5000      290685 ns/op
BenchmarkFillFT-16    	    3000      446329 ns/op
BenchmarkDashFT-16    	     500     2923512 ns/op

512x512 Image
Test                        Rep       Time
BenchmarkScanGV-16           500     3341038 ns/op
BenchmarkFillGV-16           500     4032213 ns/op
BenchmarkDashGV-16           200     6003355 ns/op

BenchmarkScanFT-16    	    5000      292884 ns/op
BenchmarkFillFT-16    	    3000      449582 ns/op
BenchmarkDashFT-16    	     500     2800493 ns/op

Here are some additional benchmarks using oksvg with either 8 large icons (landscape) and a set of 44 simpler small icons (sports):

oksvg with FT scanner

BenchmarkLandscapeIcons-16               1    2,100,513,290 ns/op
BenchmarkSportsIcons-16                200       6,017,063 ns/op

BenchmarkLandscapeIcons-16               1    2,167,196,383 ns/op
BenchmarkSportsIcons-16                200       6,020,537 ns/op

oksvg with GV scanner

BenchmarkLandscapeIcons-16               1    2,097,975,097 ns/op
BenchmarkSportsIcons-16                100      18,507,261 ns/op

BenchmarkLandscapeIcons-16               1    2,002,543,751 ns/op
BenchmarkSportsIcons-16                100      13,407,936 ns/op

So, you can either use the GV scanner or the FT scanner, depending on how big vs how complex your icons are, if you need even-odd winding rule, and whether or not you care about freetype vs go-friendly licenses. Right now the rasterx and oksvg package have no license, but I will put in place a go-friendly version soon.

Finally, a little bit more on the Bezier curve flattener from the vector package. I had to modify that for my purposes, but I also did a little optimization. Instead of doing a series of linear interpolations (Lerps), I used the expanded form (see https://pomax.github.io/bezierinfo/#matrix). This usually gives a 10-20 percent speed up for cubic Bezier curves and maybe single digit improvement for quadratics. I
also want to implement a version that uses fixed rather than floating point numbers.

One more set of benchmark results:

BenchmarkBezierQuadLerp-16        10000000           135 ns/op
BenchmarkBezierQuad-16            10000000           130 ns/op
BenchmarkBezierCubeLerp-16        10000000           162 ns/op
BenchmarkBezierCube-16            10000000           138 ns/op


Cheers!



Daniel Theophanes

unread,
Apr 28, 2018, 4:21:41 PM4/28/18
to golang-nuts
Thank you for looking into this! This looks great!

Steven Wiley

unread,
Apr 28, 2018, 4:48:10 PM4/28/18
to golang-nuts
Thanks. I just fixed a bug 2 minutes ago that I introduced while refactoring, so you probably want to pull the new version. (Thanks to Anthony Starks for pointing it out.)

ajstarks

unread,
May 1, 2018, 1:17:38 AM5/1/18
to golang-nuts
I've tested the latest version of oksvg, which has support for more elements, and thus I can test with more SVG files in the codepicdeck collection [1]
The tests compare four rendering methods, on my MacBook Pro, MacOS 10.13.4, 2.3 GHz Intel Core i7, 16GB. 

1) oksvg using GV (svgpng.go) [2]
2) oksvg using FT (svgpngft.go) [3]
3) inkscape command line

dir=`pwd`
/Applications/Inkscape.app/Contents/Resources/bin/inkscape --export-png=$dir/$1.png $dir/$1

4) batik rasterizer 
java -jar $HOME/batik-1.7/batik-rasterizer.jar -m image/png $*

The difference between the GV and FT versions is this diff:


7d6

< "image/color"

11a11

> "github.com/srwiley/scanft"

23,25c23,25

< source := image.NewUniform(color.NRGBA{0, 0, 0, 255})

< scannerGV := rasterx.NewScannerGV(w, h, img, img.Bounds(), source, image.Point{0, 0})

< raster := rasterx.NewDasher(w, h, scannerGV)

---

> painter := scanFT.NewRGBAPainter(img)

> scannerFT := scanFT.NewScannerFT(w, h, painter)

> raster := rasterx.NewDasher(w, h, scannerFT)



Here are the results: (times in seconds of real time as measured by the time command)


file                 GV FT Ink Batik
cgrid                0.673 0.021 0.725 2.226
clock                0.038 0.023 0.738 3.075
cloud                0.049 0.034 0.586 2.071
color-clouds         0.582 0.059 0.772 2.225
concentric           0.038 0.029 0.689 2.221
concentric2          8.924 0.532 1.537 2.726
conception           0.025 0.017 0.681 2.051
conception2          0.471 0.029 0.749 2.295
cube                 0.122 0.020 0.695 2.221
d4h                  0.077 0.022 0.691 2.195
diag                 2.766 0.058 0.796 2.431
eclipse              0.044 0.015 0.697 2.039
gear                 0.121 0.043 0.697 2.205
go                   0.043 0.032 0.694 2.239
mondrian             0.047 0.016 0.691 2.179
pacman               0.047 0.021 0.695 2.207
plotfunc             0.893 0.025 0.846 2.297
pyramid              0.119 0.031 0.691 2.098
randarc              0.140 0.088 0.713 2.150
randbox              0.253 0.037 0.710 2.213
randspot             0.275 0.068 0.738 2.340
recurse              0.319 0.045 0.707 2.027
richter              0.577 0.019 0.733 2.246
rl                   2.244 0.139 1.019 3.000
schotter             0.356 0.026 0.722 1.967
star                 0.063 0.040 0.695 2.248
starx                0.037 0.024 0.691 2.246

sunearth             2.212 0.026 0.773 2.160

Conclusion: the FT methods are very fast, clearly superior to all others for these tests (see also the attached chart)
Note that Steven is working on some optimizations that will address the spike in the "concentric2" case, but the speed and capability improvements are impressive.

f.pdf

Steven Wiley

unread,
May 22, 2018, 3:20:15 PM5/22/18
to golang-nuts
Hi All,

As promised I added support for SVG gradients to rasterx and oksvg. There is a minor API change: If anyone's code  is calling NewScannerGV, just drop the last two arguments. Those are handled internally now.

Also I was able to modify the golang vector package to be almost as fast as the freetype style rasterizer. There were two issues: 1) When drawing to a small rectangle contained within the entire image, unfortunately, the alpha mask does not shift with the target, so you are always drawing the upper-right hand corner of the alpha mask. This forces the current version to draw the entire image no matter how small the path is. So, when I changed it to shift the alpha mask with the target rectangle, and fit the target rectangle to the path bonds, things sped up a few fold. 2) I also had to alter the 'accumulate'  step of the rasterizer to only accumulate within the target region. Before, it was accumulating over the entire image, no matter how small the target rectangle. Both of these steps combined produce a greater than 10x speed up for some test cases.

I'm pretty sure the default API should be to shift the alpha mask with the target rectangle, but I will have to get the package maintainers' opinion on that as well as on accumulating only over the target rect. I will submit my changes to the vector.go file via gerrit, but it will take a while to be deployed even if they agree with the changes.  Also, the vector rasterizer has a lot of different modes, some of which are not invoked by the tests in the package. So I am having a little difficulty testing all of the changes I made, since I made equivalent  changes for all of the modes. If anyone familiar with the vector.go package wants to help me test it before I submit the changes, please contact me.

I guess for now I will fork the vector package on github so that anyone can try out the altered rasterizer. There are instructions in the Draw function in rasterx's scan.go file to alter the code to take advantage of the improvements. You just need to comment one line and un-comment a few others.

If and when the vector package is updated, I will update oksvg accordingly.

Finally, let me give a huge thank you to ajstarks for letting me use his test files, and helping me benchmark.

Auto Generated Inline Image 1

ajstarks

unread,
May 29, 2018, 6:11:58 PM5/29/18
to golang-nuts
After applying the latest bits (updated scan.go and vector.go).  Here are the latest benchmarks using files from the codepicdeck collection. (https://github.com/ajstarks/deck/tree/master/cmd/codepicdeck/code)

 (1) the two renderers are very fast and practically equivalent for this set of files, (2) and the spike in the concentric2 case has been fixed (it's still the slowest to render, but I'll take 0.5 sec over 6+ sec)

File                GV FT

cgrid.svg           0.057 0.061

clock.svg           0.024 0.025

cloud.svg           0.039 0.047

color-clouds.svg    0.066 0.060

concentric.svg      0.030 0.033

concentric2.svg     0.565 0.472

conception.svg      0.018 0.019

conception2.svg     0.035 0.030

cube.svg            0.023 0.022

d4h.svg             0.022 0.022

diag.svg            0.111 0.061

eclipse.svg         0.017 0.017

gear.svg            0.039 0.041

go.svg              0.026 0.026

gradient.svg        0.062 0.062

mondrian.svg        0.018 0.016

pacman.svg          0.022 0.021

plotfunc.svg        0.042 0.025

pyramid.svg         0.228 0.028

randarc.svg         0.050 0.052

randbox.svg         0.041 0.041

randspot.svg        0.070 0.073

recurse.svg         0.044 0.041

richter.svg         0.029 0.020

rl.svg              0.594 0.164

schotter.svg        0.033 0.028

star.svg            0.038 0.053

starx.svg           0.025 0.031

sunearth.svg        0.068 0.029

David Skinner

unread,
May 29, 2018, 10:36:25 PM5/29/18
to golang-nuts
I love what you have done but I cannot use it because of the GPL License.

I like to use the ISC License  https://www.isc.org/downloads/software-support-policy/isc-license/ because it is short.
or the MIT License for open source projects on GitHub,
or a BSD License in projects with my son. https://www.freebsd.org/doc/en_US.ISO8859-1/articles/bsdl-gpl/article.html

If I am working on a commercial project then I usually draft a License specific to that project. If you wanted me to draft a License for your project, you would have to retain an attorney qualified in your jurisdiction to retain my services as a paralegal (not likely to happen as my rates are unreasonably high). I have made exceptions in the past for residents of Louisiana and for American citizens residing in Belize, the only two places where I can give legal advice.

This is not legal advice, it is sage general business counsel, you should consult your family attorney in your jurisdiction for specific legal advice. 


Wojciech S. Czarnecki

unread,
May 30, 2018, 7:33:39 AM5/30/18
to golan...@googlegroups.com
On Tue, 29 May 2018 19:36:25 -0700 (PDT)
David Skinner <skinne...@gmail.com> wrote:

> I love what you have done but I cannot use it because of the GPL License.

> I like to use the ISC License | or the MIT | or a BSD

> my rates are unreasonably high.

It made my day.

I apologize for the OT intrusion but I could not resist to dig more seeing
"rates unreasonably high" and "gimme your work for free" rant stuffed
together in a single post.

[ http://www.sage.com/company ]
[ https://www.quickpay.net/ ]
[ https://github.com/QuickPay]

--
Wojciech S. Czarnecki
<< ^oo^ >> OHIR-RIPE

Steven Wiley

unread,
May 30, 2018, 12:51:17 PM5/30/18
to golang-nuts
Just to be clear to everyone; the default version of rasterx and all of oksvg are under a BSD type license, period. If you want to use the free type-derived anti-aliaser, you can, but that code is under GPL, and is kept in a separate repository scanFT, and must be downloaded separately.

Until just recently, for certain test cases, the vector.go rasterizer was significantly slower than scanFT. Now there is a version of vector.go rasterizer which overcomes the bottleneck, and I will submit it to the package maintainers via gerrit, hopefully today. I had to spend some time making sure that the package tests traversed my changes in all different modes of the rasterizer, since the last thing I want to do is introduce bugs into the Golang code base.

FYI: I may also move some of the gradient methods from oksvg into rasterx at the request of Randal O'Reilly, but everything should still work the same as far as drawing SVGs.

Dan Kortschak

unread,
May 30, 2018, 10:19:52 PM5/30/18
to David Skinner, golang-nuts
Out of curiosity. Why do you choose MIT for your projects on GitHub but
a BSD for projects with your son? What is the substantive difference
between those cases that makes you differentiate?

thanks

David Skinner

unread,
May 30, 2018, 11:12:46 PM5/30/18
to Dan Kortschak, golan...@googlegroups.com
Lol. If I do a project with my son, he decides the license, I am not going to argue with him, he is on my living will. I am old, retired, somewhat senile. My motto is "Go with the flow!"

Not everything I do is well thought out or even a good idea. Only sometimes I get it right.

I do believe that you must have either a disclaimer or an indemnification agreement. If I sell you my warranted software  then as the publisher you must indemnify me from all 3rd party claims for damage. Commercial software gets really complex really fast, not something I care to do on my own without legal counsel.

A good attorney knows the law, a great attorney knows the judge. I can read law books all day but that does not take the place of someone with real experience.

Dan Kortschak

unread,
May 31, 2018, 1:11:25 AM5/31/18
to David Skinner, golan...@googlegroups.com
Lovely answer.

thanks
Dan
Reply all
Reply to author
Forward
0 new messages