about summary refs log tree commit diff
path: root/converter/other/pamtosvg/image-proc.c
diff options
context:
space:
mode:
Diffstat (limited to 'converter/other/pamtosvg/image-proc.c')
-rw-r--r--converter/other/pamtosvg/image-proc.c516
1 files changed, 516 insertions, 0 deletions
diff --git a/converter/other/pamtosvg/image-proc.c b/converter/other/pamtosvg/image-proc.c
new file mode 100644
index 00000000..287e6384
--- /dev/null
+++ b/converter/other/pamtosvg/image-proc.c
@@ -0,0 +1,516 @@
+/* image-proc.c: image processing routines */
+
+#include <assert.h>
+#include <math.h>
+#include <string.h>
+
+#include "mallocvar.h"
+
+#include "message.h"
+
+#include "image-proc.h"
+
+#define BLACK 0
+#define WHITE 0xff
+#ifndef M_SQRT2
+#define M_SQRT2 1.41421356237
+#endif
+
+#if 0
+struct etyp { int t00, t11, t01, t01s; };
+
+
+static bool get_edge(bitmap_type, int y, int x, struct etyp *t);
+static void check(int v1, int v2, int v3, struct etyp *t);
+#endif
+
+
+/* Allocate storage for a new distance map with the same dimensions
+   as BITMAP and initialize it so that pixels in BITMAP with value
+   TARGET_VALUE are at distance zero and all other pixels are at
+   distance infinity.  Then compute the gray-weighted distance from
+   every non-target point to the nearest target point. */
+
+distance_map_type
+new_distance_map(bitmap_type bitmap, unsigned char target_value, bool padded, at_exception_type * exp)
+{
+    signed x, y;
+    float d, min;
+    distance_map_type dist;
+    unsigned char *b = bitmap.bitmap;
+    unsigned w = bitmap.width;
+    unsigned h = bitmap.height;
+    unsigned spp = bitmap.np;
+
+    dist.height = h; dist.width = w;
+    MALLOCARRAY(dist.d, h);
+    MALLOCARRAY(dist.weight, h);
+    if (dist.d == NULL || dist.weight == NULL)
+        pm_error("Unable to get memory for distance map");
+    for (y = 0; y < (signed) h; y++)
+    {
+        MALLOCARRAY(dist.d[y], w);
+        if (dist.d[y] == NULL)
+            pm_error("Unable to get memory for distance map");
+        bzero(dist.d[y], w * sizeof(float));
+        
+        MALLOCARRAY(dist.weight[y], w);
+        if (dist.weight[y] == NULL)
+            pm_error("Unable to get memory for distance map");
+    }
+
+    if (spp == 3)
+    {
+      for (y = 0; y < (signed) h; y++)
+      {
+        for (x = 0; x < (signed) w; x++, b += spp)
+        {
+          int gray; float fgray;
+          gray = (int)LUMINANCE(b[0], b[1], b[2]);
+          dist.d[y][x] = (gray == target_value ? 0.0F : 1.0e10F);
+          fgray = gray * 0.0039215686F;  /* = gray / 255.0F */
+          dist.weight[y][x] = 1.0F - fgray;
+/*        dist.weight[y][x] = 1.0F - (fgray * fgray);*/
+/*        dist.weight[y][x] = (fgray < 0.5F ? 1.0F - fgray : -2.0F * fgray * (fgray - 1.0F));*/
+	    }
+      }
+    }
+    else
+    {
+      for (y = 0; y < (signed) h; y++)
+      {
+        for (x = 0; x < (signed) w; x++, b += spp)
+        {
+          int gray; float fgray;
+          gray = b[0];
+          dist.d[y][x] = (gray == target_value ? 0.0F : 1.0e10F);
+          fgray = gray * 0.0039215686F;  /* = gray / 255.0F */
+          dist.weight[y][x] = 1.0F - fgray;
+/*        dist.weight[y][x] = 1.0F - (fgray * fgray);*/
+/*        dist.weight[y][x] = (fgray < 0.5F ? 1.0F - fgray : -2.0F * fgray * (fgray - 1.0F)); */
+        }
+      }
+    }
+
+    /* If the image is padded then border points are all at most
+       one unit away from the nearest target point. */
+    if (padded)
+    {
+        for (y = 0; y < (signed) h; y++)
+        {
+            if (dist.d[y][0] > dist.weight[y][0])
+                dist.d[y][0] = dist.weight[y][0];
+            if (dist.d[y][w - 1] > dist.weight[y][w - 1])
+                dist.d[y][w - 1] = dist.weight[y][w - 1];
+        }
+        for (x = 0; x < (signed) w; x++)
+        {
+            if (dist.d[0][x] > dist.weight[0][x])
+                dist.d[0][x] = dist.weight[0][x];
+            if (dist.d[h - 1][x] > dist.weight[h - 1][x])
+                dist.d[h - 1][x] = dist.weight[h - 1][x];
+        }
+    }
+
+    /* Scan the image from left to right, top to bottom.
+       Examine the already-visited neighbors of each point (those
+       situated above or to the left of it).  Each neighbor knows
+       the distance to its nearest target point; add to this distance
+       the distance from the central point to the neighbor (either
+       sqrt(2) or one) multiplied by the central point's weight
+       (derived from its gray level).  Replace the distance already
+       stored at the central point if the new distance is smaller. */
+    for (y = 1; y < (signed) h; y++)
+    {
+        for (x = 1; x < (signed) w; x++)
+        {
+            if (dist.d[y][x] == 0.0F) continue;
+
+            min = dist.d[y][x];
+
+            /* upper-left neighbor */
+            d = dist.d[y - 1][x - 1] + (float) M_SQRT2 * dist.weight[y][x];
+            if (d < min) min = dist.d[y][x] = d;
+
+            /* upper neighbor */
+            d = dist.d[y - 1][x] + dist.weight[y][x];
+            if (d < min) min = dist.d[y][x] = d;
+
+            /* left neighbor */
+            d = dist.d[y][x - 1] + dist.weight[y][x];
+            if (d < min) min = dist.d[y][x] = d;
+
+            /* upper-right neighbor (except at the last column) */
+            if (x + 1 < (signed) w)
+            {
+                d = dist.d[y - 1][x + 1] + (float) M_SQRT2 * dist.weight[y][x];
+                if (d < min) min = dist.d[y][x] = d;
+            }
+        }
+    }
+
+    /* Same as above, but now scanning right to left, bottom to top. */
+    for (y = h - 2; y >= 0; y--)
+    {
+        for (x = w - 2; x >= 0; x--)
+        {
+            min = dist.d[y][x];
+
+            /* lower-right neighbor */
+            d = dist.d[y + 1][x + 1] + (float) M_SQRT2 * dist.weight[y][x];
+	        if (d < min) min = dist.d[y][x] = d;
+
+            /* lower neighbor */
+            d = dist.d[y + 1][x] + dist.weight[y][x];
+	        if (d < min) min = dist.d[y][x] = d;
+
+            /* right neighbor */
+            d = dist.d[y][x + 1] + dist.weight[y][x];
+	        if (d < min) min = dist.d[y][x] = d;
+
+            /* lower-left neighbor (except at the first column) */
+            if (x - 1 >= 0)
+            {
+                d = dist.d[y + 1][x - 1] + (float) M_SQRT2 * dist.weight[y][x];
+                if (d < min) min = dist.d[y][x] = d;
+            }
+        }
+    }
+    return dist;
+}
+
+
+/* Free the dynamically-allocated storage associated with a distance map. */
+
+void
+free_distance_map(distance_map_type *dist)
+{
+    unsigned y, h;
+
+    if (!dist) return;
+
+    h = dist->height;
+
+    if (dist->d != NULL)
+    {
+	for (y = 0; y < h; y++)
+	    free(dist->d[y]);
+        free(dist->d);
+    }
+    if (dist->weight != NULL)
+    {
+	for (y = 0; y < h; y++)
+	    free(dist->weight[y]);
+        free(dist->weight);
+    }
+}
+
+
+#if 0
+void
+medial_axis(bitmap_type *bitmap, distance_map_type *dist,
+            bool bgSpec, pixel bg_color)
+{
+    unsigned x, y, test;
+    unsigned w, h;
+    unsigned char *b;
+    float **d, f;
+    pixel bg;
+
+    assert(bitmap != NULL);
+
+    assert(BITMAP_PLANES(*bitmap) == 1);
+
+    b = BITMAP_BITS(*bitmap);
+    assert(b != NULL);
+    assert(dist != NULL);
+    d = dist->d;
+    assert(d != NULL);
+
+    h = BITMAP_HEIGHT(*dist);
+    w = BITMAP_WIDTH(*dist);
+    assert(BITMAP_WIDTH(*bitmap) == w && BITMAP_HEIGHT(*bitmap) == h);
+
+    if (bgSpec)
+        bg = bg_color;
+    else 
+        PPM_ASSIGN(bg, 255, 255, 255);
+
+    f = d[0][0] + 0.5;
+    test = (f < d[1][0]) + (f < d[1][1]) + (f < d[0][1]);
+    if (test > 1) b[0] = PPM_GETR(bg);
+
+    f = d[0][w-1] + 0.5;
+    test = (f < d[1][w-1]) + (f < d[1][w-2]) + (f < d[0][w-2]);
+    if (test > 1) b[w-1] = PPM_GETR(bg);
+
+    for (x = 1; x < w - 1; x++)
+    {
+	    f = d[0][x] + 0.5;
+	    test = (f < d[0][x-1]) + (f < d[0][x+1]) + (f < d[1][x-1])
+	        + (f < d[1][x]) + (f < d[1][x+1]);
+	    if (test > 1) b[x] = PPM_GETR(bg);
+    }
+    b += w;
+
+    for (y = 1; y < h - 1; y++)
+    {
+	    f = d[y][0] + 0.5;
+	    test = (f < d[y-1][0]) + (f < d[y-1][1]) + (f < d[y][1])
+	        + (f < d[y+1][0]) + (f < d[y+1][1]);
+	    if (test > 1) b[0] = PPM_GETR(bg);
+
+	    for (x = 1; x < w - 1; x++)
+		{
+	        f = d[y][x] + 0.5;
+	        test = (f < d[y-1][x-1]) + (f < d[y-1][x]) + (f < d[y-1][x+1])
+		    + (f < d[y][x-1]) + (f < d[y][x+1])
+		    + (f < d[y+1][x-1]) + (f < d[y+1][x]) + (f < d[y+1][x+1]);
+	        if (test > 1) b[x] = PPM_GETR(bg)
+		}
+
+	    f = d[y][w-1] + 0.5;
+	    test = (f < d[y-1][w-1]) + (f < d[y-1][w-2]) + (f < d[y][w-2])
+	        + (f < d[y+1][w-1]) + (f < d[y+1][w-2]);
+	    if (test > 1)
+	        b[w-1] = PPM_GETR(bg);
+
+        b += w;
+    }
+
+    for (x = 1; x < w - 1; x++)
+    {
+	    f = d[h-1][x] + 0.5;
+	    test = (f < d[h-1][x-1]) + (f < d[h-1][x+1])
+	        + (f < d[h-2][x-1]) + (f < d[h-2][x]) + (f < d[h-2][x+1]);
+	    if (test > 1) b[x] = PPM_GETR(bg);
+    }
+
+    f = d[h-1][0] + 0.5;
+    test = (f < d[h-2][0]) + (f < d[h-2][1]) + (f < d[h-1][1]);
+    if (test > 1) b[0] = PPM_GETR(bg);
+
+    f = d[h-1][w-1] + 0.5;
+    test = (f < d[h-2][w-1]) + (f < d[h-2][w-2]) + (f < d[h-1][w-2]);
+    if (test > 1) b[w-1] = PPM_GETR(bg);
+}
+#endif
+
+
+/* Binarize a grayscale or color image. */
+
+void
+binarize(bitmap_type *bitmap)
+{
+    unsigned i, npixels, spp;
+    unsigned char *b;
+
+    assert(bitmap != NULL);
+    assert(bitmap->bitmap != NULL);
+
+    b = bitmap->bitmap;
+    spp = bitmap->np;
+    npixels = bitmap->width * bitmap->height;
+
+    if (spp == 1)
+    {
+	    for (i = 0; i < npixels; i++)
+	        b[i] = (b[i] > GRAY_THRESHOLD ? WHITE : BLACK);
+    }
+    else if (spp == 3)
+    {
+	    unsigned char *rgb = b;
+	    for (i = 0; i < npixels; i++, rgb += 3)
+		{
+	        b[i] = (LUMINANCE(rgb[0], rgb[1], rgb[2]) > GRAY_THRESHOLD
+		        ? WHITE : BLACK);
+		}
+	    REALLOCARRAY_NOFAIL(bitmap->bitmap, npixels);
+	    bitmap->np = 1;
+    }
+    else
+    {
+	    WARNING1("binarize: %u-plane images are not supported", spp);
+    }
+}
+
+
+#if 0
+/* Thin a binary image, replacing the original image with the thinned one. */
+
+bitmap_type
+ip_thin(bitmap_type input_b)
+{
+    unsigned y, x, i;
+    bool k, again;
+    struct etyp t;
+    unsigned w = BITMAP_WIDTH(input_b);
+    unsigned h = BITMAP_HEIGHT(input_b);
+    size_t num_bytes = w * h;
+    bitmap_type b = input_b;
+
+    if (BITMAP_PLANES(input_b) != 1)
+    {
+	    FATAL1("thin: single-plane image required; "
+	        "%u-plane images cannot be thinned", BITMAP_PLANES(input_b));
+	    return b;
+    }
+
+    /* Process and return a copy of the input image. */
+    MALLOCARRAY(b.bitmap, num_bytes);
+    if (b.bitmap == NULL)
+        pm_error("Unable to get memory for bitmap");
+    memcpy(b.bitmap, input_b.bitmap, num_bytes);
+
+    /* Set background pixels to zero, foreground pixels to one. */
+    for (i = 0; i < num_bytes; i++)
+	b.bitmap[i] = (b.bitmap[i] == BLACK ? 1 : 0);
+
+    again = true;
+    while (again)
+    {
+	again = false;
+
+	for (y = 1; y < h - 1; y++)
+	{
+	    for (x = 1; x < w - 1; x++)
+	    {
+		    /* During processing, pixels are used to store edge
+		       type codes, so we can't just test for WHITE or BLACK. */
+		    if (*BITMAP_PIXEL(b, y, x) == 0) continue;
+
+		    k = (!get_edge(b, y, x, &t)
+		        || (get_edge(b, y, x+1, &t) && *BITMAP_PIXEL(b, y-1, x)
+			    && *BITMAP_PIXEL(b, y+1, x))
+		        || (get_edge(b, y+1, x, &t) && *BITMAP_PIXEL(b, y, x-1)
+			    && *BITMAP_PIXEL(b, y, x+1))
+		        || (get_edge(b, y, x+1, &t) && get_edge(b, y+1, x+1, &t)
+			    && get_edge(b, y+1, x, &t)));
+		    if (k) continue;
+
+		    get_edge(b, y, x, &t);
+		    if (t.t01) *BITMAP_PIXEL(b, y, x) |= 4;
+		    *BITMAP_PIXEL(b, y, x) |= 2;
+		    again = true;
+	    }
+	}
+
+	for (y = 0; y < h; y++)
+	    for (x = 0; x < w; x++)
+		    if (*BITMAP_PIXEL(b, y, x) & 02) *BITMAP_PIXEL(b, y, x) = 0;
+
+	for (y = 1; y < h - 1; y++)
+	{
+	    for (x = 1; x < w - 1; x++)
+	    {
+		    if (*BITMAP_PIXEL(b, y, x) == 0) continue;
+
+		    k = (!get_edge(b, y, x, &t)
+		        || ((*BITMAP_PIXEL(b, y, x) & 04) == 0)
+		        || (get_edge(b, y+1, x, &t) && (*BITMAP_PIXEL(b, y, x-1))
+			    && *BITMAP_PIXEL(b, y, x+1))
+		        || (get_edge(b, y, x+1, &t) && *BITMAP_PIXEL(b, y-1, x)
+			    && *BITMAP_PIXEL(b, y+1, x))
+		        || (get_edge(b, y+1, x, &t) && get_edge(b, y, x+1, &t)
+			    && get_edge(b, y+1, x+1, &t)));
+		    if (k) continue;
+
+		    *BITMAP_PIXEL(b, y, x) |= 02;
+		    again = true;
+	    }
+	}
+
+	for (y = 0; y < h; y++)
+	{
+	    for (x = 0; x < w; x++)
+	    {
+		    if (*BITMAP_PIXEL(b, y, x) & 02) *BITMAP_PIXEL(b, y, x) = 0;
+		    else if (*BITMAP_PIXEL(b, y, x) > 0) *BITMAP_PIXEL(b, y, x) = 1;
+	    }
+	}
+    }
+
+    /* Staircase removal; northward bias. */
+    for (y = 1; y < h - 1; y++)
+    {
+	    for (x = 1; x < w - 1; x++)
+		{
+	        if (*BITMAP_PIXEL(b, y, x) == 0) continue;
+
+	        k = !(*BITMAP_PIXEL(b, y-1, x)
+		        && ((*BITMAP_PIXEL(b, y, x+1) && !*BITMAP_PIXEL(b, y-1, x+1)
+		        && !*BITMAP_PIXEL(b, y+1, x-1)
+		        && (!*BITMAP_PIXEL(b, y, x-1) || !*BITMAP_PIXEL(b, y+1, x)))
+		        || (*BITMAP_PIXEL(b, y, x-1) && !*BITMAP_PIXEL(b, y-1, x-1)
+		        && !*BITMAP_PIXEL(b, y+1, x+1) &&
+		        (!*BITMAP_PIXEL(b, y, x+1) || !*BITMAP_PIXEL(b, y+1, x)))));
+	        if (k) continue;
+
+	        *BITMAP_PIXEL(b, y, x) |= 02;
+		}
+    }
+    for (y = 0; y < h; y++)
+    {
+	    for (x = 0; x < w; x++)
+		{
+	        if (*BITMAP_PIXEL(b, y, x) & 02) *BITMAP_PIXEL(b, y, x) = 0;
+	        else if (*BITMAP_PIXEL(b, y, x) > 0) *BITMAP_PIXEL(b, y, x) = 1;
+		}
+    }
+
+    /* Southward bias */
+    for (y = 1; y < h - 1; y++)
+    {
+	    for (x = 1; x < w - 1; x++)
+		{
+	        if (*BITMAP_PIXEL(b, y, x) == 0) continue;
+
+	        k = !(*BITMAP_PIXEL(b, y+1, x)
+		    && ((*BITMAP_PIXEL(b, y, x+1) && !*BITMAP_PIXEL(b, y+1, x+1)
+		    && !*BITMAP_PIXEL(b, y-1, x-1) && (!*BITMAP_PIXEL(b, y, x-1)
+		    || !*BITMAP_PIXEL(b, y-1, x))) || (*BITMAP_PIXEL(b, y, x-1)
+		    && !*BITMAP_PIXEL(b, y+1, x-1) && !*BITMAP_PIXEL(b, y-1, x+1)
+		    && (!*BITMAP_PIXEL(b, y, x+1) || !*BITMAP_PIXEL(b, y-1, x)) )));
+	        if (k) continue;
+
+	        *BITMAP_PIXEL(b, y, x) |= 02;
+		}
+    }
+    for (y = 0; y < h; y++)
+    {
+	    for (x = 0; x < w; x++)
+		{
+	        if (*BITMAP_PIXEL(b, y, x) & 02) *BITMAP_PIXEL(b, y, x) = 0;
+	        else if (*BITMAP_PIXEL(b, y, x) > 0) *BITMAP_PIXEL(b, y, x) = 1;
+		}
+    }
+
+    /* Set background pixels to WHITE, foreground pixels to BLACK. */
+    for (i = 0; i < num_bytes; i++)
+	b.bitmap[i] = (b.bitmap[i] == 0 ? WHITE : BLACK);
+    return b;
+}
+
+
+bool get_edge(bitmap_type b, int y, int x, struct etyp *t)
+{
+    t->t00 = 0; t->t01 = 0; t->t01s = 0; t->t11 = 0;
+    check(*BITMAP_PIXEL(b, y - 1, x - 1), *BITMAP_PIXEL(b, y - 1, x),
+	*BITMAP_PIXEL(b, y - 1, x + 1), t);
+    check(*BITMAP_PIXEL(b, y - 1, x + 1), *BITMAP_PIXEL(b, y, x + 1),
+	*BITMAP_PIXEL(b, y + 1, x + 1), t);
+    check(*BITMAP_PIXEL(b, y + 1, x + 1), *BITMAP_PIXEL(b, y + 1, x),
+	*BITMAP_PIXEL(b, y + 1, x - 1), t);
+    check(*BITMAP_PIXEL(b, y + 1, x - 1), *BITMAP_PIXEL(b, y, x - 1),
+	*BITMAP_PIXEL(b, y - 1, x - 1), t);
+    return *BITMAP_PIXEL(b, y, x) && t->t00 && t->t11 && !t->t01s;
+}
+
+
+void check(int v1, int v2, int v3, struct etyp *t)
+{
+    if (!v2 && (!v1 || !v3)) t->t00 = 1;
+    if (v2 && (v1 || v3)) t->t11 = 1;
+    if ((!v1 && v2) || (!v2 && v3)) { t->t01s = t->t01; t->t01 = 1; }
+}
+#endif