diff options
Diffstat (limited to 'converter/other/pamtosvg/pxl-outline.c')
-rw-r--r-- | converter/other/pamtosvg/pxl-outline.c | 1370 |
1 files changed, 1370 insertions, 0 deletions
diff --git a/converter/other/pamtosvg/pxl-outline.c b/converter/other/pamtosvg/pxl-outline.c new file mode 100644 index 00000000..68cd0565 --- /dev/null +++ b/converter/other/pamtosvg/pxl-outline.c @@ -0,0 +1,1370 @@ +/* pxl-outline.c: find the outlines of a bitmap image; each outline is + made up of one or more pixels; and each pixel participates via one + or more edges. +*/ + +#include <assert.h> + +#include "mallocvar.h" + +#include "message.h" +#include "bitmap.h" +#include "bitmap.h" +#include "logreport.h" +#include "pxl-outline.h" + +/* We consider each pixel to consist of four edges, and we travel along + edges, instead of through pixel centers. This is necessary for those + unfortunate times when a single pixel is on both an inside and an + outside outline. + + The numbers chosen here are not arbitrary; the code that figures out + which edge to move to depends on particular values. See the + `TRY_PIXEL' macro in `edge.c'. To emphasize this, I've written in the + numbers we need for each edge value. */ + +typedef enum + { + TOP = 1, LEFT = 2, BOTTOM = 3, RIGHT = 0, NO_EDGE = 4 + } edge_type; + +/* This choice is also not arbitrary: starting at the top edge makes the + code find outside outlines before inside ones, which is certainly + what we want. */ +#define START_EDGE top + +typedef enum + { + NORTH = 0, NORTHWEST = 1, WEST = 2, SOUTHWEST = 3, SOUTH = 4, + SOUTHEAST = 5, EAST = 6, NORTHEAST = 7 + } direction_type; + +#define NUM_EDGES NO_EDGE + +#define COMPUTE_DELTA(axis, dir) \ + ((dir) % 2 != 0 \ + ? COMPUTE_##axis##_DELTA ((dir) - 1) \ + + COMPUTE_##axis##_DELTA (((dir) + 1) % 8) \ + : COMPUTE_##axis##_DELTA (dir) \ + ) + +#define COMPUTE_ROW_DELTA(dir) \ + ((dir) == NORTH ? -1 : (dir) == SOUTH ? +1 : 0) + +#define COMPUTE_COL_DELTA(dir) \ + ((dir) == WEST ? -1 : (dir) == EAST ? +1 : 0) + +static void append_pixel_outline (pixel_outline_list_type *, + pixel_outline_type); +static pixel_outline_type new_pixel_outline (void); +static void free_pixel_outline (pixel_outline_type *); +static void concat_pixel_outline (pixel_outline_type *, + const pixel_outline_type*); +static bool is_marked_edge (edge_type, unsigned short, unsigned short, bitmap_type); + +static void +mark_edge (edge_type e, unsigned short, unsigned short, bitmap_type *); + +static bool is_marked_dir(unsigned short, unsigned short, direction_type, bitmap_type); +static bool is_other_dir_marked(unsigned short, unsigned short, direction_type, bitmap_type); +static void mark_dir(unsigned short, unsigned short, direction_type, bitmap_type *); + +static unsigned +num_neighbors(unsigned short, unsigned short, bitmap_type); + +#define CHECK_FATAL() if (at_exception_got_fatal(exp)) goto cleanup; +#define RETURN_IF_FATAL() if (at_exception_got_fatal(exp)) return; + + + + +static pixel +getBitmapColor(bitmap_type const bitmap, + unsigned int const row, + unsigned int const col) { + + pixel pix; + unsigned char *p = BITMAP_PIXEL (bitmap, row, col); + + if (bitmap.np >= 3) + PPM_ASSIGN(pix, p[0], p[1], p[2]); + else + PPM_ASSIGN(pix, p[0], p[0], p[0]); + + return pix; +} + + + + +static void +append_outline_pixel(pixel_outline_type * const pixelOutlineP, + pm_pixelcoord const coord) { +/*---------------------------------------------------------------------------- + Add a point to the pixel list. +-----------------------------------------------------------------------------*/ + + O_LENGTH(*pixelOutlineP)++; + REALLOCARRAY_NOFAIL(pixelOutlineP->data, O_LENGTH(*pixelOutlineP)); + + O_COORDINATE(*pixelOutlineP, O_LENGTH(*pixelOutlineP) - 1) = coord; +} + + + +/* We check to see if the edge of the pixel at position ROW and COL + is an outline edge */ + +static bool +is_outline_edge (edge_type edge, bitmap_type bitmap, + unsigned short row, unsigned short col, pixel color, + at_exception_type * exp) +{ + /* If this pixel isn't of the same color, it's not part of the outline. */ + if (!PPM_EQUAL (getBitmapColor (bitmap, row, col), color)) + return false; + + switch (edge) + { + case LEFT: + return (bool) + (col == 0 || + !PPM_EQUAL (getBitmapColor (bitmap, row, col - 1), color)); + + case TOP: + return (bool) + (row == 0 || + !PPM_EQUAL (getBitmapColor (bitmap, row - 1, col), color)); + + case RIGHT: + return (bool) + (col == bitmap.width - 1 + || !PPM_EQUAL(getBitmapColor(bitmap, row, col + 1), color)); + + case BOTTOM: + return (bool) + (row == bitmap.height - 1 + || !PPM_EQUAL(getBitmapColor (bitmap, row + 1, col), color)); + + case NO_EDGE: + default: + LOG1 ("is_outline_edge: Bad edge value(%d)", edge); + at_exception_fatal(exp, "is_outline_edge: Bad edge value"); + } + + return false; /* NOT REACHED */ +} + + +/* Is this really an edge and is it still unmarked? */ + +static bool +is_unmarked_outline_edge(unsigned short row, + unsigned short col, + edge_type edge, + bitmap_type bitmap, + bitmap_type marked, + pixel color, + at_exception_type * exp) +{ + return + (bool)(!is_marked_edge (edge, row, col, marked) + && is_outline_edge (edge, bitmap, row, col, color, exp)); +} + + +static bool +is_valid_dir(unsigned int const row, + unsigned int const col, + direction_type const dir, + bitmap_type const bitmap, + bitmap_type const marked) { + + return(!is_marked_dir(row, col, dir, marked) + && COMPUTE_DELTA(ROW, dir)+row > 0 + && COMPUTE_DELTA(COL, dir)+col > 0 + && BITMAP_VALID_PIXEL(bitmap, + COMPUTE_DELTA(ROW, dir) + row, + COMPUTE_DELTA(COL, dir) + col) + && PPM_EQUAL(getBitmapColor(bitmap, + COMPUTE_DELTA(ROW, dir) + row, + COMPUTE_DELTA(COL, dir) + col), + getBitmapColor(bitmap, row, col))); +} + + + +static bool +next_unmarked_pixel(unsigned int * const row, + unsigned int * const col, + direction_type * const dir, + bitmap_type const bitmap, + bitmap_type * const marked) { + + unsigned int const orig_row = *row; + unsigned int const orig_col = *col; + direction_type const orig_dir = *dir; + + direction_type test_dir; + pixel color; + + test_dir = *dir; /* initial value */ + color = getBitmapColor(bitmap, *row, *col); + + do { + if (is_valid_dir(orig_row, orig_col, test_dir, bitmap, *marked)) { + *row = orig_row + COMPUTE_DELTA(ROW, test_dir); + *col = orig_col + COMPUTE_DELTA(COL, test_dir); + *dir = test_dir; + break; + } + + if (orig_dir == test_dir) + test_dir = (orig_dir + 2) % 8; + else if ((orig_dir + 2) % 8 == test_dir) + test_dir = (orig_dir + 6) % 8; + else if ((orig_dir + 6) % 8 == test_dir) + test_dir = (orig_dir + 1) % 8; + else if ((orig_dir + 1) % 8 == test_dir) + test_dir = (orig_dir + 7) % 8; + else if ((orig_dir + 7) % 8 == test_dir) + test_dir = (orig_dir + 3) % 8; + else if ((orig_dir + 3) % 8 == test_dir) + test_dir = (orig_dir + 5) % 8; + else if ((orig_dir + 5) % 8 == test_dir) + break; + } while (1); + + if ((*row != orig_row || *col != orig_col) && + (!(is_other_dir_marked(orig_row, orig_col, test_dir, *marked) && + is_other_dir_marked(orig_row + COMPUTE_DELTA(ROW, test_dir), + orig_col + COMPUTE_DELTA(COL, test_dir), + test_dir,*marked)))) + return true; + else + return false; +} + + + +static pixel_outline_type +find_one_centerline(bitmap_type const bitmap, + direction_type const original_dir, + unsigned int const original_row, + unsigned int const original_col, + bitmap_type * const marked) { + + direction_type search_dir; + unsigned int row, col; + pixel_outline_type outline; + + outline = new_pixel_outline(); + outline.open = false; + outline.color = getBitmapColor(bitmap, row, col); + + /* Add the starting pixel to the output list, changing from bitmap + to Cartesian coordinates and specifying the left edge so that + the coordinates won't be adjusted. + */ + { + pm_pixelcoord pos; + pos.col = col; pos.row = bitmap.height - row - 1; + LOG2(" (%d,%d)", pos.col, pos.row); + append_outline_pixel(&outline, pos); + } + search_dir = original_dir; /* initial value */ + row = original_row; /* initial value */ + col = original_col; /* initial values */ + + for ( ; ; ) { + unsigned int const prev_row = row; + unsigned int const prev_col = col; + + /* If there is no adjacent, unmarked pixel, we can't proceed + any further, so return an open outline. + */ + if (!next_unmarked_pixel(&row, &col, &search_dir, bitmap, marked)) { + outline.open = true; + break; + } + + /* If we've moved to a new pixel, mark all edges of the previous + pixel so that it won't be revisited. + */ + if (!(prev_row == original_row && prev_col == original_col)) + mark_dir(prev_row, prev_col, search_dir, marked); + mark_dir(row, col, (search_dir + 4) % 8, marked); + + /* If we've returned to the starting pixel, we're done. */ + if (row == original_row && col == original_col) + break; + + + { + /* Add the new pixel to the output list. */ + pm_pixelcoord pos; + pos.col = col; pos.row = bitmap.height - row - 1; + LOG2(" (%d,%d)", pos.col, pos.row); + append_outline_pixel(&outline, pos); + } + } + mark_dir(original_row, original_col, original_dir, marked); + + return outline; +} + + + +static bool +wrongDirection(unsigned int const row, + unsigned int const col, + direction_type const dir, + bitmap_type const bitmap, + bitmap_type const marked) { + + return (!is_valid_dir(row, col, dir, bitmap, marked) + || (!is_valid_dir(COMPUTE_DELTA(ROW, dir) + row, + COMPUTE_DELTA(COL, dir) + col, + dir, bitmap, marked) + && num_neighbors(row, col, bitmap) > 2) + || num_neighbors(row, col, bitmap) > 4 + || num_neighbors(COMPUTE_DELTA(ROW, dir) + row, + COMPUTE_DELTA(COL, dir) + col, bitmap) > 4 + || (is_other_dir_marked(row, col, dir, marked) + && is_other_dir_marked(row + COMPUTE_DELTA(ROW, dir), + col + COMPUTE_DELTA(COL, dir), + dir, marked))); +} + + + +pixel_outline_list_type +find_centerline_pixels(bitmap_type const bitmap, + pixel const bg_color, + at_progress_func notify_progress, + void * const progress_data, + at_testcancel_func test_cancel, + void * const testcancel_data, + at_exception_type * const exp) { + + pixel_outline_list_type outline_list; + signed short row; + bitmap_type marked = new_bitmap(bitmap.width, bitmap.height); + unsigned int const max_progress = bitmap.height * bitmap.width; + + O_LIST_LENGTH(outline_list) = 0; + outline_list.data = NULL; + + for (row = 0; row < bitmap.height; ++row) { + signed short col; + for (col = 0; col < bitmap.width; ) { + bool const clockwise = false; + + direction_type dir; + pixel_outline_type outline; + + if (notify_progress) + notify_progress((float)(row * bitmap.width + col) / + ((float) max_progress * (float)3.0), + progress_data); + + if (PPM_EQUAL(getBitmapColor(bitmap, row, col), bg_color)) { + ++col; + continue; + } + + dir = EAST; + + if (wrongDirection(row, col, dir, bitmap, marked)) { + dir = SOUTHEAST; + if (wrongDirection(row, col, dir, bitmap, marked)) { + dir = SOUTH; + if (wrongDirection(row, col, dir, bitmap, marked)) { + dir = SOUTHWEST; + if (wrongDirection(row, col, dir, bitmap, marked)) { + ++col; + continue; + } + } + } + } + + LOG2("#%u: (%sclockwise) ", O_LIST_LENGTH(outline_list), + clockwise ? "" : "counter"); + + outline = find_one_centerline(bitmap, dir, row, col, &marked); + + /* If the outline is open (i.e., we didn't return to the + starting pixel), search from the starting pixel in the + opposite direction and concatenate the two outlines. + */ + + if (outline.open) { + pixel_outline_type partial_outline; + bool okay = false; + + if (dir == EAST) { + dir = SOUTH; + okay = is_valid_dir(row, col, dir, bitmap, marked); + if (!okay) { + dir = SOUTHWEST; + okay = is_valid_dir(row, col, dir, bitmap, marked); + if (!okay) { + dir = SOUTHEAST; + okay = is_valid_dir(row, col, dir, bitmap, marked); + } + } + } else if (dir == SOUTHEAST) { + dir = SOUTHWEST; + okay = is_valid_dir(row, col, dir, bitmap, marked); + if (!okay) { + dir = EAST; + okay=is_valid_dir(row, col, dir, bitmap, marked); + if (!okay) { + dir = SOUTH; + okay = is_valid_dir(row, col, dir, bitmap, marked); + } + } + } else if (dir == SOUTH) { + dir = EAST; + okay = is_valid_dir(row, col, dir, bitmap, marked); + if (!okay) { + dir = SOUTHEAST; + okay = is_valid_dir(row, col, dir, bitmap, marked); + if (!okay) { + dir = SOUTHWEST; + okay = is_valid_dir(row, col, dir, bitmap, marked); + } + } + } else if (dir == SOUTHWEST) { + dir = SOUTHEAST; + okay = is_valid_dir(row, col, dir, bitmap, marked); + if (!okay) { + dir = EAST; + okay = is_valid_dir(row, col, dir, bitmap, marked); + if (!okay) { + dir = SOUTH; + okay = is_valid_dir(row, col, dir, bitmap, marked); + } + } + } + if (okay) { + partial_outline = + find_one_centerline(bitmap, dir, row, col, &marked); + concat_pixel_outline(&outline, &partial_outline); + if (partial_outline.data) + free(partial_outline.data); + } else + ++col; + } + + /* Outside outlines will start at a top edge, and move + counterclockwise, and inside outlines will start at a + bottom edge, and move clockwise. This happens because of + the order in which we look at the edges. + */ + O_CLOCKWISE(outline) = clockwise; + if (O_LENGTH(outline) > 1) + append_pixel_outline(&outline_list, outline); + LOG1("(%s)", (outline.open ? " open" : " closed")); + LOG1(" [%u].\n", O_LENGTH(outline)); + if (O_LENGTH(outline) == 1) + free_pixel_outline(&outline); + } + } + if (test_cancel && test_cancel(testcancel_data)) { + if (O_LIST_LENGTH (outline_list) != 0) + free_pixel_outline_list (&outline_list); + } + free_bitmap(&marked); + flush_log_output(); + return outline_list; +} + + + +/* Add an outline to an outline list. */ + +static void +append_pixel_outline (pixel_outline_list_type *outline_list, + pixel_outline_type outline) +{ + O_LIST_LENGTH (*outline_list)++; + REALLOCARRAY_NOFAIL(outline_list->data, outline_list->length); + O_LIST_OUTLINE (*outline_list, O_LIST_LENGTH (*outline_list) - 1) = outline; +} + + +/* Free the list of outline lists. */ + +void +free_pixel_outline_list (pixel_outline_list_type *outline_list) +{ + unsigned this_outline; + + for (this_outline = 0; this_outline < outline_list->length; this_outline++) + { + pixel_outline_type o = outline_list->data[this_outline]; + free_pixel_outline (&o); + } + outline_list->length = 0; + + if (outline_list->data != NULL) + { + free (outline_list->data); + outline_list->data = NULL; + } + + flush_log_output (); +} + + +/* Return an empty list of pixels. */ + + +static pixel_outline_type +new_pixel_outline (void) +{ + pixel_outline_type pixel_outline; + + O_LENGTH (pixel_outline) = 0; + pixel_outline.data = NULL; + pixel_outline.open = false; + + return pixel_outline; +} + +static void +free_pixel_outline (pixel_outline_type * outline) +{ + if (outline->data) + { + free (outline->data) ; + outline->data = NULL; + outline->length = 0; + } +} + +/* Concatenate two pixel lists. The two lists are assumed to have the + same starting pixel and to proceed in opposite directions therefrom. */ + +static void +concat_pixel_outline(pixel_outline_type *o1, const pixel_outline_type *o2) +{ + int src, dst; + unsigned o1_length, o2_length; + if (!o1 || !o2 || O_LENGTH(*o2) <= 1) return; + + o1_length = O_LENGTH(*o1); + o2_length = O_LENGTH(*o2); + O_LENGTH(*o1) += o2_length - 1; + /* Resize o1 to the sum of the lengths of o1 and o2 minus one (because + the two lists are assumed to share the same starting pixel). */ + REALLOCARRAY_NOFAIL(o1->data, O_LENGTH(*o1)); + /* Shift the contents of o1 to the end of the new array to make room + to prepend o2. */ + for (src = o1_length - 1, dst = O_LENGTH(*o1) - 1; src >= 0; src--, dst--) + O_COORDINATE(*o1, dst) = O_COORDINATE(*o1, src); + /* Prepend the contents of o2 (in reverse order) to o1. */ + for (src = o2_length - 1, dst = 0; src > 0; src--, dst++) + O_COORDINATE(*o1, dst) = O_COORDINATE(*o2, src); +} + + +/* If EDGE is not already marked, we mark it; otherwise, it's a fatal error. + The position ROW and COL should be inside the bitmap MARKED. EDGE can be + NO_EDGE. */ + +static void +mark_edge (edge_type edge, unsigned short row, + unsigned short col, bitmap_type *marked) +{ + *BITMAP_PIXEL (*marked, row, col) |= 1 << edge; +} + + +/* Mark the direction of the pixel ROW/COL in MARKED. */ + +static void +mark_dir(unsigned short row, unsigned short col, direction_type dir, bitmap_type *marked) +{ + *BITMAP_PIXEL(*marked, row, col) |= 1 << dir; +} + + +/* Test if the direction of pixel at ROW/COL in MARKED is marked. */ + +static bool +is_marked_dir(unsigned short row, unsigned short col, direction_type dir, bitmap_type marked) +{ + return (bool)((*BITMAP_PIXEL(marked, row, col) & 1 << dir) != 0); +} + + +static bool +is_other_dir_marked(unsigned short row, unsigned short col, direction_type dir, bitmap_type marked) +{ + return (bool)((*BITMAP_PIXEL(marked, row, col) & (255 - (1 << dir) - (1 << ((dir + 4) % 8))) ) != 0); +} + + +/* Return the number of pixels adjacent to pixel ROW/COL that are black. */ + +static unsigned +num_neighbors(unsigned short row, unsigned short col, bitmap_type bitmap) +{ + unsigned dir, count = 0; + pixel color = getBitmapColor(bitmap, row, col); + for (dir = NORTH; dir <= NORTHEAST; dir++) + { + int delta_r = COMPUTE_DELTA(ROW, dir); + int delta_c = COMPUTE_DELTA(COL, dir); + unsigned int test_row = row + delta_r; + unsigned int test_col = col + delta_c; + if (BITMAP_VALID_PIXEL(bitmap, test_row, test_col) + && PPM_EQUAL(getBitmapColor(bitmap, test_row, test_col), color)) + ++count; + } + return count; +} + + +/* Test if the edge EDGE at ROW/COL in MARKED is marked. */ + +static bool +is_marked_edge (edge_type edge, unsigned short row, unsigned short col, bitmap_type marked) +{ + return + (bool)(edge == NO_EDGE ? false : (*BITMAP_PIXEL (marked, row, col) & (1 << edge)) != 0); +} + + + +static void +nextClockwisePointTop(bitmap_type const bitmap, + edge_type * const edge, + unsigned int * const row, + unsigned int * const col, + pixel const color, + bitmap_type const marked, + at_exception_type * const exp, + pm_pixelcoord * const posP) { + + if ((*col >= 1 && !is_marked_edge(TOP, *row, *col-1, marked) && + is_outline_edge(TOP, bitmap, *row, *col-1, color, exp))) { + + /* WEST */ + + *edge = TOP; + --*col; + posP->col = *col; + posP->row = bitmap.height - *row; + return; + } + + RETURN_IF_FATAL(); + + if ((*col >= 1 && *row >= 1 && + !is_marked_edge(RIGHT, *row-1, *col-1, marked) && + is_outline_edge(RIGHT, bitmap, *row-1, *col-1, + color, exp)) && + !(is_marked_edge(LEFT, *row-1, *col, marked) && + is_marked_edge(TOP, *row,*col-1, marked)) && + !(is_marked_edge(BOTTOM, *row-1, *col, marked) && + is_marked_edge(RIGHT, *row, *col-1, marked))) { + + /* NORTHWEST */ + + *edge = RIGHT; + --*col; + --*row; + posP->col = *col + 1; + posP->row = bitmap.height - *row; + return; + } + + RETURN_IF_FATAL(); + + if ((!is_marked_edge(LEFT, *row, *col, marked) + && is_outline_edge(LEFT, bitmap, *row, *col, color, exp))) { + + *edge = LEFT; + posP->col = *col; + posP->row = bitmap.height - *row - 1; + return; + } + + RETURN_IF_FATAL(); + + *edge = NO_EDGE; +} + + + +static void +nextClockwisePointRight(bitmap_type const bitmap, + edge_type * const edge, + unsigned int * const row, + unsigned int * const col, + pixel const color, + bitmap_type const marked, + at_exception_type * const exp, + pm_pixelcoord * const posP) { + + if ((*row >= 1 && + !is_marked_edge(RIGHT, *row-1, *col, marked) && + is_outline_edge(RIGHT, bitmap, *row-1, *col, color, exp))) { + + /* NORTH */ + + *edge = RIGHT; + --*row; + posP->col = *col+1; + posP->row = bitmap.height - *row; + return; + } + + RETURN_IF_FATAL(); + + if ((*col+1 < marked.width && *row >= 1 && + !is_marked_edge(BOTTOM, *row-1, *col+1, marked) && + is_outline_edge(BOTTOM, bitmap, *row-1, *col+1, + color, exp)) && + !(is_marked_edge(LEFT, *row, *col+1, marked) && + is_marked_edge(BOTTOM, *row-1, *col, marked)) && + !(is_marked_edge(TOP, *row, *col+1, marked) && + is_marked_edge(RIGHT, *row-1, *col, marked))) { + + /* NORTHEAST */ + *edge = BOTTOM; + ++*col; + --*row; + posP->col = *col + 1; + posP->row = bitmap.height - *row - 1; + return; + } + + RETURN_IF_FATAL(); + + if ((!is_marked_edge(TOP, *row, *col, marked) && + is_outline_edge(TOP, bitmap, *row, *col, color, exp))) { + + *edge = TOP; + posP->col = *col; + posP->row = bitmap.height - *row; + return; + } + + RETURN_IF_FATAL(); + + *edge = NO_EDGE; +} + + + +static void +nextClockwisePointBottom(bitmap_type const bitmap, + edge_type * const edge, + unsigned int * const row, + unsigned int * const col, + pixel const color, + bitmap_type const marked, + at_exception_type * const exp, + pm_pixelcoord * const posP) { + + if ((*col+1 < marked.width && + !is_marked_edge(BOTTOM, *row, *col+1, marked) && + is_outline_edge(BOTTOM, bitmap, *row, *col+1, color, exp))) { + + /* EAST */ + + *edge = BOTTOM; + ++*col; + posP->col = *col+1; + posP->row = bitmap.height - *row - 1; + return; + } + + RETURN_IF_FATAL(); + + if ((*col + 1 < marked.width && *row+1 < marked.height && + !is_marked_edge(LEFT, *row+1,*col+1, marked) && + is_outline_edge(LEFT, bitmap, *row+1, *col+1, color, exp)) && + !(is_marked_edge(TOP, *row+1, *col, marked) && + is_marked_edge(LEFT, *row, *col+1, marked)) && + !(is_marked_edge(RIGHT, *row+1, *col, marked) && + is_marked_edge(BOTTOM, *row, *col+1, marked))) { + + /* SOUTHEAST */ + + *edge = LEFT; + ++*col; + ++*row; + posP->col = *col; + posP->row = bitmap.height - *row - 1; + return; + } + + RETURN_IF_FATAL(); + + if ((!is_marked_edge(RIGHT, *row, *col, marked) && + is_outline_edge(RIGHT, bitmap, *row, *col, color, exp))) { + + *edge = RIGHT; + posP->col = *col+1; + posP->row = bitmap.height - *row; + return; + } + + RETURN_IF_FATAL(); + + *edge = NO_EDGE; +} + + + +static void +nextClockwisePointLeft(bitmap_type const bitmap, + edge_type * const edge, + unsigned int * const row, + unsigned int * const col, + pixel const color, + bitmap_type const marked, + at_exception_type * const exp, + pm_pixelcoord * const posP) { + + if ((*row+1 < marked.height && + !is_marked_edge(LEFT, *row+1, *col, marked) && + is_outline_edge(LEFT, bitmap, *row+1, *col, color, exp))) { + + /* SOUTH */ + + *edge = LEFT; + ++*row; + posP->col = *col; + posP->row = bitmap.height - *row - 1; + return; + } + + RETURN_IF_FATAL(); + + if ((*col >= 1 && *row+1 < marked.height && + !is_marked_edge(TOP, *row+1, *col-1, marked) && + is_outline_edge(TOP, bitmap, *row+1, *col-1, color, exp)) && + !(is_marked_edge(RIGHT, *row, *col-1, marked) && + is_marked_edge(TOP, *row+1, *col, marked)) && + !(is_marked_edge(BOTTOM, *row, *col-1, marked) && + is_marked_edge(LEFT, *row+1, *col, marked))) { + + /* SOUTHWEST */ + + *edge = TOP; + --*col; + ++*row; + posP->col = *col; + posP->row = bitmap.height - *row; + return; + } + + RETURN_IF_FATAL(); + + if ((!is_marked_edge(BOTTOM, *row, *col, marked) && + is_outline_edge(BOTTOM, bitmap, *row, *col, color, exp))) { + *edge = BOTTOM; + posP->col = *col+1; + posP->row = bitmap.height - *row - 1; + return; + } + RETURN_IF_FATAL(); + + *edge = NO_EDGE; +} + + + +static void +nextClockwisePoint(bitmap_type const bitmap, + edge_type * const edge, + unsigned int * const row, + unsigned int * const col, + pixel const color, + bitmap_type const marked, + at_exception_type * const exp, + pm_pixelcoord * const posP) { + + switch (*edge) { + case TOP: + nextClockwisePointTop( bitmap, edge, row, col, color, + marked, exp, posP); + break; + case RIGHT: + nextClockwisePointRight(bitmap, edge, row, col, color, + marked, exp, posP); + break; + case BOTTOM: + nextClockwisePointBottom(bitmap, edge, row, col, color, + marked, exp, posP); + break; + case LEFT: + nextClockwisePointLeft( bitmap, edge, row, col, color, + marked, exp, posP); + break; + case NO_EDGE: + break; + default: + *edge = NO_EDGE; + break; + } +} + + + +static void +nextCcwPointTop(bitmap_type const bitmap, + edge_type * const edge, + unsigned int * const row, + unsigned int * const col, + pixel const color, + bitmap_type const marked, + at_exception_type * const exp, + pm_pixelcoord * const posP) { + + if ((!is_marked_edge(LEFT, *row, *col, marked) && + is_outline_edge(LEFT,bitmap,*row,*col, color, exp))) { + + *edge = LEFT; + posP->col = *col; + posP->row = bitmap.height - *row - 1; + return; + } + + RETURN_IF_FATAL(); + + if ((*col >= 1 && + !is_marked_edge(TOP, *row, *col-1, marked) && + is_outline_edge(TOP, bitmap, *row, *col-1, color, exp))) { + + /* WEST */ + + *edge = TOP; + --*col; + posP->col = *col; + posP->row = bitmap.height - *row; + return; + } + + RETURN_IF_FATAL(); + + if ((*col >= 1 && *row >= 1 && + !is_marked_edge(RIGHT, *row-1, *col-1, marked) && + is_outline_edge(RIGHT, bitmap, *row-1, *col-1, color, exp))) { + + /* NORTHWEST */ + + *edge = RIGHT; + --*col; + --*row; + posP->col = *col + 1; + posP->row = bitmap.height - *row; + return; + } + + RETURN_IF_FATAL(); + + *edge = NO_EDGE; +} + + + +static void +nextCcwPointRight(bitmap_type const bitmap, + edge_type * const edge, + unsigned int * const row, + unsigned int * const col, + pixel const color, + bitmap_type const marked, + at_exception_type * const exp, + pm_pixelcoord * const posP) { + + if ((!is_marked_edge(TOP, *row, *col, marked) && + is_outline_edge(TOP, bitmap, *row, *col, color, exp))) { + + *edge = TOP; + posP->col = *col; + posP->row = bitmap.height - *row; + return; + } + + RETURN_IF_FATAL(); + + if ((*row >= 1 && + !is_marked_edge(RIGHT, *row-1, *col, marked) && + is_outline_edge(RIGHT, bitmap, *row-1, *col, color, exp))) { + + /* NORTH */ + + *edge = RIGHT; + --*row; + posP->col = *col + 1; + posP->row = bitmap.height - *row; + return; + } + + RETURN_IF_FATAL(); + + if ((*col + 1 < marked.width && *row >= 1 && + !is_marked_edge(BOTTOM, *row-1, *col+1, marked) && + is_outline_edge(BOTTOM, bitmap, *row-1, *col+1, color, exp))) { + + /* NORTHEAST */ + + *edge = BOTTOM; + ++*col; + ++*row; + posP->col = *col + 1; + posP->row = bitmap.height - *row - 1; + return; + } + + RETURN_IF_FATAL(); + + *edge = NO_EDGE; +} + + + +static void +nextCcwPointBottom(bitmap_type const bitmap, + edge_type * const edge, + unsigned int * const row, + unsigned int * const col, + pixel const color, + bitmap_type const marked, + at_exception_type * const exp, + pm_pixelcoord * const posP) { + + if ((!is_marked_edge(RIGHT, *row, *col, marked) && + is_outline_edge(RIGHT, bitmap, *row, *col, color, exp))) { + + *edge = RIGHT; + posP->col = *col + 1; + posP->row = bitmap.height - *row; + return; + } + + RETURN_IF_FATAL(); + + if ((*col + 1 < marked.width && + !is_marked_edge(BOTTOM, *row, *col+1, marked) && + is_outline_edge(BOTTOM, bitmap, *row, *col+1, color, exp))) { + + /* EAST */ + + *edge = BOTTOM; + ++*col; + posP->col = *col + 1; + posP->row = bitmap.height - *row - 1; + return; + } + + RETURN_IF_FATAL(); + + if ((*col + 1 < marked.width && *row + 1 < marked.height && + !is_marked_edge(LEFT, *row+1, *col+1, marked) && + is_outline_edge(LEFT, bitmap, *row+1, *col+1, color, exp))) { + + /* SOUTHEAST */ + + *edge = LEFT; + ++*col; + ++*row; + posP->col = *col; + posP->row = bitmap.height - *row - 1; + return; + } + + RETURN_IF_FATAL(); + + *edge = NO_EDGE; +} + + + +static void +nextCcwPointLeft(bitmap_type const bitmap, + edge_type * const edge, + unsigned int * const row, + unsigned int * const col, + pixel const color, + bitmap_type const marked, + at_exception_type * const exp, + pm_pixelcoord * const posP) { + + + if ((!is_marked_edge(BOTTOM, *row, *col, marked) && + is_outline_edge(BOTTOM, bitmap, *row, *col, color, exp))) { + + *edge = BOTTOM; + posP->col = *col + 1; + posP->row = bitmap.height - *row - 1; + return; + } + + RETURN_IF_FATAL(); + + if ((*row + 1 < marked.height && + !is_marked_edge(LEFT, *row+1, *col, marked) && + is_outline_edge(LEFT, bitmap, *row+1, *col, color, exp))) { + + /* SOUTH */ + + *edge = LEFT; + ++*row; + posP->col = *col; + posP->row = bitmap.height - *row - 1; + return; + } + + RETURN_IF_FATAL(); + + if ((*col >= 1 && *row + 1 < marked.height && + !is_marked_edge(TOP, *row+1, *col-1, marked) && + is_outline_edge(TOP, bitmap, *row+1, *col-1, color, exp))) { + + /* SOUTHWEST */ + + *edge = TOP; + --*col; + ++*row; + posP->col = *col; + posP->row = bitmap.height - *row; + return; + } + + RETURN_IF_FATAL(); + + *edge = NO_EDGE; +} + +static void +nextCounterClockwisePoint(bitmap_type const bitmap, + edge_type * const edge, + unsigned int * const row, + unsigned int * const col, + pixel const color, + bitmap_type const marked, + at_exception_type * const exp, + pm_pixelcoord * const posP) { + + switch (*edge) { + case TOP: + nextCcwPointTop( bitmap, edge, row, col, color, marked, exp, posP); + break; + case RIGHT: + nextCcwPointRight( bitmap, edge, row, col, color, marked, exp, posP); + break; + case BOTTOM: + nextCcwPointBottom(bitmap, edge, row, col, color, marked, exp, posP); + break; + case LEFT: + nextCcwPointLeft( bitmap, edge, row, col, color, marked, exp, posP); + break; + case NO_EDGE: + break; + default: + *edge = NO_EDGE; + break; + } +} + + + +static void +nextPoint(bitmap_type const bitmap, + edge_type * const edge, + unsigned int * const row, + unsigned int * const col, + pm_pixelcoord * const nextPointP, + pixel const color, + bool const clockwise, + bitmap_type const marked, + at_exception_type * const exp) { + + if (!clockwise) + nextClockwisePoint( bitmap, edge, row, col, color, + marked, exp, nextPointP); + else + nextCounterClockwisePoint(bitmap, edge, row, col, color, + marked, exp, nextPointP); +} + + + +static pixel_outline_type +find_one_outline(bitmap_type const bitmap, + edge_type const originalEdge, + unsigned int const originalRow, + unsigned int const originalCol, + bitmap_type * const marked, + bool const clockwise, + bool const ignore, + at_exception_type * const exp) { +/*---------------------------------------------------------------------------- + Calculate one single outline. We pass the position of the + starting pixel and the starting edge. We mark all edges we track along + and append the outline pixels to the coordinate list. +-----------------------------------------------------------------------------*/ + unsigned int row; + unsigned int col; + edge_type edge; + + pixel_outline_type outline; + pm_pixelcoord pos; + + outline = new_pixel_outline(); + outline.color = getBitmapColor(bitmap, originalRow, originalCol); + + row = originalRow; /* initial value */ + col = originalCol; /* initial value */ + edge = originalEdge; /* initial value */ + + /* Set initial position */ + pos.col = col + ((edge == RIGHT) || (edge == BOTTOM) ? 1 : 0); + pos.row = bitmap.height - row - 1 + + (edge == TOP || edge == RIGHT ? 1 : 0); + + do { + /* Put this edge into the output list */ + if (!ignore) { + LOG2(" (%d,%d)", pos.col, pos.row); + append_outline_pixel(&outline, pos); + } + + mark_edge(edge, row, col, marked); + nextPoint(bitmap, &edge, &row, &col, &pos, outline.color, clockwise, + *marked, exp); + /* edge, row, and col are both input and output in the above */ + } while (edge != NO_EDGE && !at_exception_got_fatal(exp)); + + if (ignore || at_exception_got_fatal(exp)) + free_pixel_outline(&outline); + + return outline; +} + + + +pixel_outline_list_type +find_outline_pixels(bitmap_type const bitmap, + bool const bg_spec, + pixel const bg_color, + at_progress_func notify_progress, + void * const progress_data, + at_testcancel_func test_cancel, + void * const testcancel_data, + at_exception_type * const exp) { +/*---------------------------------------------------------------------------- + Return a list of outlines in the image whose raster is 'bitmap'. + + The background color of the image is 'bg_color' if 'bg_spec' is true; + otherwise, there is no background color. +-----------------------------------------------------------------------------*/ + /* We go through a bitmap TOP to BOTTOM, LEFT to RIGHT, looking for + each pixel with an unmarked edge that we consider a starting point + of an outline. When we find one, we trace the outline and add it + to the list, marking the edges in it as we go. + */ + unsigned int const max_progress = bitmap.height * bitmap.width; + + pixel_outline_list_type outline_list; + unsigned int row; + bitmap_type marked; + + marked = new_bitmap (bitmap.width, bitmap.height); + + O_LIST_LENGTH(outline_list) = 0; + outline_list.data = NULL; + + for (row = 0; row < bitmap.height; ++row) { + unsigned int col; + for (col = 0; col < bitmap.width; ++col) { + pixel const color = getBitmapColor(bitmap, row, col); + bool const is_background = + bg_spec && PPM_EQUAL(color, bg_color); + + if (notify_progress) + notify_progress((float)(row * bitmap.width + col) / + ((float) max_progress * (float)3.0), + progress_data); + + /* A valid edge can be TOP for an outside outline. + Outside outlines are traced counterclockwise. + */ + + if (!is_background && + is_unmarked_outline_edge(row, col, TOP, + bitmap, marked, color, exp)) { + pixel_outline_type outline; + + CHECK_FATAL(); /* FREE(DONE) outline_list */ + + LOG1("#%u: (counterclockwise)", O_LIST_LENGTH(outline_list)); + + outline = find_one_outline(bitmap, TOP, row, col, &marked, + false, false, exp); + CHECK_FATAL(); /* FREE(DONE) outline_list */ + + O_CLOCKWISE(outline) = false; + append_pixel_outline(&outline_list, outline); + + LOG1(" [%u].\n", O_LENGTH (outline)); + } else + CHECK_FATAL (); /* FREE(DONE) outline_list */ + + /* A valid edge can be BOTTOM for an inside outline. + Inside outlines are traced clockwise. + */ + if (row > 0) { + pixel const colorAbove = getBitmapColor(bitmap, row-1, col); + if (!(bg_spec && PPM_EQUAL(colorAbove, bg_color)) && + is_unmarked_outline_edge(row-1, col, BOTTOM, + bitmap, marked, colorAbove,exp)) { + CHECK_FATAL(); /* FREE(DONE) outline_list */ + + /* This lines are for debugging only:*/ + if (is_background) { + pixel_outline_type outline; + + LOG1("#%u: (clockwise)", O_LIST_LENGTH(outline_list)); + + outline = find_one_outline(bitmap, BOTTOM, row-1, col, + &marked, true, false, exp); + CHECK_FATAL(); /* FREE(DONE) outline_list */ + + O_CLOCKWISE(outline) = true; + append_pixel_outline(&outline_list, outline); + + LOG1(" [%u].\n", O_LENGTH(outline)); + } else { + find_one_outline(bitmap, BOTTOM, row-1, col, + &marked, true, true, exp); + CHECK_FATAL(); /* FREE(DONE) outline_list */ + } + } else + CHECK_FATAL(); /* FREE(DONE) outline_list */ + } + if (test_cancel && test_cancel(testcancel_data)) { + free_pixel_outline_list(&outline_list); + goto cleanup; + } + } + } + cleanup: + free_bitmap(&marked); + flush_log_output(); + if (at_exception_got_fatal(exp)) + free_pixel_outline_list(&outline_list); + + return outline_list; +} + |