Mon, 18 Sep 2023


— SjG @ 12:21 pm

When designing visual stuff, I often would like the ability to create a patterned frame for whatever it is I’m doing. It’s not so much work to drag and move image tiles around to make a nice frame, but I wanted to make it easier.

Thus “Borderline” was born. It’s a JavaScript tool for laying out tiles into frame designs, with a few automated features to make playing around easier.

Borderline lets you arrange small SVG files to form a border. It optionally does some rotational and mirroring transforms to make the pattern look nice.

It includes a small collection of custom, original SVG tiles to use for making nice borders. You can also “upload” your own! (since this all takes place in your browser, nothing actually gets uploaded to a server or leaves your local machine).

Once you’re happy with your design, you can export the entire border as an SVG suitable for printing or modification in your favorite vector graphics application.

You should be able to use just about any SVG file as a tile, although if it’s too big or complicated your browser may run out of memory and behave badly.

You can try it here: Borderline. Source is available at Codeberg.

Thu, 20 Apr 2023

Taming logwatch on Linux*

— SjG @ 6:59 am

*This is actually on Rocky Linux / CENTOS / RHEL, but will likely work on others.

Logwatch can be a nice tool for keeping an eye on your servers. It goes through your logs and creates a nightly aggregate email containing information to keep you apprised of various important details. It can be good to bring things to the attention of lazy / overwhelmed sysadmins like me.

Where it fails, though, is where it overwhelms you with useless information. There are different output level settings, and if you turn the detail levels down far enough, it helps a lot. However, with certain configs and certain OSes, you still get overwhelmed with non-actionable information. Here’s how to fix a few of those.

Crontabs. In Rocky Linux, cron logs a success message that contains a process number, which means the default log is filled with lots and lots of lines like session-685197.scope: Succeeded.: 1 Time(s) which logwatch happily throws into the nightly email. Most searches tell you to edit your /etc/logwatch/conf/ignore.conf file and add the following line:

session-.*scope: Succeeded

This doesn’t work for me. Further research indicates that the ignore.conf file wants a Perl-style regular expression. The recommendation above is sort-of-Perlish, but what ended up working correctly for me was putting the following line in my ignore.conf:

\s*session-(.*?)\.scope: Succeeded\.(.*)

HTTP. For some reason, someone thought having a long list of hostile IP addresses would be helpful. Maybe to manually block them? Seems like a hopeless task. Check out /usr/share/logwatch/scripts/services/http around line 596… and un-comment out the conditional.

