629 lines
14 KiB
C
Executable file
629 lines
14 KiB
C
Executable file
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include "bitmap-outliner.h"
|
|
|
|
#define MIN_SEGMENTS_COUNT 64
|
|
|
|
/**
|
|
* Information about how to proceed to next arrow.
|
|
*/
|
|
typedef struct {
|
|
bmol_arr_type arrow:8; ///< Type of arrow.
|
|
int8_t dx; ///< Relative to current position.
|
|
int8_t dy; ///< Relative to current position.
|
|
} const arrow_next;
|
|
|
|
/**
|
|
* String buffer context.
|
|
*/
|
|
typedef struct {
|
|
char* buffer; ///< A string buffer.
|
|
size_t buf_size; ///< The remaining buffer size
|
|
size_t size; ///< The buffer size.
|
|
} buffer_ctx;
|
|
|
|
/**
|
|
* The arrow states.
|
|
*/
|
|
static arrow_next const states[][2][4] = {
|
|
[BMOL_ARR_RIGHT] = {
|
|
{
|
|
{BMOL_ARR_LEFT, +1, 0}, {BMOL_ARR_UP, +1, -1},
|
|
{BMOL_ARR_RIGHT, +1, 0}, {BMOL_ARR_DOWN, +1, +1},
|
|
}, {
|
|
{BMOL_ARR_DOWN, +1, +1}, {BMOL_ARR_DOWN, +1, +1},
|
|
{BMOL_ARR_RIGHT, +1, 0}, {BMOL_ARR_UP, +1, -1},
|
|
},
|
|
},
|
|
[BMOL_ARR_LEFT] = {
|
|
{
|
|
{BMOL_ARR_RIGHT, -1, 0}, {BMOL_ARR_DOWN, 0, +1},
|
|
{BMOL_ARR_LEFT, -1, 0}, {BMOL_ARR_UP, 0, -1},
|
|
}, {
|
|
{BMOL_ARR_UP, 0, -1}, {BMOL_ARR_UP, 0, -1},
|
|
{BMOL_ARR_LEFT, -1, 0}, {BMOL_ARR_DOWN, 0, +1},
|
|
},
|
|
},
|
|
[BMOL_ARR_DOWN] = {
|
|
{
|
|
{BMOL_ARR_UP, 0, +2}, {BMOL_ARR_RIGHT, 0, +1},
|
|
{BMOL_ARR_DOWN, 0, +2}, {BMOL_ARR_LEFT, -1, +1}
|
|
}, {
|
|
{BMOL_ARR_LEFT, -1, +1}, {BMOL_ARR_LEFT, -1, +1},
|
|
{BMOL_ARR_DOWN, 0, +2}, {BMOL_ARR_RIGHT, 0, +1},
|
|
},
|
|
},
|
|
[BMOL_ARR_UP] = {
|
|
{
|
|
{BMOL_ARR_DOWN, 0, -2}, {BMOL_ARR_LEFT, -1, -1},
|
|
{BMOL_ARR_UP, 0, -2}, {BMOL_ARR_RIGHT, 0, -1},
|
|
}, {
|
|
{BMOL_ARR_RIGHT, 0, -1}, {BMOL_ARR_RIGHT, 0, -1},
|
|
{BMOL_ARR_UP, 0, -2}, {BMOL_ARR_LEFT, -1, -1},
|
|
},
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Convert arrow grid coordinates to path coordinates.
|
|
*
|
|
* @param type Arrow type.
|
|
* @param gx Grid X-coordinate.
|
|
* @param gy Grid Y-coordinate.
|
|
* @param rx Path X-coordinates.
|
|
* @param ry Path Y-coordinates.
|
|
*/
|
|
static void real_coords(bmol_arr_type type, int gx, int gy, int* rx, int* ry) {
|
|
/**
|
|
* Defines coordinates.
|
|
*/
|
|
typedef struct {
|
|
int8_t x; ///< X-coordinate.
|
|
int8_t y; ///< Y-coordinate.
|
|
} coords;
|
|
|
|
/**
|
|
* Delta to add to convert to real coordinates.
|
|
*/
|
|
static coords const real_delta[] = {
|
|
[BMOL_ARR_RIGHT] = {0, 0},
|
|
[BMOL_ARR_LEFT] = {1, 0},
|
|
[BMOL_ARR_DOWN] = {0, 0},
|
|
[BMOL_ARR_UP] = {0, 1},
|
|
};
|
|
|
|
coords const* real = &real_delta[type];
|
|
|
|
*rx = gx - 1 + real->x;
|
|
*ry = (gy - 1) / 2 + real->y;
|
|
}
|
|
|
|
/**
|
|
* Allocate more segments.
|
|
*
|
|
* @param outliner The outline object.
|
|
* @return 0 on success.
|
|
*/
|
|
static int grow_segments(bmol_outliner* outliner) {
|
|
bmol_path_seg* segments = outliner->segments;
|
|
int segments_cap = outliner->segments_cap * 2 + 1;
|
|
|
|
if (segments_cap < MIN_SEGMENTS_COUNT) {
|
|
segments_cap = MIN_SEGMENTS_COUNT;
|
|
}
|
|
|
|
segments = realloc(segments, sizeof(*segments) * segments_cap);
|
|
|
|
if (!segments) {
|
|
return -1;
|
|
}
|
|
|
|
outliner->segments = segments;
|
|
outliner->segments_cap = segments_cap;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Add segment to segment list.
|
|
*
|
|
* @param outliner The outline object.
|
|
* @param type The arrow type.
|
|
* @param dx Path segment X-coordinate.
|
|
* @param dy Path segment Y-coordinate.
|
|
* @return 0 on success.
|
|
*/
|
|
static int push_segment(bmol_outliner* outliner, bmol_arr_type type, int dx, int dy) {
|
|
bmol_path_seg* segments = outliner->segments;
|
|
|
|
if (outliner->segments_size >= outliner->segments_cap) {
|
|
if (grow_segments(outliner) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
segments = outliner->segments;
|
|
}
|
|
|
|
bmol_path_seg* segment = &segments[outliner->segments_size++];
|
|
|
|
segment->type = type;
|
|
segment->dx = dx;
|
|
segment->dy = dy;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Search adjacent arrow relative to given position.
|
|
*
|
|
* @param width Width of bitmap.
|
|
* @param height Height of bitmap.
|
|
* @param grid Arrow grid.
|
|
* @param type Current arrow type.
|
|
* @param inner Is inner path.
|
|
* @param xd Arrow X-coordinate.
|
|
* @param yd Arrow Y-coordinate.
|
|
*/
|
|
static bmol_arrow* search_adjacent_arrow(int width, int height, bmol_arrow grid[height * 2 + 3][width + 3], bmol_arr_type type, int inner, int* xd, int* yd) {
|
|
arrow_next* arrows = &states[type][inner][0];
|
|
|
|
// search for adjacent arrows in precedence order
|
|
for (int n = 0; n < 4; n++) {
|
|
arrow_next const* search = &arrows[n];
|
|
int const xn = *xd + search->dx;
|
|
int const yn = *yd + search->dy;
|
|
bmol_arrow* nextArrow = &grid[yn][xn];
|
|
|
|
// follow adjacent arrow
|
|
if (nextArrow->type == search->arrow && !nextArrow->seen) {
|
|
// is opposite arrow
|
|
if (n == 0) {
|
|
if (!inner && nextArrow->inner) {
|
|
*xd = xn;
|
|
*yd = yn;
|
|
|
|
nextArrow->seen = 0; // do not mark opposite arrow as seen
|
|
type = nextArrow->type;
|
|
|
|
// search next inner arrow relative to opposite arrow
|
|
return search_adjacent_arrow(width, height, grid, type, 1, xd, yd);
|
|
}
|
|
else {
|
|
continue;
|
|
}
|
|
}
|
|
// ignore arrows not in path type
|
|
else if (nextArrow->inner != inner) {
|
|
continue;
|
|
}
|
|
|
|
*xd = xn;
|
|
*yd = yn;
|
|
|
|
return nextArrow;
|
|
}
|
|
}
|
|
|
|
// switch to outer path if no more inner path arrows found
|
|
if (inner) {
|
|
return search_adjacent_arrow(width, height, grid, type, 0, xd, yd);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Make path segments.
|
|
*
|
|
* @param x First path arrow.
|
|
* @param y First path arrow.
|
|
* @param width Width of bitmap.
|
|
* @param height Height of bitmap.
|
|
* @param grid Grid to search for paths.
|
|
*/
|
|
static int make_path(bmol_outliner* outliner, int x, int y, int width, int height, bmol_arrow grid[height * 2 + 3][width + 3]) {
|
|
int xd = x;
|
|
int yd = y;
|
|
int xr, yr;
|
|
int xp, yp;
|
|
bmol_arrow* arrow = &grid[yd][xd];
|
|
bmol_arrow* nextArrow = arrow;
|
|
bmol_arr_type type = arrow->type;
|
|
int inner = (type == BMOL_ARR_LEFT);
|
|
bmol_arr_type prevType = type;
|
|
|
|
real_coords(type, xd, yd, &xr, &yr);
|
|
|
|
xp = xr;
|
|
yp = yr;
|
|
|
|
// begin path
|
|
if (push_segment(outliner, BMOL_ARR_NONE, xr, yr) != 0) {
|
|
return -1;
|
|
}
|
|
|
|
do {
|
|
arrow = nextArrow;
|
|
arrow->seen = 1; // mark as seen
|
|
|
|
nextArrow = search_adjacent_arrow(width, height, grid, type, inner, &xd, &yd);
|
|
type = BMOL_ARR_NONE;
|
|
|
|
if (nextArrow) {
|
|
type = nextArrow->type;
|
|
inner = nextArrow->inner; // switch arrow type
|
|
}
|
|
|
|
// end path segment if arrow changes
|
|
// and ignore last path segment
|
|
if (type != prevType && type) {
|
|
int dx, dy;
|
|
|
|
real_coords(type, xd, yd, &xr, &yr);
|
|
|
|
dx = xr - xp;
|
|
dy = yr - yp;
|
|
xp = xr;
|
|
yp = yr;
|
|
|
|
// add path segment
|
|
if (push_segment(outliner, prevType, dx, dy) < 0) {
|
|
return-1;
|
|
}
|
|
|
|
prevType = type;
|
|
}
|
|
}
|
|
while (type);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Mark arrow as outer and inner.
|
|
*
|
|
* @param x First path arrow.
|
|
* @param y First path arrow.
|
|
* @param width Width of bitmap.
|
|
* @param height Height of bitmap.
|
|
* @param grid Grid to search for paths.
|
|
*/
|
|
static void set_path_type(bmol_outliner* outliner, int x, int y, int width, int height, bmol_arrow grid[height * 2 + 3][width + 3]) {
|
|
bmol_arrow* arrow = &grid[y][x];
|
|
bmol_arr_type type = arrow->type;
|
|
int const inner = (type == BMOL_ARR_LEFT);
|
|
|
|
do {
|
|
arrow_next* arrows = &states[type][inner][1]; // ignore opponent arrow
|
|
|
|
// mark as visited
|
|
arrow->visited = 1;
|
|
arrow->inner = inner;
|
|
|
|
type = BMOL_ARR_NONE;
|
|
|
|
// search for adjacent arrows in precedence order
|
|
for (int n = 0; n < 3; n++) {
|
|
arrow_next* search = &arrows[n];
|
|
int xn = x + search->dx;
|
|
int yn = y + search->dy;
|
|
bmol_arrow* nextArrow = &grid[yn][xn];
|
|
|
|
// follow adjacent arrow
|
|
if (nextArrow->type == search->arrow && !nextArrow->visited) {
|
|
x = xn;
|
|
y = yn;
|
|
type = nextArrow->type;
|
|
arrow = nextArrow;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
while (type);
|
|
}
|
|
|
|
/**
|
|
* Search all paths in arrow grid.
|
|
*
|
|
* @param width Width of bitmap.
|
|
* @param height Height of bitmap.
|
|
* @param grid Grid to search for paths.
|
|
*/
|
|
static int search_paths(bmol_outliner* outliner, int width, int height, bmol_arrow grid[height * 2 + 3][width + 3]) {
|
|
int const gridWidth = width + 3;
|
|
int const gridHeight = height * 2 + 3;
|
|
|
|
// set arrow types
|
|
for (int y = 1; y < gridHeight - 1; y += 2) {
|
|
for (int x = 1; x < gridWidth - 1; x++) {
|
|
bmol_arrow arrow = grid[y][x];
|
|
|
|
if (arrow.type && !arrow.visited) {
|
|
set_path_type(outliner, x, y, width, height, grid);
|
|
}
|
|
}
|
|
}
|
|
|
|
// search right and left arrows in grid
|
|
for (int y = 1; y < gridHeight - 1; y += 2) {
|
|
for (int x = 1; x < gridWidth - 1; x++) {
|
|
bmol_arrow arrow = grid[y][x];
|
|
|
|
if (arrow.type && !arrow.seen) {
|
|
if (make_path(outliner, x, y, width, height, grid) < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Fill arrow grid.
|
|
*
|
|
* @param width Width of bitmap.
|
|
* @param height Height of bitmap.
|
|
* @param map The bitmap.
|
|
* @param grid Grid to fill with arrows.
|
|
*/
|
|
static void set_arrows(int width, int height, uint8_t const map[height][width], bmol_arrow grid[height * 2 + 3][width + 3]) {
|
|
int x, y, t;
|
|
|
|
for (x = 0; x < width; x++) {
|
|
for (y = 0, t = 0; y < height; y++) {
|
|
int p = map[y][x] != 0;
|
|
|
|
if (p != t) {
|
|
grid[y * 2 + 1][x + 1].type = t ? BMOL_ARR_LEFT : BMOL_ARR_RIGHT;
|
|
t = p;
|
|
}
|
|
}
|
|
|
|
if (map[y - 1][x]) {
|
|
grid[y * 2 + 1][x + 1].type = BMOL_ARR_LEFT;
|
|
}
|
|
}
|
|
|
|
for (y = 0; y < height; y++) {
|
|
for (x = 0, t = 0; x < width; x++) {
|
|
int p = map[y][x] != 0;
|
|
|
|
if (p != t) {
|
|
grid[y * 2 + 2][x + 1].type = t ? BMOL_ARR_DOWN : BMOL_ARR_UP;
|
|
t = p;
|
|
}
|
|
}
|
|
|
|
if (map[y][x - 1]) {
|
|
grid[y * 2 + 2][x + 1].type = BMOL_ARR_DOWN;
|
|
}
|
|
}
|
|
}
|
|
|
|
bmol_outliner* bmol_alloc(int width, int height, uint8_t const* data) {
|
|
bmol_outliner* outliner;
|
|
size_t const size = (width + 3) * (height * 2 + 3) * sizeof(outliner->arrow_grid[0]);
|
|
|
|
outliner = calloc(1, sizeof(*outliner) + size);
|
|
|
|
if (!outliner) {
|
|
return NULL;
|
|
}
|
|
|
|
outliner->width = width;
|
|
outliner->height = height;
|
|
outliner->data = data;
|
|
|
|
if (grow_segments(outliner) != 0) {
|
|
bmol_free(outliner);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
return outliner;
|
|
}
|
|
|
|
void bmol_free(bmol_outliner* outliner) {
|
|
free(outliner->segments);
|
|
free(outliner);
|
|
}
|
|
|
|
bmol_path_seg const* bmol_find_paths(bmol_outliner* outliner, int* out_size) {
|
|
int const width = outliner->width;
|
|
int const height = outliner->height;
|
|
bmol_arrow* grid = outliner->arrow_grid;
|
|
uint8_t const* data = outliner->data;
|
|
|
|
if (!data) {
|
|
return NULL;
|
|
}
|
|
|
|
outliner->segments_size = 0;
|
|
|
|
set_arrows(width, height, (const uint8_t (*)[width])data, (bmol_arrow (*)[width])grid);
|
|
|
|
if (search_paths(outliner, width, height, (bmol_arrow (*)[width])grid) < 0) {
|
|
return NULL;
|
|
}
|
|
|
|
if (out_size) {
|
|
*out_size = outliner->segments_size;
|
|
}
|
|
|
|
return outliner->segments;
|
|
}
|
|
|
|
/**
|
|
* Append formated string to buffer.
|
|
*
|
|
* @param buffer_ref A reference to the buffer.
|
|
* @param size_ref A reference to the buffer size.
|
|
* @param format The number format.
|
|
*/
|
|
static void append_string(buffer_ctx* ctx, char const* format, ...) {
|
|
int size;
|
|
va_list args;
|
|
|
|
va_start(args, format);
|
|
size = vsnprintf(ctx->buffer, ctx->buf_size, format, args);
|
|
va_end(args);
|
|
|
|
if (size > 0) {
|
|
if (size >= ctx->buf_size) {
|
|
size = ctx->buf_size - 1;
|
|
}
|
|
|
|
ctx->buf_size -= size;
|
|
ctx->buffer += size;
|
|
ctx->size += size;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate log2 of an integer.
|
|
*
|
|
* https://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
|
|
*
|
|
* @param n The value to get the log2 from.
|
|
* @return log2 of the given integer.
|
|
*/
|
|
static uint32_t log2_fast(uint32_t n) {
|
|
uint32_t r;
|
|
uint32_t shift;
|
|
|
|
r = (n > 0xFFFF) << 4; n >>= r;
|
|
shift = (n > 0xFF ) << 3; n >>= shift; r |= shift;
|
|
shift = (n > 0xF ) << 2; n >>= shift; r |= shift;
|
|
shift = (n > 0x3 ) << 1; n >>= shift; r |= shift;
|
|
r |= (n >> 1);
|
|
|
|
return r;
|
|
}
|
|
|
|
/**
|
|
* Calculate log10 of an integer.
|
|
*
|
|
* https://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10
|
|
*
|
|
* @param n The value to get the log10 from.
|
|
* @return log10 of the given integer.
|
|
*/
|
|
static int log10_fast(uint32_t n) {
|
|
static uint32_t const pow_10[] = {
|
|
1, 10, 100, 1000, 10000, 100000,
|
|
1000000, 10000000, 100000000, 1000000000
|
|
};
|
|
|
|
int t = (log2_fast(n) + 1) * 1233 >> 12;
|
|
int r = t - (n < pow_10[t]);
|
|
|
|
return r;
|
|
}
|
|
|
|
/**
|
|
* Write SVG path to string buffer.
|
|
*
|
|
* @param segments The path segments.
|
|
* @param count The number of path segments.
|
|
* @param buffer_ref A reference to the buffer.
|
|
* @param size_ref A reference to the buffer size.
|
|
*/
|
|
static void write_svg(bmol_path_seg const* segments, int count, buffer_ctx* ctx) {
|
|
for (int i = 0; i < count; i++) {
|
|
bmol_path_seg const* segment = &segments[i];
|
|
|
|
switch (segment->type) {
|
|
case BMOL_ARR_NONE: {
|
|
if (i > 0) {
|
|
append_string(ctx, "z");
|
|
}
|
|
|
|
append_string(ctx, "M%d,%d", segment->dx, segment->dy);
|
|
break;
|
|
}
|
|
case BMOL_ARR_RIGHT:
|
|
case BMOL_ARR_LEFT: {
|
|
append_string(ctx, "h%d", segment->dx);
|
|
break;
|
|
}
|
|
case BMOL_ARR_DOWN:
|
|
case BMOL_ARR_UP: {
|
|
append_string(ctx, "v%d", segment->dy);
|
|
break;
|
|
}
|
|
default: {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
append_string(ctx, "z");
|
|
}
|
|
|
|
size_t bmol_svg_path_len(bmol_outliner* outliner) {
|
|
int len = 0;
|
|
|
|
for (int i = 0; i < outliner->segments_size; i++) {
|
|
bmol_path_seg const* segment = &outliner->segments[i];
|
|
int dx = segment->dx;
|
|
int dy = segment->dy;
|
|
|
|
switch (segment->type) {
|
|
case BMOL_ARR_NONE: {
|
|
len += 4 + log10_fast(dx > 0 ? dx : 1) + 1 + log10_fast(dy > 0 ? dy : 1) + 1;
|
|
break;
|
|
}
|
|
case BMOL_ARR_RIGHT:
|
|
case BMOL_ARR_LEFT:
|
|
case BMOL_ARR_DOWN:
|
|
case BMOL_ARR_UP: {
|
|
if (dx < 0) {
|
|
dx = -dx;
|
|
len++;
|
|
}
|
|
|
|
if (dx > 0) {
|
|
len += 1 + log10_fast(dx) + 1;
|
|
}
|
|
|
|
if (dy < 0) {
|
|
dy = -dy;
|
|
len++;
|
|
}
|
|
|
|
if (dy > 0) {
|
|
len += 1 + log10_fast(dy) + 1;
|
|
}
|
|
|
|
break;
|
|
}
|
|
default: {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return len + 1;
|
|
}
|
|
|
|
size_t bmol_svg_path(bmol_outliner* outliner, char buffer[], size_t buf_size) {
|
|
buffer_ctx ctx = {
|
|
.buffer = buffer,
|
|
.buf_size = buf_size,
|
|
.size = 0,
|
|
};
|
|
|
|
write_svg(outliner->segments, outliner->segments_size, &ctx);
|
|
|
|
return ctx.size;
|
|
}
|
|
|
|
void bmol_set_bitmap(bmol_outliner* outliner, uint8_t const* data) {
|
|
outliner->data = data;
|
|
}
|