summaryrefslogtreecommitdiffstats
path: root/app/paint/gimpink-blob.c
diff options
context:
space:
mode:
Diffstat (limited to 'app/paint/gimpink-blob.c')
-rw-r--r--app/paint/gimpink-blob.c875
1 files changed, 875 insertions, 0 deletions
diff --git a/app/paint/gimpink-blob.c b/app/paint/gimpink-blob.c
new file mode 100644
index 0000000..68a135a
--- /dev/null
+++ b/app/paint/gimpink-blob.c
@@ -0,0 +1,875 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * gimpink-blob.c: routines for manipulating scan converted convex polygons.
+ * Copyright 1998-1999, Owen Taylor <otaylor@gtk.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+
+#include <glib-object.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "paint-types.h"
+
+#include "gimpink-blob.h"
+
+
+typedef enum
+{
+ EDGE_NONE = 0,
+ EDGE_LEFT = 1 << 0,
+ EDGE_RIGHT = 1 << 1
+} EdgeType;
+
+
+/* local function prototypes */
+
+static GimpBlob * gimp_blob_new (gint y,
+ gint height);
+static void gimp_blob_fill (GimpBlob *b,
+ EdgeType *present);
+static void gimp_blob_make_convex (GimpBlob *b,
+ EdgeType *present);
+
+#if 0
+static void gimp_blob_line_add_pixel (GimpBlob *b,
+ gint x,
+ gint y);
+static void gimp_blob_line (GimpBlob *b,
+ gint x0,
+ gint y0,
+ gint x1,
+ gint y1);
+#endif
+
+
+/* public functions */
+
+/* Return blob for the given (convex) polygon
+ */
+GimpBlob *
+gimp_blob_polygon (GimpBlobPoint *points,
+ gint n_points)
+{
+ GimpBlob *result;
+ EdgeType *present;
+ gint i;
+ gint im1;
+ gint ip1;
+ gint ymin, ymax;
+
+ ymax = points[0].y;
+ ymin = points[0].y;
+
+ for (i = 1; i < n_points; i++)
+ {
+ if (points[i].y > ymax)
+ ymax = points[i].y;
+ if (points[i].y < ymin)
+ ymin = points[i].y;
+ }
+
+ result = gimp_blob_new (ymin, ymax - ymin + 1);
+ present = g_new0 (EdgeType, result->height);
+
+ im1 = n_points - 1;
+ i = 0;
+ ip1 = 1;
+
+ for (; i < n_points ; i++)
+ {
+ gint sides = 0;
+ gint j = points[i].y - ymin;
+
+ if (points[i].y < points[im1].y)
+ sides |= EDGE_RIGHT;
+ else if (points[i].y > points[im1].y)
+ sides |= EDGE_LEFT;
+
+ if (points[ip1].y < points[i].y)
+ sides |= EDGE_RIGHT;
+ else if (points[ip1].y > points[i].y)
+ sides |= EDGE_LEFT;
+
+ if (sides & EDGE_RIGHT)
+ {
+ if (present[j] & EDGE_RIGHT)
+ {
+ result->data[j].right = MAX (result->data[j].right, points[i].x);
+ }
+ else
+ {
+ present[j] |= EDGE_RIGHT;
+ result->data[j].right = points[i].x;
+ }
+ }
+
+ if (sides & EDGE_LEFT)
+ {
+ if (present[j] & EDGE_LEFT)
+ {
+ result->data[j].left = MIN (result->data[j].left, points[i].x);
+ }
+ else
+ {
+ present[j] |= EDGE_LEFT;
+ result->data[j].left = points[i].x;
+ }
+ }
+
+ im1 = i;
+ ip1++;
+ if (ip1 == n_points)
+ ip1 = 0;
+ }
+
+ gimp_blob_fill (result, present);
+ g_free (present);
+
+ return result;
+}
+
+/* Scan convert a square specified by _offsets_ of major and minor
+ * axes, and by center into a blob
+ */
+GimpBlob *
+gimp_blob_square (gdouble xc,
+ gdouble yc,
+ gdouble xp,
+ gdouble yp,
+ gdouble xq,
+ gdouble yq)
+{
+ GimpBlobPoint points[4];
+
+ /* Make sure we order points ccw */
+
+ if (xp * yq - xq * yp < 0)
+ {
+ xq = -xq;
+ yq = -yq;
+ }
+
+ points[0].x = xc + xp + xq;
+ points[0].y = yc + yp + yq;
+ points[1].x = xc + xp - xq;
+ points[1].y = yc + yp - yq;
+ points[2].x = xc - xp - xq;
+ points[2].y = yc - yp - yq;
+ points[3].x = xc - xp + xq;
+ points[3].y = yc - yp + yq;
+
+ return gimp_blob_polygon (points, 4);
+}
+
+/* Scan convert a diamond specified by _offsets_ of major and minor
+ * axes, and by center into a blob
+ */
+GimpBlob *
+gimp_blob_diamond (gdouble xc,
+ gdouble yc,
+ gdouble xp,
+ gdouble yp,
+ gdouble xq,
+ gdouble yq)
+{
+ GimpBlobPoint points[4];
+
+ /* Make sure we order points ccw */
+
+ if (xp * yq - xq * yp < 0)
+ {
+ xq = -xq;
+ yq = -yq;
+ }
+
+ points[0].x = xc + xp;
+ points[0].y = yc + yp;
+ points[1].x = xc - xq;
+ points[1].y = yc - yq;
+ points[2].x = xc - xp;
+ points[2].y = yc - yp;
+ points[3].x = xc + xq;
+ points[3].y = yc + yq;
+
+ return gimp_blob_polygon (points, 4);
+}
+
+
+#define TABLE_SIZE 256
+
+#define ELLIPSE_SHIFT 2
+#define TABLE_SHIFT 12
+#define TOTAL_SHIFT (ELLIPSE_SHIFT + TABLE_SHIFT)
+
+/*
+ * The choose of this values limits the maximal image_size to
+ * 16384 x 16384 pixels. The values will overflow as soon as
+ * x or y > INT_MAX / (1 << (ELLIPSE_SHIFT + TABLE_SHIFT)) / SUBSAMPLE
+ *
+ * Alternatively the code could be change the code as follows:
+ *
+ * xc_base = floor (xc)
+ * xc_shift = 0.5 + (xc - xc_base) * (1 << TOTAL_SHIFT);
+ *
+ * gint x = xc_base + (xc_shift + c * xp_shift + s * xq_shift +
+ * (1 << (TOTAL_SHIFT - 1))) >> TOTAL_SHIFT;
+ *
+ * which would change the limit from the image to the ellipse size
+ *
+ * Update: this change was done, and now there apparently is a limit
+ * on the ellipse size. I'm too lazy to fully understand what's going
+ * on here and simply leave this comment here for
+ * documentation. --Mitch
+ */
+
+static gboolean trig_initialized = FALSE;
+static gint trig_table[TABLE_SIZE];
+
+/* Scan convert an ellipse specified by _offsets_ of major and
+ * minor axes, and by center into a blob
+ */
+GimpBlob *
+gimp_blob_ellipse (gdouble xc,
+ gdouble yc,
+ gdouble xp,
+ gdouble yp,
+ gdouble xq,
+ gdouble yq)
+{
+ GimpBlob *result;
+ EdgeType *present;
+ gint i;
+ gdouble r1, r2;
+ gint maxy, miny;
+ gint step;
+ gdouble max_radius;
+
+ gint xc_shift, yc_shift;
+ gint xp_shift, yp_shift;
+ gint xq_shift, yq_shift;
+ gint xc_base, yc_base;
+
+ if (! trig_initialized)
+ {
+ trig_initialized = TRUE;
+
+ for (i = 0; i < 256; i++)
+ trig_table[i] = 0.5 + sin (i * (G_PI / 128.0)) * (1 << TABLE_SHIFT);
+ }
+
+ /* Make sure we traverse ellipse in ccw direction */
+
+ if (xp * yq - xq * yp < 0)
+ {
+ xq = -xq;
+ yq = -yq;
+ }
+
+ /* Compute bounds as if we were drawing a rectangle */
+
+ maxy = ceil (yc + fabs (yp) + fabs (yq));
+ miny = floor (yc - fabs (yp) - fabs (yq));
+
+ result = gimp_blob_new (miny, maxy - miny + 1);
+ present = g_new0 (EdgeType, result->height);
+
+ xc_base = floor (xc);
+ yc_base = floor (yc);
+
+ /* Figure out a step that will draw most of the points */
+
+ r1 = sqrt (xp * xp + yp * yp);
+ r2 = sqrt (xq * xq + yq * yq);
+ max_radius = MAX (r1, r2);
+ step = TABLE_SIZE;
+
+ while (step > 1 && (TABLE_SIZE / step < 4 * max_radius))
+ step >>= 1;
+
+ /* Fill in the edge points */
+
+ xc_shift = 0.5 + (xc - xc_base) * (1 << TOTAL_SHIFT);
+ yc_shift = 0.5 + (yc - yc_base) * (1 << TOTAL_SHIFT);
+ xp_shift = 0.5 + xp * (1 << ELLIPSE_SHIFT);
+ yp_shift = 0.5 + yp * (1 << ELLIPSE_SHIFT);
+ xq_shift = 0.5 + xq * (1 << ELLIPSE_SHIFT);
+ yq_shift = 0.5 + yq * (1 << ELLIPSE_SHIFT);
+
+ for (i = 0 ; i < TABLE_SIZE ; i += step)
+ {
+ gint s = trig_table[i];
+ gint c = trig_table[(TABLE_SIZE + TABLE_SIZE / 4 - i) % TABLE_SIZE];
+
+ gint x = ((xc_shift + c * xp_shift + s * xq_shift +
+ (1 << (TOTAL_SHIFT - 1))) >> TOTAL_SHIFT) + xc_base;
+ gint y = (((yc_shift + c * yp_shift + s * yq_shift +
+ (1 << (TOTAL_SHIFT - 1))) >> TOTAL_SHIFT)) + yc_base
+ - result->y;
+
+ gint dydi = c * yq_shift - s * yp_shift;
+
+ if (dydi <= 0) /* left edge */
+ {
+ if (present[y] & EDGE_LEFT)
+ {
+ result->data[y].left = MIN (result->data[y].left, x);
+ }
+ else
+ {
+ present[y] |= EDGE_LEFT;
+ result->data[y].left = x;
+ }
+ }
+
+ if (dydi >= 0) /* right edge */
+ {
+ if (present[y] & EDGE_RIGHT)
+ {
+ result->data[y].right = MAX (result->data[y].right, x);
+ }
+ else
+ {
+ present[y] |= EDGE_RIGHT;
+ result->data[y].right = x;
+ }
+ }
+ }
+
+ /* Now fill in missing points */
+
+ gimp_blob_fill (result, present);
+ g_free (present);
+
+ return result;
+}
+
+void
+gimp_blob_bounds (GimpBlob *b,
+ gint *x,
+ gint *y,
+ gint *width,
+ gint *height)
+{
+ gint i;
+ gint x0, x1, y0, y1;
+
+ i = 0;
+ while (i < b->height && b->data[i].left > b->data[i].right)
+ i++;
+
+ if (i < b->height)
+ {
+ y0 = b->y + i;
+ x0 = b->data[i].left;
+ x1 = b->data[i].right + 1;
+
+ while (i < b->height && b->data[i].left <= b->data[i].right)
+ {
+ x0 = MIN (b->data[i].left, x0);
+ x1 = MAX (b->data[i].right + 1, x1);
+ i++;
+ }
+
+ y1 = b->y + i;
+ }
+ else
+ {
+ x0 = y0 = 0;
+ x1 = y1 = 0;
+ }
+
+ *x = x0;
+ *y = y0;
+ *width = x1 - x0;
+ *height = y1 - y0;
+}
+
+GimpBlob *
+gimp_blob_convex_union (GimpBlob *b1,
+ GimpBlob *b2)
+{
+ GimpBlob *result;
+ gint y;
+ gint i, j;
+ EdgeType *present;
+
+ /* Create the storage for the result */
+
+ y = MIN (b1->y, b2->y);
+ result = gimp_blob_new (y, MAX (b1->y + b1->height, b2->y + b2->height)-y);
+
+ if (result->height == 0)
+ return result;
+
+ present = g_new0 (EdgeType, result->height);
+
+ /* Initialize spans from original objects */
+
+ for (i = 0, j = b1->y-y; i < b1->height; i++, j++)
+ {
+ if (b1->data[i].right >= b1->data[i].left)
+ {
+ present[j] = EDGE_LEFT | EDGE_RIGHT;
+ result->data[j].left = b1->data[i].left;
+ result->data[j].right = b1->data[i].right;
+ }
+ }
+
+ for (i = 0, j = b2->y - y; i < b2->height; i++, j++)
+ {
+ if (b2->data[i].right >= b2->data[i].left)
+ {
+ if (present[j])
+ {
+ if (result->data[j].left > b2->data[i].left)
+ result->data[j].left = b2->data[i].left;
+ if (result->data[j].right < b2->data[i].right)
+ result->data[j].right = b2->data[i].right;
+ }
+ else
+ {
+ present[j] = EDGE_LEFT | EDGE_RIGHT;
+ result->data[j].left = b2->data[i].left;
+ result->data[j].right = b2->data[i].right;
+ }
+ }
+ }
+
+ gimp_blob_make_convex (result, present);
+
+ g_free (present);
+
+ return result;
+}
+
+GimpBlob *
+gimp_blob_duplicate (GimpBlob *b)
+{
+ g_return_val_if_fail (b != NULL, NULL);
+
+ return g_memdup (b, sizeof (GimpBlob) + sizeof (GimpBlobSpan) * (b->height - 1));
+}
+
+#if 0
+void
+gimp_blob_dump (GimpBlob *b)
+{
+ gint i,j;
+
+ for (i = 0; i < b->height; i++)
+ {
+ for (j = 0; j < b->data[i].left; j++)
+ putchar (' ');
+
+ for (j = b->data[i].left; j <= b->data[i].right; j++)
+ putchar ('*');
+
+ putchar ('\n');
+ }
+}
+#endif
+
+
+/* private functions */
+
+static GimpBlob *
+gimp_blob_new (gint y,
+ gint height)
+{
+ GimpBlob *result;
+
+ result = g_malloc (sizeof (GimpBlob) + sizeof (GimpBlobSpan) * (height - 1));
+
+ result->y = y;
+ result->height = height;
+
+ return result;
+}
+
+static void
+gimp_blob_fill (GimpBlob *b,
+ EdgeType *present)
+{
+ gint start;
+ gint x1, x2, i1, i2;
+ gint i;
+
+ /* Mark empty lines at top and bottom as unused */
+
+ start = 0;
+ while (! present[start])
+ {
+ b->data[start].left = 0;
+ b->data[start].right = -1;
+ start++;
+ }
+
+ if (present[start] != (EDGE_RIGHT | EDGE_LEFT))
+ {
+ if (present[start] == EDGE_RIGHT)
+ b->data[start].left = b->data[start].right;
+ else
+ b->data[start].right = b->data[start].left;
+
+ present[start] = EDGE_RIGHT | EDGE_LEFT;
+ }
+
+ for (i = b->height - 1; ! present[i]; i--)
+ {
+ b->data[i].left = 0;
+ b->data[i].right = -1;
+ }
+
+ if (present[i] != (EDGE_RIGHT | EDGE_LEFT))
+ {
+ if (present[i] == EDGE_RIGHT)
+ b->data[i].left = b->data[i].right;
+ else
+ b->data[i].right = b->data[i].left;
+
+ present[i] = EDGE_RIGHT | EDGE_LEFT;
+ }
+
+
+ /* Restore missing edges */
+
+ /* We fill only interior regions of convex hull, as if we were
+ * filling polygons. But since we draw ellipses with nearest points,
+ * not interior points, maybe it would look better if we did the
+ * same here. Probably not a big deal either way after anti-aliasing
+ */
+
+ /* left edge */
+ for (i1 = start; i1 < b->height - 2; i1++)
+ {
+ /* Find empty gaps */
+ if (! (present[i1 + 1] & EDGE_LEFT))
+ {
+ gint increment; /* fractional part */
+ gint denom; /* denominator of fraction */
+ gint step; /* integral step */
+ gint frac; /* fractional step */
+ gint reverse;
+
+ /* find bottom of gap */
+ i2 = i1 + 2;
+ while (i2 < b->height && ! (present[i2] & EDGE_LEFT))
+ i2++;
+
+ if (i2 < b->height)
+ {
+ denom = i2 - i1;
+ x1 = b->data[i1].left;
+ x2 = b->data[i2].left;
+ step = (x2 - x1) / denom;
+ frac = x2 - x1 - step * denom;
+ if (frac < 0)
+ {
+ frac = -frac;
+ reverse = 1;
+ }
+ else
+ reverse = 0;
+
+ increment = 0;
+ for (i = i1 + 1; i < i2; i++)
+ {
+ x1 += step;
+ increment += frac;
+ if (increment >= denom)
+ {
+ increment -= denom;
+ x1 += reverse ? -1 : 1;
+ }
+ if (increment == 0 || reverse)
+ b->data[i].left = x1;
+ else
+ b->data[i].left = x1 + 1;
+ }
+ }
+
+ i1 = i2 - 1; /* advance to next possibility */
+ }
+ }
+
+ /* right edge */
+ for (i1 = start; i1 < b->height - 2; i1++)
+ {
+ /* Find empty gaps */
+ if (! (present[i1 + 1] & EDGE_RIGHT))
+ {
+ gint increment; /* fractional part */
+ gint denom; /* denominator of fraction */
+ gint step; /* integral step */
+ gint frac; /* fractional step */
+ gint reverse;
+
+ /* find bottom of gap */
+ i2 = i1 + 2;
+ while (i2 < b->height && ! (present[i2] & EDGE_RIGHT))
+ i2++;
+
+ if (i2 < b->height)
+ {
+ denom = i2 - i1;
+ x1 = b->data[i1].right;
+ x2 = b->data[i2].right;
+ step = (x2 - x1) / denom;
+ frac = x2 - x1 - step * denom;
+ if (frac < 0)
+ {
+ frac = -frac;
+ reverse = 1;
+ }
+ else
+ reverse = 0;
+
+ increment = 0;
+ for (i = i1 + 1; i<i2; i++)
+ {
+ x1 += step;
+ increment += frac;
+ if (increment >= denom)
+ {
+ increment -= denom;
+ x1 += reverse ? -1 : 1;
+ }
+ if (reverse && increment != 0)
+ b->data[i].right = x1 - 1;
+ else
+ b->data[i].right = x1;
+ }
+ }
+
+ i1 = i2 - 1; /* advance to next possibility */
+ }
+ }
+
+}
+
+static void
+gimp_blob_make_convex (GimpBlob *b,
+ EdgeType *present)
+{
+ gint x1, x2, y1, y2, i1, i2;
+ gint i;
+ gint start;
+
+ /* Walk through edges, deleting points that aren't on convex hull */
+
+ start = 0;
+ while (! present[start])
+ start++;
+
+ /* left edge */
+
+ i1 = start - 1;
+ i2 = start;
+ x1 = b->data[start].left - b->data[start].right;
+ y1 = 0;
+
+ for (i = start + 1; i < b->height; i++)
+ {
+ if (! (present[i] & EDGE_LEFT))
+ continue;
+
+ x2 = b->data[i].left - b->data[i2].left;
+ y2 = i - i2;
+
+ while (x2 * y1 - x1 * y2 < 0) /* clockwise rotation */
+ {
+ present[i2] &= ~EDGE_LEFT;
+ i2 = i1;
+ while ((--i1) >= start && (! (present[i1] & EDGE_LEFT)));
+
+ if (i1 < start)
+ {
+ x1 = b->data[start].left - b->data[start].right;
+ y1 = 0;
+ }
+ else
+ {
+ x1 = b->data[i2].left - b->data[i1].left;
+ y1 = i2 - i1;
+ }
+ x2 = b->data[i].left - b->data[i2].left;
+ y2 = i - i2;
+ }
+
+ x1 = x2;
+ y1 = y2;
+ i1 = i2;
+ i2 = i;
+ }
+
+ /* Right edge */
+
+ i1 = start -1;
+ i2 = start;
+ x1 = b->data[start].right - b->data[start].left;
+ y1 = 0;
+
+ for (i = start + 1; i < b->height; i++)
+ {
+ if (! (present[i] & EDGE_RIGHT))
+ continue;
+
+ x2 = b->data[i].right - b->data[i2].right;
+ y2 = i - i2;
+
+ while (x2 * y1 - x1 * y2 > 0) /* counter-clockwise rotation */
+ {
+ present[i2] &= ~EDGE_RIGHT;
+ i2 = i1;
+ while ((--i1) >= start && (! (present[i1] & EDGE_RIGHT)));
+
+ if (i1 < start)
+ {
+ x1 = b->data[start].right - b->data[start].left;
+ y1 = 0;
+ }
+ else
+ {
+ x1 = b->data[i2].right - b->data[i1].right;
+ y1 = i2 - i1;
+ }
+
+ x2 = b->data[i].right - b->data[i2].right;
+ y2 = i - i2;
+ }
+
+ x1 = x2;
+ y1 = y2;
+ i1 = i2;
+ i2 = i;
+ }
+
+ gimp_blob_fill (b, present);
+}
+
+
+#if 0
+
+static void
+gimp_blob_line_add_pixel (GimpBlob *b,
+ gint x,
+ gint y)
+{
+ if (b->data[y - b->y].left > b->data[y - b->y].right)
+ {
+ b->data[y - b->y].left = b->data[y - b->y].right = x;
+ }
+ else
+ {
+ b->data[y - b->y].left = MIN (b->data[y - b->y].left, x);
+ b->data[y - b->y].right = MAX (b->data[y - b->y].right, x);
+ }
+}
+
+static void
+gimp_blob_line (GimpBlob *b,
+ gint x0,
+ gint y0,
+ gint x1,
+ gint y1)
+{
+ gint dx, dy, d;
+ gint incrE, incrNE;
+ gint x, y;
+
+ gint xstep = 1;
+ gint ystep = 1;
+
+ dx = x1 - x0;
+ dy = y1 - y0;
+
+ if (dx < 0)
+ {
+ dx = -dx;
+ xstep = -1;
+ }
+
+ if (dy < 0)
+ {
+ dy = -dy;
+ ystep = -1;
+ }
+
+ /* for (y = y0; y != y1 + ystep ; y += ystep)
+ {
+ b->data[y-b->y].left = 0;
+ b->data[y-b->y].right = -1;
+ }*/
+
+ x = x0;
+ y = y0;
+
+ if (dy < dx)
+ {
+ d = 2 * dy - dx; /* initial value of d */
+ incrE = 2 * dy; /* increment used for move to E */
+ incrNE = 2 * (dy - dx); /* increment used for move to NE */
+
+ gimp_blob_line_add_pixel (b, x, y);
+
+ while (x != x1)
+ {
+ if (d <= 0)
+ {
+ d += incrE;
+ x += xstep;
+ }
+ else
+ {
+ d += incrNE;
+ x += xstep;
+ y += ystep;
+ }
+
+ gimp_blob_line_add_pixel (b, x, y);
+ }
+ }
+ else
+ {
+ d = 2 * dx - dy; /* initial value of d */
+ incrE = 2 * dx; /* increment used for move to E */
+ incrNE = 2 * (dx - dy); /* increment used for move to NE */
+
+ gimp_blob_line_add_pixel (b, x, y);
+
+ while (y != y1)
+ {
+ if (d <= 0)
+ {
+ d += incrE;
+ y += ystep;
+ }
+ else
+ {
+ d += incrNE;
+ x += xstep;
+ y += ystep;
+ }
+
+ gimp_blob_line_add_pixel (b, x, y);
+ }
+ }
+}
+
+#endif