$flag = 1;
foreach my $i (sort keys %ban_ip) {
   if ($flag) {
      print "\nA total of ".scalar(keys %ban_ip)." sites probed the server \n";
      $flag = 0;
   #if ($detail > 4) {
      print "   $i\n";

sshd. I know there are a lot of hackers, script kiddies, and bots out there. I don’t need to see the long list of people who tried and failed to log in with ssh. Unfortunately, the detail level setting for sshd aren’t very helpful. I ended up editing /usr/share/logwatch/scripts/services/sshd and liberally sprinkling my own if ($Detail > 4) {} barriers starting around line 500. Hacky, I know. Also will be clobbered with the next logwatch update. Yuck.

Maybe it’s time for me to submit a bunch of pull requests.

Sun, 4 Sep 2022

Illustration, in History and in the Future

— SjG @ 7:51 am

We’ve been watching many episodes of Pete Beard’s YouTube channel on the unsung heroes of illustration. Beard does a nice job of giving you the life details of these people, along with showing you representative samples of their work.

For a period that lasted roughly 150 years, a talented illustrator could become rich and famous. The increasing general literacy rates and growth of leisure time created an expanding market for books in the late 18th century. There were also technological breakthroughs in printing, which made adding illustrations to books more affordable, and publishers found that the illustrated books sold better. And while the concept of periodical magazines goes back to the 17th century, it was in the later part of the 19th century that illustrated magazines exploded in popularity.

Magazines were the YouTube or Twitter of their day, the place where culture was discussed and developed, and where art movements gained their momentum. They commissioned illustrations for their covers and their content, and once an illustrator had a reputation, they could make a good living. Once an artist was well known, they became sought for advertising and poster work. Some did merch, too.

It’s fascinating to see the enormous quantity and quality of work. As someone who has dabbled in art, it’s a little overwhelming. You can view a lot of the work online. Beard’s channel is a good introduction, but then you can go down the rabbit hole and look at scans of Jugend magazine, the outstanding Illustration History site from the Norman Rockwell Museum, commercial sites like Granger, and so on. More than you can possibly absorb is just a web search away.

The growth of photography and techniques for photographic printing changed the equation in the 1950s. Illustration took a back seat, with many localized renaissances like 60s music posters and 80s ‘zine culture.

Today, another revolution is happening. Over the past year, “Artificial Intelligence” art has gone from being a fairly obscure discipline to the point where it is producing stunning results. I’ve been playing with Stable Diffusion, and the results are both impressive, unsettling, and fascinating. This system works on a mathematical model that was fed a huge volume of images and text from the internet. Given a text prompt or general directing image, it uses that model to creates a set of mathematically weighted values representing how it “understands” your request. It then uses random noise and repeated filters to create an image that satisfies those mathematical weights.

The creation of the initial model is extremely computationally expensive and involved. The creation of images using that model, however, is something that can be done on any reasonably modern home computer.

There’s debate about “who creates the art,” when using a system like this. After all, it only “knows” the art it was fed in the initial model creation. So, in that sense, it can’t create anything “new” — and yet, the way it combines things is at a low enough level that there isn’t any reproduction of any of the original work. That being said, it’s still biased by the work it was trained on. It “understands” beauty and ugliness based on the descriptions of the images it was created with. This bakes in a lot of other subtle prejudices: ranging from abstract ideas like what constitutes futuristic, to more fraught ideas like what traits define racial terms. It’s telling that the initial release of the software put a “not safe for work” filter in place, because such a high percentage of the images of women in the initial model were nude.

People are already using this software to generate printed images and even movies. There’s definitely an art to creating the source prompts to get the results you want, but this is getting easier and easier. I think it will be a matter of weeks or months before the clip-art business is just a front-end to this kind of software. It will be fascinating to see where this ends up.

Thu, 16 Jun 2022

Ugh. Change.

— SjG @ 10:36 am

It looks like some update to WordPress has broken all of the image galleries on this site.

Oh Joy! Now I’ll need to find time to redo the theme so it all works again. I mean, I just did a redesign in … 2014. Sigh.

Fri, 17 Dec 2021

Manipulating SVG in PHP, part 2.

— SjG @ 1:06 pm

As mentioned in Part 1, the purpose of this code is to help automate file format conversions and presentation for posting vector designs to Etsy. One kind of design I make is “shadow theaters,” which are essentially little layered dioramas that look nice when backlit.

Sample 8-Layer Shadow Theater

The individual layers of these designs comprise an outline, scoring lines for folding the paper, and the design itself. However, creating a thumbnail to represent the design has a unique challenge: the layers are negatives. The design file fills the areas that will be cut out, but this is where the greatest illumination will come through. I want the thumbnail to look like the photograph above. Furthermore, I can’t simply composite all the layers, as they are opaque, and the result would only show the top layer.

So the task becomes to invert the layers. But even that isn’t so simple. In each of the layers, I want to take the filled area and make it transparent, and take the unfilled area and fill it. To really make it work, the filled areas should not be a uniform color, but should vary by how far “down” in the stack the layer is.

However, this isn’t as simple as removing layers like I did in Part 1. What I have to do is take the outermost layer, fill it, and subtract the geometry of the inner design from it. Fortunately, that’s not so difficult to do in SVG. If you take a path in SVG and close it, you can continue to add sub-paths. When those sub-paths are outside the original path, they are added to it. If the sub-paths are inside the original path, they are subtracted from it. If they overlap, they’re both added and subtracted. This is probably better explained with pictures.

Disclaimer: for the purpose of this discussion, I’m assuming two SVG settings: fill-rule and clip-rule are set to “evenodd”. There are other options, but they aren’t helpful for what I want to do.

What do these paths and sub-paths look like? If you look at the example from Part 1, you can see that a path has a d attribute, which is a string of commands and coordinates. Rather than go through those here, I’ll just link the relevant Mozilla SVG tutorial page which I found helpful. The key thing is that a complete, closed path ends with a Z command. Any path details after that are a subpath.

Looking at an SVG file of one of my shadow gallery layers, what I want to do is convert the “Design” layer to a fully-contained sub-path of the “PaperOutline” layer. Based on what we know of SVG paths, that means I can just append the design path to the outline path – the design path will become a sub-path which will be subtracted from the outline path! I then can set the fill color of this composite path, and I will effectively have filled the outline and subtracted my design.

I again take advantage of the fact that the layer names are following my standard. I search through the file for the “Design” layer, and copy its path into the $subtract_path variable. Then I loop through the groups again, find the “PaperOutline” layer, and append the path. While I’m at it, I set the fill color with a shade I calculate based on the layer number.

$total_layer_count = 1;
for ($this_layer_index = 1; $this_layer_index <= $total_layer_count; $this_layer_index++)
    $doc = new DOMDocument();
    $shade = 255 * ($total_layer_count - $this_layer_index / 1.5) / $total_layer_count;
    $subtract_path = '';
    $rm = [];
    $groups = $doc->getElementsByTagName('g');
    foreach ($groups as $this_group)
        $layer_name = $this_group->getAttribute('id');
        if (preg_match('/design/i', $layer_name))
            foreach ($this_group->childNodes as $node)
                if (in_array(get_class($node), ['DOMNode', 'DOMElement']))
                    $subtract_path .= $node->getAttribute('d');
            array_push($rm, $this_group);
    foreach ($groups as $this_group)
        $layer_name = $this_group->getAttribute('id');
        if (preg_match('/outlinepaper/i', $layer_name))
            foreach ($this_group->childNodes as $node)
                if (in_array(get_class($node), ['DOMNode', 'DOMElement']))
                    $opath = $node->getAttribute('d');
                    $node->setAttribute('d', $opath . $subtract_path);
                    $style = $node->getAttribute('style');
                    if (preg_match_all('/fill:([^;]+)/', $style, $matches))
                        foreach ($matches[1] as $tm)
                            $style = str_replace($tm, "rgb($shade,$shade,$shade)", $style);
                    $node->setAttribute('style', $style);
    foreach ($rm as $trm)
    $res = $doc->saveXML();
    $out = fopen("layer-{$this_layer_index}-inverted.svg", 'w');
    fwrite($out, $res);

Not the most elegant code in the world, and will certainly fail on SVG files that don’t match my naming and layout conventions, but illustrates the process.

Once I’ve created these inverted SVGs, I convert them into PNGs, composite the stack into a single image, and come up with what I consider a handsome representation for my design thumbnail.

Composited, complete

Once I have the ability to take layers and change the transparent areas and the fill color, I can do all sorts of other manipulations. For example, I can create striking negative images, or feed the the layers into other scripts to create “exploded” views of the shadow theaters.