Wednesday, October 20, 2021

Creating a Pencil Effect in SVG (Part 2)

Quite some time ago, I wrote a post about creating a pencil effect in SVG.  It's challenging to do a pencil effect in SVG because SVG is fundamentally a vector graphics format, and pencils are fundamentally not.  I ended up applying an SVG filter that added some texture and other effects to create a line that looks at least somewhat like a pencil:

At the time, I left it at that and skipped over a couple of other characteristics of pencil-drawn lines that I didn't have a good way to replicate.  One of those characteristics is that pencil doesn't completely cover whatever is underneath it.  So when two pencil lines overlap, they build up to a darker color.  You can see an example in the drawing below, where a single pencil makes a spectrum of shades, in part by building up graphite in areas where lines are drawn repeatedly:


Admittedly, artists control darkness by pressure and density of line as well, and the color of the graphite limits how dark you can go.  But in general if we want to have a good “hand-drawn pencil" look, we'd like to get some darker textures where lines overlap.

In SVG, when one line crosses another line, the top line complete covers the bottom line.  So when you have two lines of the same color that overlap, they just seamlessly blend into each other.  Here's an example of a map where I've filled in a forest area with a bunch of scribbled lines in a pencil color:
It has a little bit of texture thanks to the pencil effect, but all the lines just blend into one flat area of color.  That's pretty unsatisfactory.  So how do we get the colors to “build up" where lines cross?

You might think (I certainly did) that opacity is the answer.  If we set the opacity of our lines to (say) 50%, then 50% of the underlying color will show through, and that will let us build up a darker shade where lines cross.  But unfortunately, it doesn't work that way.  If the colors and opacities are the same, then the overlap area looks just like the non-overlapped areas.  No matter how many layers you build up, it always looks the same.

There are other work-arounds that I might consider, but if you have any experience with computer graphics you might have heard of blend modes.  Blend modes control how two colors interact when they overlap.  The default is alpha compositing, which is what SVG does where the top color completely obscures the bottom color.  But there's another mode called multiply that blends two colors when they overlap in a way that would be useful for pencil strokes.

