The library provides autojoin16, autojoin47, and autojoin256 procs for turfs and movable atoms. This lets you easily set the state of a turf or add an overlay to create autojoining effects.
To create an outline around walls, all you have to do is:
turf
New()
..()
if(density)
var/n = autojoin16("density")
overlays += icon('outline.dmi', "[n]")
The autojoin16() proc checks the density var of each neighbor and returns a number in the 0 .. 15 range based on which neighbors are dense. You can use this value to change the turf's icon_state or add an overlay that creates a visually consistent effect from tile to tile.
Performance-wise, I think you'll actually do better switching a lot of these procs to use get_step() instead of locate(). The locate() implementation has to be much less efficient than get_step() over a large number of runs, because you're doing match operations on x and y instead of letting the system handle it, which is just running more instructions than you need. I prefer to use loops for my implementations, but perhaps the unrolling you use is faster.
From what I can tell, it also looks like your 47-state joining is broken. The logic in your code says the corner is filled if 1) either side is a match, or 2) the corner itself is a match. In 47-state joining a corner should only be filled if both adjacent sides and the corner all match. This is simple to calculate by doing just a 256-state join and then some bit twiddling. The bit twiddling would be the same for your bitflags as the standard ones, except that you'd use an OR of 170 instead of 85.