chiark / gitweb /
Initial just-about-works completely shonky version
[bedbugs.git] / src / bedbugs.c
diff --git a/src/bedbugs.c b/src/bedbugs.c
new file mode 100644 (file)
index 0000000..ef9fd3c
--- /dev/null
@@ -0,0 +1,251 @@
+/*
+ * Relevant bits of original copyright notice from
+ * http://bjh21.me.uk/bedstead/  bedstead.c  #137:
+ *
+ * [...] the file is covered by the following:
+ *
+ * Copyright (c) 2009 Ben Harris.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+/*
+ * This is a program to construct an outline font from a bitmap.  It's
+ * based on the character-rounding algorithm of the Mullard SAA5050
+ * series of Teletext character generators, and thus works best on
+ * character shapes in the same style of those of the SAA5050.  This
+ * file includes all of the glyphs from the SAA5050, SAA5051, SAA5052,
+ * SAA5053, SAA5054, SAA5055, SAA5056, and SAA5057.  The output is a
+ * Spline Font Database file suitable for feeding to Fontforge.
+ *
+ * The character-smoothing algorithm of the SAA5050 and friends is
+ * a fairly simple means of expanding a 5x9 pixel character to 10x18
+ * pixels for use on an interlaced display.  All it does is to detect
+ * 2x2 clumps of pixels containing a diagonal line and add a couple of
+ * subpixels to it, like this:
+ *
+ * . #  -> . . # # -> . . # # or # . -> # # . . -> # # . .
+ * # .     . . # #    . # # #    . #    # # . .    # # # .
+ *         # # . .    # # # .           . . # #    . # # #
+ *         # # . .    # # . .           . . # #    . . # #
+ *
+ * This is applied to every occurrence of these patterns, even when
+ * they overlap, and the result is that thin diagonal lines are
+ * smoothed out while other features mostly remain the same.
+ *
+ * One way of extending this towards continuity would be to repeatedly
+ * double the resolution and add more pixels to diagonals each time,
+ * but this ends up with the diagonals being much too heavy.  Instead,
+ * in places where the SAA5050 would add pixels, this program adds a
+ * largeish triangle to each unfilled pixel, and removes a small
+ * triangle from each filled one, something like this:
+ *
+ * . #  -> . . # # -> . . / # or # . -> # # . . -> # \ . .
+ * # .     . . # #    . / # /    . #    # # . .    \ # \ .
+ *         # # . .    / # / .           . . # #    . \ # \
+ *         # # . .    # / . .           . . # #    . . \ #
+ * 
+ * The position of the lines is such that on a long diagonal line, the
+ * amount of filled space is the same as in the rounded bitmap.  There
+ * are a few additional complications, in that the trimming of filled
+ * pixels can leave odd gaps where a diagonal stem joins another one,
+ * so the code detects this and doesn't trim in these cases:
+ *
+ * . # # -> . . # # # # -> . . / # # # -> . . / # # #
+ * # . .    . . # # # #    . / # / # #    . / # # # #
+ *          # # . . . .    / # / . . .    / # / . . .
+ *          # # . . . .    # / . . . .    # / . . . .
+ *
+ * That is the interesting part of the program, and is in the dochar()
+ * function.  Most of the rest is just dull geometry to join all the
+ * bits together into a sensible outline.  Much of the code is wildly
+ * inefficient -- O(n^2) algorithms aren't much of a problem when you
+ * have at most a few thousand points to deal with.
+ *
+ * A rather nice feature of the outlines produced by this program is
+ * that when rasterised at precisely 10 or 20 pixels high, they
+ * produce the input and output respectively of the character-rounding
+ * process.  While there are obious additional smoothings that could
+ * be applied, doing so would probably lose this nice property.
+ *
+ * [...]
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static bool *
+blank(int size)
+{
+    return calloc(size*size, sizeof(bool));
+}
+
+static void output(bool *r, int size) {
+    int x, y;
+    for (y=0; y<size; y++) {
+        putchar('\"');
+        for (x=0; x<size; x++) {
+            putchar(r[x+y*size] ? 'A' : '.');
+        }
+        putchar('\"');
+        putchar('\n');
+    }
+}
+
+static bool *
+triangle(bool *r, int size, int startpos, bool top, bool left)
+{
+    int x, y;
+    for (y = 0; y < startpos; y++) {
+        for (x = 0; x < startpos-y; x++) {
+            int rx, ry;
+            if (top) {
+                ry = y;
+            } else {
+                ry = size-1 - y;
+            }
+            if (left) {
+                rx = x;
+            } else {
+                rx = size-1 - x;
+            }
+            r[rx+ry*size]=true;
+        }
+    }
+    return r;
+}
+
+static void
+triangles(bool *r, int size, int startpos, int bl, int br, int tr, int tl)
+{
+    if (!bl) {
+        triangle(r, size, startpos, false, true);
+    }
+    if (!br) {
+        triangle(r, size, startpos, false, false);
+    }
+    if (!tr) {
+        triangle(r, size, startpos, true, false);
+    }
+    if (!tl) {
+        triangle(r, size, startpos, true, true);
+    }
+}
+
+static void
+whitepixel(bool *r, int size, int bl, int br, int tr, int tl)
+{
+    /* wrt blackpixel(): -1 for adjacency, -1 for gridlines */
+    const int startpos = size-size/4-2;
+    triangles(r, size, startpos, bl, br, tr, tl);
+}
+
+static void
+blackpixel(bool *r, int size, int bl, int br, int tr, int tl)
+{
+    const int startpos = size/4;
+    int i;
+    triangles(r, size, startpos, bl, br, tr, tl);
+    for (i=0; i<size*size; i++) {
+        r[i] = !r[i];
+    }
+}
+
+int main(int argc, char *argv[])
+{
+#define GETPIX(x,y) (!!(iter & 1u<<((y)*3+(x))))
+#define L GETPIX(x-1, y)
+#define R GETPIX(x+1, y)
+#define U GETPIX(x, y-1)
+#define D GETPIX(x, y+1)
+#define UL GETPIX(x-1, y-1)
+#define UR GETPIX(x+1, y-1)
+#define DL GETPIX(x-1, y+1)
+#define DR GETPIX(x+1, y+1)
+
+        int iter;
+        for (iter = 0; iter < 1u<<9; iter++) {
+                        int state, x = 1, y = 1;
+                       if (GETPIX(x, y)) {
+                               bool tl, tr, bl, br;
+
+                               /* Assume filled in */
+                               tl = tr = bl = br = true;
+                               /* Check for diagonals */
+                               if ((UL && !U && !L) || (DR && !D && !R))
+                                       tr = bl = false;
+                               if ((UR && !U && !R) || (DL && !D && !L))
+                                       tl = br = false;
+                               /* Avoid odd gaps */
+                               if (L || UL || U) tl = true;
+                               if (R || UR || U) tr = true;
+                               if (L || DL || D) bl = true;
+                               if (R || DR || D) br = true;
+                                state = 2 + 16 + (bl | br<<1 | tr<<2 | tl<<3);
+                       } else {
+                               bool tl, tr, bl, br;
+
+                               /* Assume clear */
+                               tl = tr = bl = br = false;
+                               /* white pixel -- just diagonals */
+                               if (L && U && !UL) tl = true;
+                               if (R && U && !UR) tr = true;
+                               if (L && D && !DL) bl = true;
+                               if (R && D && !DR) br = true;
+                                state = 2 + (bl | br<<1 | tr<<2 | tl<<3);
+                       }
+                        printf("%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
+                               GETPIX(x,y),U,UR,R,DR,D,DL,L,UL,state);
+        }
+        {
+            const int sizes[] = {7, 15, 31};
+            int size_index;
+            for (size_index = 0;
+                 size_index < sizeof(sizes)/sizeof(sizes[0]);
+                 size_index++) {
+                int size = sizes[size_index], state;
+                printf("XPM\n/* width height num_colors chars_per_pixel */\n");
+                printf("\"%d %d 2 1\"\n", size, size*33);
+                printf("/* colors */\n\". c #000000\"\n\"A c #FFFFFF\"\n");
+                /* icons never used for state 0 */
+                for (state = 1; state < 34; state++) {
+                    bool *r = blank(size);
+                    if (state == 0) {
+                        ; /* nothing */
+                    } else if (state == 1) {
+                        /* everything */
+                        memset(r, 1, size*size*sizeof(*r));
+                    } else if (state < 16+2) {
+                        int bits = ~(state-2);
+                        whitepixel(r, size, bits&1, bits&2, bits&4, bits&8);
+                    } else {
+                        int bits = state-16-2;
+                        blackpixel(r, size, bits&1, bits&2, bits&4, bits&8);
+                    }
+                    printf("/* icon for state %d */\n", state);
+                    output(r, size);
+                    free(r);
+                }
+            }
+        }
+        return 0;
+}