The bad news is that SVG doesn't support multiply blend mode except in filters.  There's no way to tell regular SVG elements to multiply with colors below.  (Except perhaps by abusing filters in horrible ways that I don't want to contemplate.)  The good news is that CSS does support a multiply blend mode!

CSS is a styling mechanism for browser pages that provides a flexible way to control the appearance of page elements.  CSS was invented around the same time as SVG, but unlike SVG has continued to grow and evolve and has added capabilities like blend modes.  In modern browsers you can apply CSS styles even to individual SVG elements, which will enable us to borrow the multiply blend mode from CSS and use it in SVG.  

Here's what the flat fill example from above looks like when I turn on CSS multiply blend mode on all the individual pencil strokes:

Needs some tuning, but you can see that the color now gets darker where strokes overlap.

(There are some reasons not to mix CSS and SVG, the primary one being that the CSS effects will be lost when you isolate the SVG -- say to import it into Inkscape for some manual editing.  But in this case, there's no real work-around that would allow me to create this effect in “pure" SVG.)

There are a number of improvements I can make to the first effort above.  One is to break up the long fill strokes into shorter segments.  An amateur human artist likely wouldn't draw single lines all the way across the area.  He'd fill in using shorter strokes and work in patches across the area.  Splitting up the fill area into realistic patches is a little too challenging, but it isn't too hard to break up the lines going across the fill area into smaller segments:

I've circled an area where you can (faintly) see where long lines have been broken up into consecutive line segments.  Because both ends of each segment have rounded ends, and one segment ends on the start point of the next segment, you get a sort of circle where they overlap.  That's a little too regular to be pleasing, so let I'll make the start and end points a bit more random:

This goes a long way towards making the strokes more evident.  In the same area as above, you can clearly see a stroke ending and an overlap just above.  This example is a bit too obvious for my tastes, so I'll tone it down and adjust it to be a little more subtle:

Another aspect of hand-drawn pencil strokes that I didn't include the first time around is gradient.  Pencil strokes are often darker at the beginning, or where they change direction.  This is partly because humans aren't very precise and don't always hit exactly the right shade when they make a pencil stroke.  If you start too light you can just go over it again, but if you start too dark all you can do is ease up quickly, leaving a darker start to the line.  And it's partly because physiologically it's easier to trail off a line than to trail in a line.  At any rate, you often see strokes like on the right side of this image:
where some strokes start dark and end lighter.  Compared to the pencil strokes on the left side, these seem more realistic.  (Although somewhat exaggerated here.)  

The bad news is that, like blend modes, SVG doesn't implement gradient colors on lines.  The good news is that I already tackled this problem back when I was making river borders work better.  So it's just a matter of reusing that code to occasionally mix in a stroke that starts dark and ends lighter.

But in the process of doing that, I discovered some problems in the code that fills an area with lines.  It was drawing some lines in one direction and some in the other.  And the code itself was complex enough that I didn't really understand how it worked anymore.  So I stopped and reimplemented the code to “sweep" a polygon in a more straightforward way.  There are some efficient algorithms for doing this, but I was content with a simpler implementation based on the description here.  (The basic idea of drawing lines across a polygon at an arbitrary angle by first rotating the polygon and then drawing vertical lines is very clever!)

This change means re-tuning the various parameters, but eventually I have this:
Which you can see now contains the occasional line that starts dark and gets lighter.  I've kept this fairly infrequent.  In this example I've used a fairly wide “pencil" and kept a fair amount of erratic placement.  I think this looks good, but you might prefer an artist with a better “hand" who is closer to that flat machine fill ideal:
Or perhaps a narrow pencil:
I prefer the first version, but all the styles seem pretty realistic to my eye.  Here's a look at the entire map with these changes in place (click through for a hopefully full-sized image):
One area that could still be improved is the line direction.  Especially when seen on a full map, the very consistent line direction doesn't look realistic.  I've done some simple experiments to try to address this and haven't been entirely happy with the results, but I'll continue to think about it.  Maybe that will be in (Part 3) in a year or so!

Wednesday, October 6, 2021

Haunted Forests

(After a long break to pursue other interests, I've returned to Dragons Abound with some new material.  I have at least a few months of postings lined up and we'll see where it goes after that.  Special thanks to the folks who reached out to ask if I was okay!  I was fine, just off doing other things, but the care you showed meant a lot to me.)

The forests I implemented for “Knurden" style maps (here) are essentially a background color filled with scattered trees, like this:

Partway through implementing these forests, it occurred to me that I could create a “haunted forest" by replacing the forest background color with a blurry grey and making skeletal tree icons.  I set that thought aside, but I did parameterize the top-level routine so that it could work with any kind of tree style and make something like haunted forests easy to implement.  Famous last words!  Let's see how right or wrong they are.

The first step is to pick a clump of forest to make haunted.  I want a clump that is fairly small, like the forest north of the river on this map:

I use the debugger to get the size of that forest, and then just pick a forest of roughly that size to make the haunted forest.  For the moment I draw it as a fir forest to check the logic.  On this map I got lucky and got that same forest:
Eventually I might want to use some additional criteria to pick the forest (such as picking one away from towns) but for now I'll just take the first forest I find that is the proper size.

The next step is to draw in the gray fog.  I'll start with just filling in the forest shape with gray:
This is supposed to be fog, so I'll make the edges indistinct.  Maybe it should have tendrils or wisps as well, but I can always add that later.  And I'll make it somewhat translucent as well.
I like the edges here, but overall this is too subtle.  This might be a good place for a radial gradient, so that I can make it heavier and darker in the center and fading out to the edges.  SVG only supports circular gradients, so the fade will not be consistent but might still look good.  I also played around with using some different colors.  I ended up with this:
This looks pretty nicely fog-like, although it isn't perhaps as menacing as I might like for a haunted forest.

The next step is to add some trees to the mist.  As you'll find if Google it, there are a lot of ways to procedural generate tree skeletons.  A common one is “Lindenmayer systems" (or L systems) which is a family of grammars that can be used to generate tree skeletons as well as other phenomenon.  The trees I'm going to generate are so small and simple that an L system seems like overkill.  So I'll just roll my own and see how it goes; I can always do something more complex later if this doesn't work out.

We'll start with the trunks as a smoothly tapered line:
Of course, scary trees shouldn't grow straight like that, so let me add some jitter:
For tree trunks, I want the jitter to go back and forth (I don't want trees shaped like a big C for example) and I want the perturbations to be pretty sharp, not smoothed out as when I create a “hand-drawn" line.  So I had to create a new perturbation function that alternated the direction to perturb the line.

While I'm at it, I also want to apply a mask that fades out the tree towards the bottom, as if it is disappearing in the fog.  I can do this by applying a mask filled with a linear gradient from white to black.
Note that I've adjusted the mask so that the tree doesn't completely fade away.

Now I need to add some branches to the tree.  I'll alternate sides on the tree and make the branches shorter as they get near the top.  Maybe I'll need branches on the branches, but we'll see.  These icons are pretty small.  To make branches, I'll pick a point on the tree, draw a line straight up, and then rotate it either left or right.  After playing around a bit:
(This is at 150% size.)  This looks sort of okay -- good enough to move forward with putting these on the map to see how they really look.

To sprinkle the trees throughout the fog area, I use Poisson sampling, which I've talked about previously.  It takes a few tries to get a good sampling distance, but that gives me this:
I'm not sure whether I like this or not.  At this scale it is hard to be artistic, but these look (to my eye anyway) to blotchy and clumsy.  I made some adjustments to try to keep white space between the branches and to generally thin down the lines.
The “trees" look a bit like scraggly Christmas trees, but there's a limit to what can be done at this scale.  The trees are also too regular in the fill, but that I can address; I tried a couple of different approaches and settled on this:
Now that I'm more-or-less happy with the look of the haunted forest,  I want to put an appropriate label onto the forest.  And this is where I run into a problem.

As it turns out, the haunted forest above isn't really a separate forest.  It's actually part of the larger forest south of the river.  But maps are often drawn with the forests pulled back from the rivers to show how they run, and so this forest gets split by the Flotilla River and displays as if it is two separate forests.  This becomes apparent if I turn off the option to draw the forest back from the river:
*Poof* no more haunted forest.  (The river should be under the forest, but that's a different problem.)

This matters because naming occurs earlier in the map generation process and works on the intact forests.  So there isn't any way to give the haunted forest segment its own name.  The haunted forest really needs to be its own separate forest.  The best way to accomplish this is to split off the haunted forest earlier in the generation process, and modify map so that it is separated from other forests even if the forests aren't pulled back from the rivers.  That's not so easy to do (and my approach is not particularly efficient) but eventually I have this:
The haunted forest is now on a different part of the map,  As you can see I fixed the error so that the forests are now obscuring the rivers, but I've still pushed the forest back away from the haunted forest even though that's not on for display purposes.  So now I can go back to trying to label the haunted forest.

First I'll just put a placeholder name on the forest.  I'm just reusing the existing forest label code, so this is straightforward.
Next I need to generate a good name for a haunted forest.  I haven't done place names in a while, so I have to go back and re-familiarize myself with the code.  I use a library called RiTa.js which I've modified somewhat to meet my needs.  Names are generated using a (mostly) context-free grammar, which I explained in detail here.  But I don't need to create an entirely new grammar; it should be sufficient to borrow the grammar for naming forests, use a lexicon suited for scary haunted places and throw out any name forms that aren't appropriate.  I can borrow some of the lexicon from the similar Lost Coast names.  With some editing, here's a list of ten random haunted forest names:
  • Milky Forest
  • Wailing Forest
  • Wasted Woods
  • Bloodstained Woods
  • The Calamity Forest
  • The Apparition Forest
  • Cruel Woods
  • Unearthly Aberration Forest
  • Sepulchral Talons Forest
  • Foggy Toll
And for my example map:
The “Sunless Forest".  This label style is a little difficult to read on the gray of the haunted forest, so that might need to be tweaked.

The last thing I've added is some code to place the haunted forest as far away from the nearest city as possible.  Although I could see some interest in having a haunted forest right outside of a town, I think on balance it probably makes more sense to have it far away.

And that's about it for the haunted forest, at least for now.  One thing I'm trying to do is stop development of a feature at a reasonable point and let it sit for a while.  Later new ideas might occur to me, or, after having seen it on maps a few times, I might have an idea on how to improve it.  So I'll let this age and possibly revisit it later.  (And if anyone has any good ideas on drawing a skeletal tree better at this scale let me know!)

Next time I'll return to the topic of pencil effects in SVG.

Tuesday, March 2, 2021

Knurden Style: Completed Maps (Part 7)

As promised last time, some completed maps.  These are 3250x3250 so you will have to click through or download to view full size.  (You may use the maps for non-commercial purposes, if you'd like.)

This was my primary test map.  The city names that lie across islands are difficult to read; I will probably tweak the city label style some to try to address that.

I tried a map using the Mediterranean template that creates a central sea:
With this configuration the central rhumblines become a pretty striking feature.  It's not the sort of thing you'd want to see on every map, but it look pretty good here.  There are a couple of other nice features on this map.  I like the big enclosed bay at the lower right (although “Obvioussland" is not a great name -- I'm not even sure how the name generator came up with that).  The snaky river in the upper left looks very good, although for some reason it got two names.
 
Here's a final map, this time a “coast" map showing one coast of a larger continent:
This one happened to generate a Lost Coast and I've modified the label style for that to fit in with the general style of these maps.  There's also a weird coincidence on this map where one of the rhumblines comes out of the mouth of river which looks strange.

Here's a side-by-side comparison:
You can certainly see differences in the scale and color palette, but overall, I think the finished maps capture a lot of the style of the original map.  

So that's it for Knurden-style maps.  Next time ... haunted forests.

Sunday, February 14, 2021

Knurden Style: Labels & Etc (Part 6)

In previous postings, I implemented the major portions of Daniel Hasenbro's Knurden map style, the mountains and the forests.  I also previously handled the shoreline and the basic colors.  There remain a few more elements to implement.

One important element is the labeling.  The excerpt above shows examples of the important label types.

The font used for the labels is one of the IM Fell fonts (DW pica).  The city labels are filled in a light reddish brown, and stroked in a dark red brown.  There are two other potential elements to labels.  The first is a mask that blocks out the background around the letters; these labels do not appear to have any masking.  The second is a halo.  This is usually a white blur that surrounds the letters and also helps separate them from the background.  The Knurden city labels have a narrow, somewhat transparent white blur.  Matching the style is then a matter of trying different colors, sizes, blurs and so on until I get something that looks approximately the same.
The colors are still a little bit off because fonts are treated somewhat different in SVG than in Photoshop, but it's a fairly close match.

The labels for woods use an italic version of the IM Fell font.  The letters are outlined in the same dark brown but filled with white, and there does not seem to be a mask or halo.  The forest labels are also fairly small, about 60% or so the size of the town labels.  But here I run into a problem.

The italic version of the IM Fell font has characters that are a little too skinny at the size I need for the forest labels.  In theory, that's not a problem, because you can change the thickness of fonts by using the CSS “font-weight" style.  By setting the font-weight appropriately, I can get a thickness that is readable and roughly matches the Knurden map.  However, I discovered that setting font-weight breaks the font stroke (outline).  You can make the font fatter, or you can have an outline, but you can't have both.

This is probably because font outlining is done using the generic SVG shape stroking capability.  The text is just treated as an SVG shape and then the perimeter of the shape is stroked to create an outline.  Font weight, on the other hand, is a CSS style feature that presumably happens later in the display pipeline.  If the shape is already stroked, it can't then be made fatter.

As it turns out, there is a CSS style to stroke a font as well, called “-webkit-text-stroke"  So you might guess that it would be possible to use this in combination with font-weight to get weighted, stroked text.  Unfortunately you'd be wrong, as -webkit-text-stroke doesn't seem to work on SVG text.

Another possibility for creating an outline effect is to put two copies of the same text on top of each other, and make the back copy thicker using font-weight so that it shows around the edges of the top copy.  Then make the top copy the fill color and the bottom copy the stroke color, and it will appear that you have stroked the outside of the text in a different color.  And while this does work, it turns out that the difference in thickness between the heaviest and the lightest font weights is still pretty minimal, so that the stroke is very thin.  
Just a hair of black peeking out.  So on a practical level, this doesn't work.

Yet another possibility is to make the bottom copy bigger by changing it's size.  The problem with this idea is that when the font changes size, the individual characters don't stay centered on each other.  The space between characters also gets bigger, and this throws everything off.
In theory I suppose you could handle every character separately and line them up correctly, but ... not going to go down that rabbit hole!

After much experimentation, I found yet another way to get the desired effect.  There's a little-known attribute for SVG text called “paint-order" which can be used to modify the order in which the fill, stroke and markers get drawn.  It turns out that when the stroke is drawn first (instead of the fill), “font-weight" starts working!  
I'm not entirely sure of the reasoning here, but I'll take it!

(I later thought of another way to achieve this effect.  Draw the text first with a thick outline, and then draw the text with no outline over the top.   That might work.  It seems there are many ways to skin this particular cat -- I'm glad at least one of them works!)

With this fix in place it's straightforward to get a reasonably close match:
There are only two river labels on the Knurden map, but they use an interesting technique that is used on other labels on the map as well:
For these labels, Daniel gets a kind of “negative" effect by using a transparent light color for the font and surrounding it with a dark halo.  This is fairly straightforward in Dragons Abound, although it takes a lot of tuning to get the colors close (if not exactly the same):
One problem with this style of label is that it can be hard to read on a busy background:
So I may tweak it a bit to address that at some point.

Ocean labels are much as river labels, adjusted for the ocean colors, so I won't go into any big detail on them.  Region labels are like forest labels, but not italic and in all-caps.

With labels done, I want to go back to forests and pick up an interesting little trick from the Knurden map.  Although forests are done as large masses, Daniel also scatters some solitary trees around the map:
As in this example, the trees are normally scattered around the edges of forests and also along rivers.  The solitary trees sometimes appear as clumps of two, or less frequently, three.

Implementing this was a bit more challenging than I expected.  Finding the border around a shape is not very easy or efficient, and in this case often creates a torus shape (a polygon with a hole in it) which is tricky to work with.  Eventually I settled on iterating over all the underlying Voronoi polygons in the land and deciding for each one whether or not it was in the border area, and then whether or not it should contain a tree clump.  At any rate, after that I have isolated trees around the edges of the forest masses:
Now I can do something similar for rivers.  I'll look for locations near rivers that also have above average precipitation.  (As an aside: I don't actually keep the precipitation values by the time I'm generating these trees, but I do have the biomes for each location, so I can use the biomes as a proxy for the level of precipitation.)  That adds a scattering of trees alongside fertile rivers that get a lot of rainfall:
The last thing I want to replicate from Daniel's map are his rhumblines.  Normally rhumblines radiate from spots in the ocean, or from a compass.  Daniel has done something interesting by making them come from the middle of the land.  This makes them useless as navigational aids, but it looks pretty cool.  It looks a little odd to have these radiate from the exact center of my (square) maps, so I'll offset the lines upward a bit.

The last thing I want to replicate is something Daniel does on the larger islands:
He puts a white circle around the island, and fills the circle with transparent white to lighten the background.  I don't think this means anything; it appears to be just a decoration.  Still, it looks neat and I'd like to be able to duplicate it.

Picking the islands to decorate is an interesting problem.  Daniel doesn't decorate all of the islands, generally avoiding those that are in clusters with other large islands or too close to the mainland.  He also doesn't do this decoration on two islands close to each other.  Here's my first attempt to pick out islands:
I have a test to make sure an island is not too close to the mainland, but I've got the polarity of the test reversed, so I'm picking islands that are close instead of far.  Here's a second attempt:
This is better, but the upper island is too close to another big island.  I want these to be somewhat isolated islands (at least from other big islands).  Filtering for that gets me this:
Now I just need to adjust the circle and fill:
That's a pretty reasonable approximation.

There are a few other features of Daniels' map that I won't be reproducing.  His city icons are little works of art:
And while Dragons Abound's city icons are not terrible, they're not nearly as nice as this.  (But that's something I hope to revisit!)

Daniel also does something nice with his country borders by adding a halo effect:
I rather like that, and stylistically it fits in well with his labels.  Dragons Abound does do country borders and I could add this effect fairly easily, but I'm not happy with how Dragons Abound places borders so I don't use that at the moment.

Finally, there's a coast decoration Daniel does with a looping line on the inside of the coast:
That's very nice but I don't have any good ideas on how to easily recreate it, so I'm going to pass on that as well.

Next time, some complete maps!