From 772cd3e73f810c142ff3f7b45c3bad73146098e0 Mon Sep 17 00:00:00 2001 From: YAMAMOTO Takashi Date: Mon, 22 Mar 2021 10:56:51 +0900 Subject: [PATCH] Improve stack usage for flood filling * Make flood filling logic iterative (vs recursive) I basically tried one-to-one conversions here to avoid mistakes. probably it has a room for later optimizations. * Use explicit malloc (vs variables on stack) to allocate the work area. * Estimate the amount of memory for the work area dynamically from the image size, instead of using a constant FLOOD_FILL_MAX_DEPTH, which is too big in the most cases. --- lib/identify.c | 117 ++++++++++++++++++++++++++++++------------- lib/quirc.c | 23 +++++++++ lib/quirc_internal.h | 14 ++++++ 3 files changed, 119 insertions(+), 35 deletions(-) diff --git a/lib/identify.c b/lib/identify.c index 3cf2d28..00c4dd4 100644 --- a/lib/identify.c +++ b/lib/identify.c @@ -122,52 +122,99 @@ static void perspective_unmap(const double *c, * Span-based floodfill routine */ -#define FLOOD_FILL_MAX_DEPTH 4096 - typedef void (*span_func_t)(void *user_data, int y, int left, int right); -static void flood_fill_seed(struct quirc *q, int x, int y, int from, int to, - span_func_t func, void *user_data, - int depth) +static void flood_fill_seed(struct quirc *q, + int x0, int y0, + int from, int to, + span_func_t func, void *user_data) { - int left = x; - int right = x; + struct quirc_flood_fill_vars *const stack = q->flood_fill_vars; + const size_t stack_size = q->num_flood_fill_vars; + const struct quirc_flood_fill_vars *const last_vars = + &stack[stack_size - 1]; + + struct quirc_flood_fill_vars *vars; + struct quirc_flood_fill_vars *next_vars; int i; - quirc_pixel_t *row = q->pixels + y * q->w; + quirc_pixel_t *row; - if (depth >= FLOOD_FILL_MAX_DEPTH) - return; + /* Set up the first context */ + next_vars = stack; + next_vars->x = x0; + next_vars->y = y0; - while (left > 0 && row[left - 1] == from) - left--; +call: + vars = next_vars; + vars->left = vars->x; + vars->right = vars->x; - while (right < q->w - 1 && row[right + 1] == from) - right++; + row = q->pixels + vars->y * q->w; + + while (vars->left > 0 && row[vars->left - 1] == from) + vars->left--; + + while (vars->right < q->w - 1 && row[vars->right + 1] == from) + vars->right++; /* Fill the extent */ - for (i = left; i <= right; i++) + for (i = vars->left; i <= vars->right; i++) row[i] = to; if (func) - func(user_data, y, left, right); + func(user_data, vars->y, vars->left, vars->right); - /* Seed new flood-fills */ - if (y > 0) { - row = q->pixels + (y - 1) * q->w; - - for (i = left; i <= right; i++) - if (row[i] == from) - flood_fill_seed(q, i, y - 1, from, to, - func, user_data, depth + 1); + if (vars == last_vars) { + return; } - if (y < q->h - 1) { - row = q->pixels + (y + 1) * q->w; + /* Seed new flood-fills */ + if (vars->y > 0) { + row = q->pixels + (vars->y - 1) * q->w; - for (i = left; i <= right; i++) - if (row[i] == from) - flood_fill_seed(q, i, y + 1, from, to, - func, user_data, depth + 1); + for (i = vars->left; i <= vars->right; i++) + if (row[i] == from) { + /* Save the current context */ + vars->i = i; + vars->pc = 1; + + /* Set up the next context */ + next_vars = vars + 1; + next_vars->x = i; + next_vars->y = vars->y - 1; + goto call; +return_from_call1: ; + } + } + + if (vars->y < q->h - 1) { + row = q->pixels + (vars->y + 1) * q->w; + + for (i = vars->left; i <= vars->right; i++) + if (row[i] == from) { + /* Save the current context */ + vars->i = i; + vars->pc = 2; + + /* Set up the next context */ + next_vars = vars + 1; + next_vars->x = i; + next_vars->y = vars->y + 1; + goto call; +return_from_call2: ; + } + } + + if (vars > stack) { + /* Restore the previous context */ + vars--; + i = vars->i; + if (vars->pc == 1) { + row = q->pixels + (vars->y - 1) * q->w; + goto return_from_call1; + } + row = q->pixels + (vars->y + 1) * q->w; + goto return_from_call2; } } @@ -260,7 +307,7 @@ static int region_code(struct quirc *q, int x, int y) box->seed.y = y; box->capstone = -1; - flood_fill_seed(q, x, y, pixel, region, area_count, box, 0); + flood_fill_seed(q, x, y, pixel, region, area_count, box); return region; } @@ -330,7 +377,7 @@ static void find_region_corners(struct quirc *q, psd.scores[0] = -1; flood_fill_seed(q, region->seed.x, region->seed.y, rcode, QUIRC_PIXEL_BLACK, - find_one_corner, &psd, 0); + find_one_corner, &psd); psd.ref.x = psd.corners[0].x - psd.ref.x; psd.ref.y = psd.corners[0].y - psd.ref.y; @@ -348,7 +395,7 @@ static void find_region_corners(struct quirc *q, flood_fill_seed(q, region->seed.x, region->seed.y, QUIRC_PIXEL_BLACK, rcode, - find_other_corners, &psd, 0); + find_other_corners, &psd); } static void record_capstone(struct quirc *q, int ring, int stone) @@ -966,10 +1013,10 @@ static void record_qr_grid(struct quirc *q, int a, int b, int c) flood_fill_seed(q, reg->seed.x, reg->seed.y, qr->align_region, QUIRC_PIXEL_BLACK, - NULL, NULL, 0); + NULL, NULL); flood_fill_seed(q, reg->seed.x, reg->seed.y, QUIRC_PIXEL_BLACK, qr->align_region, - find_leftmost_to_line, &psd, 0); + find_leftmost_to_line, &psd); } } diff --git a/lib/quirc.c b/lib/quirc.c index bbe1fe9..6108cfb 100644 --- a/lib/quirc.c +++ b/lib/quirc.c @@ -41,6 +41,7 @@ void quirc_destroy(struct quirc *q) same size, so we need to be careful here to avoid a double free */ if (!QUIRC_PIXEL_ALIAS_IMAGE) free(q->pixels); + free(q->flood_fill_vars); free(q); } @@ -48,6 +49,8 @@ int quirc_resize(struct quirc *q, int w, int h) { uint8_t *image = NULL; quirc_pixel_t *pixels = NULL; + size_t num_vars; + struct quirc_flood_fill_vars *vars = NULL; /* * XXX: w and h should be size_t (or at least unsigned) as negatives @@ -86,6 +89,22 @@ int quirc_resize(struct quirc *q, int w, int h) goto fail; } + /* + * alloc the work area for the flood filling logic. + * + * the size was chosen with the following assumptions and observations: + * + * - rings are the regions which requires the biggest work area. + * - they consumes the most when they are rotated by about 45 degree. + * in that case, the necessary depth is about (2 * height_of_the_ring). + * - the maximum height of rings would be about 1/3 of the image height. + */ + + num_vars = h * 2 / 3; + vars = malloc(sizeof(*vars) * num_vars); + if (!vars) + goto fail; + /* alloc succeeded, update `q` with the new size and buffers */ q->w = w; q->h = h; @@ -95,12 +114,16 @@ int quirc_resize(struct quirc *q, int w, int h) free(q->pixels); q->pixels = pixels; } + free(q->flood_fill_vars); + q->flood_fill_vars = vars; + q->num_flood_fill_vars = num_vars; return 0; /* NOTREACHED */ fail: free(image); free(pixels); + free(vars); return -1; } diff --git a/lib/quirc_internal.h b/lib/quirc_internal.h index 371572f..a94d8e9 100644 --- a/lib/quirc_internal.h +++ b/lib/quirc_internal.h @@ -17,6 +17,8 @@ #ifndef QUIRC_INTERNAL_H_ #define QUIRC_INTERNAL_H_ +#include + #include "quirc.h" #define QUIRC_PIXEL_WHITE 0 @@ -75,6 +77,15 @@ struct quirc_grid { double c[QUIRC_PERSPECTIVE_PARAMS]; }; +struct quirc_flood_fill_vars { + int x; + int y; + int right; + int left; + int i; + int pc; /* caller id */ +}; + struct quirc { uint8_t *image; quirc_pixel_t *pixels; @@ -89,6 +100,9 @@ struct quirc { int num_grids; struct quirc_grid grids[QUIRC_MAX_GRIDS]; + + size_t num_flood_fill_vars; + struct quirc_flood_fill_vars *flood_fill_vars; }; /************************************************************************