diff options
Diffstat (limited to 'zbar/sqcode.c')
-rw-r--r-- | zbar/sqcode.c | 578 |
1 files changed, 578 insertions, 0 deletions
diff --git a/zbar/sqcode.c b/zbar/sqcode.c new file mode 100644 index 0000000..422c803 --- /dev/null +++ b/zbar/sqcode.c @@ -0,0 +1,578 @@ +/*Copyright (C) 2018 Javier Serrano Polo <javier@jasp.net> + You can redistribute this library and/or modify it under the terms of the + GNU Lesser General Public License as published by the Free Software + Foundation; either version 2.1 of the License, or (at your option) any later + version.*/ +#include "config.h" + +#include "sqcode.h" + +#include <stdbool.h> +#include <stdlib.h> + +#include "image.h" +#include "img_scanner.h" + +typedef enum +{ + SHAPE_DOT, + SHAPE_CORNER, + SHAPE_OTHER, + SHAPE_VOID +} shape_t; + +typedef struct { + float x; + float y; +} sq_point; + +typedef struct { + shape_t type; + unsigned x0; + unsigned y0; + unsigned width; + unsigned height; + sq_point center; +} sq_dot; + +struct sq_reader { + bool enabled; +}; + +/*Initializes a client reader handle.*/ +static void sq_reader_init(sq_reader *reader) +{ + reader->enabled = true; +} + +/*Allocates a client reader handle.*/ +sq_reader *_zbar_sq_create(void) +{ + sq_reader *reader = malloc(sizeof(sq_reader)); + if (reader) + sq_reader_init(reader); + return reader; +} + +/*Frees a client reader handle.*/ +void _zbar_sq_destroy(sq_reader *reader) +{ + free(reader); +} + +/* reset finder state between scans */ +void _zbar_sq_reset(sq_reader *reader) +{ + reader->enabled = true; +} + +int _zbar_sq_new_config(sq_reader *reader, unsigned config) +{ + reader->enabled = config; + return 0; +} + +static const char base64_table[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' +}; + +static char *base64_encode_buffer(const char *s, size_t size) +{ + char *e; + size_t encoded_size = (size + 2) / 3 * 4 + 1; + char *encoded = malloc(encoded_size); + if (!encoded) + return NULL; + e = encoded; + for (;;) { + unsigned char c = (*s >> 2) & 0x3f; + *e++ = base64_table[c]; + c = (*s++ << 4) & 0x30; + if (!--size) { + *e++ = base64_table[c]; + *e++ = '='; + *e++ = '='; + break; + } + c |= (*s >> 4) & 0x0f; + *e++ = base64_table[c]; + c = (*s++ << 2) & 0x3c; + if (!--size) { + *e++ = base64_table[c]; + *e++ = '='; + break; + } + c |= (*s >> 6) & 0x03; + *e++ = base64_table[c]; + c = *s++ & 0x3f; + *e++ = base64_table[c]; + if (!--size) + break; + } + *e = '\0'; + return encoded; +} + +static bool sq_extract_text(zbar_image_scanner_t *iscn, const char *buf, + size_t len) +{ + size_t b64_len; + zbar_symbol_t *sym = _zbar_image_scanner_alloc_sym(iscn, ZBAR_SQCODE, 0); + sym->data = base64_encode_buffer(buf, len); + if (!sym->data) { + _zbar_image_scanner_recycle_syms(iscn, sym); + return true; + } + b64_len = (len + 2) / 3 * 4; + sym->data_alloc = b64_len + 1; + sym->datalen = b64_len; + _zbar_image_scanner_add_sym(iscn, sym); + return false; +} + +static bool is_black_color(const unsigned char c) +{ + return c <= 0x7f; +} + +static bool is_black(zbar_image_t *img, int x, int y) +{ + const unsigned char *data; + if (x < 0 || (unsigned)x >= img->width || y < 0 || + (unsigned)y >= img->height) + return false; + data = img->data; + return is_black_color(data[y * img->width + x]); +} + +static void set_dot_center(sq_dot *dot, float x, float y) +{ + dot->center.x = x; + dot->center.y = y; +} + +static void sq_scan_shape(zbar_image_t *img, sq_dot *dot, int start_x, + int start_y) +{ + int x, y; + unsigned x0, y0, width, height, x_sum, y_sum, total_weight; + const unsigned char *data; + if (!is_black(img, start_x, start_y)) { + dot->type = SHAPE_VOID; + dot->x0 = start_x; + dot->y0 = start_y; + dot->width = 0; + dot->height = 0; + set_dot_center(dot, start_x, start_y); + return; + } + + x0 = start_x; + y0 = start_y; + width = 1; + height = 1; + +new_point: + for (x = x0 - 1; x < (int)(x0 + width + 1); x++) { + if (is_black(img, x, y0 - 1)) { + y0 = y0 - 1; + height++; + goto new_point; + } + if (is_black(img, x, y0 + height)) { + height++; + goto new_point; + } + } + for (y = y0; y < (int)(y0 + height); y++) { + if (is_black(img, x0 - 1, y)) { + x0 = x0 - 1; + width++; + goto new_point; + } + if (is_black(img, x0 + width, y)) { + width++; + goto new_point; + } + } + + dot->x0 = x0; + dot->y0 = y0; + dot->width = width; + dot->height = height; + + /* Is it a corner? */ + if (is_black(img, x0 + 0.25 * width, y0 + 0.25 * height) && + !is_black(img, x0 + 0.75 * width, y0 + 0.25 * height) && + !is_black(img, x0 + 0.25 * width, y0 + 0.75 * height) && + is_black(img, x0 + 0.75 * width, y0 + 0.75 * height)) { + dot->type = SHAPE_CORNER; + set_dot_center(dot, x0 + 0.5 * width, y0 + 0.5 * height); + return; + } + + /* Set dot center */ + data = img->data; + x_sum = 0; + y_sum = 0; + total_weight = 0; + for (y = y0; y < (int)(y0 + height); y++) { + int x; + for (x = x0; x < (int)(x0 + width); x++) { + unsigned char weight; + if (!is_black(img, x, y)) + continue; + weight = 0xff - data[y * img->width + x]; + x_sum += weight * x; + y_sum += weight * y; + total_weight += weight; + } + } + dot->type = SHAPE_DOT; + set_dot_center(dot, x_sum / (float)total_weight + 0.5, + y_sum / (float)total_weight + 0.5); + + /* TODO: Is it other shape? White hole? Really a dot? */ +} + +static void set_middle_point(sq_point *middle, const sq_point *start, + const sq_point *end) +{ + middle->x = (start->x + end->x) / 2; + middle->y = (start->y + end->y) / 2; +} + +bool find_left_dot(zbar_image_t *img, sq_dot *dot, unsigned *found_x, + unsigned *found_y) +{ + int y, x; + for (y = dot->y0; y < (int)(dot->y0 + dot->height); y++) { + for (x = dot->x0 - 1; x >= (int)(dot->x0 - 2 * dot->width); x--) { + if (is_black(img, x, y)) { + *found_x = x; + *found_y = y; + return true; + } + } + } + return false; +} + +bool find_right_dot(zbar_image_t *img, sq_dot *dot, unsigned *found_x, + unsigned *found_y) +{ + int y, x; + for (y = dot->y0; y < (int)(dot->y0 + dot->height); y++) { + for (x = dot->x0 + dot->width; x < (int)(dot->x0 + 3 * dot->width); + x++) { + if (is_black(img, x, y)) { + *found_x = x; + *found_y = y; + return true; + } + } + } + return false; +} + +bool find_bottom_dot(zbar_image_t *img, sq_dot *dot, unsigned *found_x, + unsigned *found_y) +{ + int x, y; + + for (x = dot->x0 + dot->width - 1; x >= (int)dot->x0; x--) { + for (y = dot->y0 + dot->height; y < (int)(dot->y0 + 3 * dot->height); + y++) { + if (is_black(img, x, y)) { + *found_x = x; + *found_y = y; + return true; + } + } + } + return false; +} + +int _zbar_sq_decode(sq_reader *reader, zbar_image_scanner_t *iscn, + zbar_image_t *img) +{ + unsigned scan_y, scan_x, y; + sq_dot start_dot, top_left_dot, top_right_dot, bottom_left_dot, + bottom_right_dot, bottom_left2_dot; + bool start_corner, error; + sq_point *top_border, *left_border, *right_border, *bottom_border; + size_t border_len, cur_len, offset, bit_side_len, bit_len, byte_len, idx; + float inc_x, inc_y; + void *ptr; + char *buf; + + if (!reader->enabled) + return 0; + + if (img->format != fourcc('Y', '8', '0', '0')) { + fputs("Unexpected image format\n", stderr); + return 1; + } + + /* Starting pixel */ + for (scan_y = 0; scan_y < img->height; scan_y++) { + for (scan_x = 0; scan_x < img->width; scan_x++) { + if (is_black(img, scan_x, scan_y)) + goto found_start; + } + } + return 1; + +found_start:; + /* Starting dot */ + sq_scan_shape(img, &start_dot, scan_x, scan_y); + + start_corner = start_dot.type == SHAPE_CORNER; + + error = true; + + top_border = NULL; + left_border = NULL; + right_border = NULL; + bottom_border = NULL; + + if (start_corner) { + border_len = 0; + } else { + border_len = 1; + top_border = malloc(sizeof(sq_point)); + if (!top_border) + return 1; + top_border[0] = start_dot.center; + } + + top_left_dot = start_dot; + while (find_left_dot(img, &top_left_dot, &scan_x, &scan_y)) { + sq_scan_shape(img, &top_left_dot, scan_x, scan_y); + if (top_left_dot.type != SHAPE_DOT) + goto free_borders; + if (border_len) { + void *ptr; + size_t i; + border_len += 2; + ptr = realloc(top_border, border_len * sizeof(sq_point)); + if (!ptr) + goto free_borders; + top_border = ptr; + for (i = border_len - 1; i >= 2; i--) + top_border[i] = top_border[i - 2]; + top_border[0] = top_left_dot.center; + set_middle_point(&top_border[1], &top_border[0], &top_border[2]); + } else { + border_len = 1; + top_border = malloc(sizeof(sq_point)); + if (!top_border) + return 1; + top_border[0] = top_left_dot.center; + } + } + if (top_left_dot.type != SHAPE_DOT) + goto free_borders; + + top_right_dot = start_dot; + if (!start_corner) { + while (find_right_dot(img, &top_right_dot, &scan_x, &scan_y)) { + void *ptr; + sq_scan_shape(img, &top_right_dot, scan_x, scan_y); + if (top_right_dot.type == SHAPE_CORNER) + break; + if (top_right_dot.type != SHAPE_DOT) + goto free_borders; + border_len += 2; + ptr = realloc(top_border, border_len * sizeof(sq_point)); + if (!ptr) + goto free_borders; + top_border = ptr; + top_border[border_len - 1] = top_right_dot.center; + set_middle_point(&top_border[border_len - 2], + &top_border[border_len - 3], + &top_border[border_len - 1]); + } + } + if (border_len < 3) + goto free_borders; + inc_x = top_border[border_len - 1].x - top_border[border_len - 3].x; + inc_y = top_border[border_len - 1].y - top_border[border_len - 3].y; + border_len += 3; + ptr = realloc(top_border, border_len * sizeof(sq_point)); + if (!ptr) + goto free_borders; + top_border = ptr; + top_border[border_len - 3].x = top_border[border_len - 4].x + 0.5 * inc_x; + top_border[border_len - 3].y = top_border[border_len - 4].y + 0.5 * inc_y; + top_border[border_len - 2].x = top_border[border_len - 4].x + inc_x; + top_border[border_len - 2].y = top_border[border_len - 4].y + inc_y; + top_border[border_len - 1].x = top_border[border_len - 4].x + 1.5 * inc_x; + top_border[border_len - 1].y = top_border[border_len - 4].y + 1.5 * inc_y; + + left_border = malloc(border_len * sizeof(sq_point)); + if (!left_border) + goto free_borders; + left_border[0] = top_border[0]; + + bottom_left_dot = top_left_dot; + cur_len = 1; + while (find_bottom_dot(img, &bottom_left_dot, &scan_x, &scan_y)) { + sq_scan_shape(img, &bottom_left_dot, scan_x, scan_y); + if (bottom_left_dot.type == SHAPE_CORNER) + break; + if (bottom_left_dot.type != SHAPE_DOT) + goto free_borders; + cur_len += 2; + if (cur_len > border_len) + goto free_borders; + left_border[cur_len - 1] = bottom_left_dot.center; + set_middle_point(&left_border[cur_len - 2], &left_border[cur_len - 3], + &left_border[cur_len - 1]); + } + if (cur_len != border_len - 3 || bottom_left_dot.type != SHAPE_CORNER) + goto free_borders; + inc_x = left_border[cur_len - 1].x - left_border[cur_len - 3].x; + inc_y = left_border[cur_len - 1].y - left_border[cur_len - 3].y; + left_border[border_len - 3].x = left_border[border_len - 4].x + 0.5 * inc_x; + left_border[border_len - 3].y = left_border[border_len - 4].y + 0.5 * inc_y; + left_border[border_len - 2].x = left_border[border_len - 4].x + inc_x; + left_border[border_len - 2].y = left_border[border_len - 4].y + inc_y; + left_border[border_len - 1].x = left_border[border_len - 4].x + 1.5 * inc_x; + left_border[border_len - 1].y = left_border[border_len - 4].y + 1.5 * inc_y; + + right_border = malloc(border_len * sizeof(sq_point)); + if (!right_border) + goto free_borders; + + bottom_right_dot = top_right_dot; + cur_len = 3; + while (find_bottom_dot(img, &bottom_right_dot, &scan_x, &scan_y)) { + sq_scan_shape(img, &bottom_right_dot, scan_x, scan_y); + if (bottom_right_dot.type != SHAPE_DOT) + goto free_borders; + if (cur_len == 3) { + cur_len++; + if (cur_len > border_len) + goto free_borders; + right_border[cur_len - 1] = bottom_right_dot.center; + } else { + cur_len += 2; + if (cur_len > border_len) + goto free_borders; + right_border[cur_len - 1] = bottom_right_dot.center; + set_middle_point(&right_border[cur_len - 2], + &right_border[cur_len - 3], + &right_border[cur_len - 1]); + } + } + if (cur_len != border_len || border_len < 6) + return 1; + inc_x = right_border[5].x - right_border[3].x; + inc_y = right_border[5].y - right_border[3].y; + right_border[2].x = right_border[3].x - 0.5 * inc_x; + right_border[2].y = right_border[3].y - 0.5 * inc_y; + right_border[1].x = right_border[3].x - inc_x; + right_border[1].y = right_border[3].y - inc_y; + right_border[0].x = right_border[3].x - 1.5 * inc_x; + right_border[0].y = right_border[3].y - 1.5 * inc_y; + + bottom_border = malloc(border_len * sizeof(sq_point)); + if (!bottom_border) + goto free_borders; + bottom_border[border_len - 1] = right_border[border_len - 1]; + + bottom_left2_dot = bottom_right_dot; + offset = border_len - 1; + while (find_left_dot(img, &bottom_left2_dot, &scan_x, &scan_y)) { + sq_scan_shape(img, &bottom_left2_dot, scan_x, scan_y); + if (bottom_left2_dot.type == SHAPE_CORNER) + break; + if (bottom_left2_dot.type != SHAPE_DOT) + goto free_borders; + if (offset < 2) + goto free_borders; + offset -= 2; + bottom_border[offset] = bottom_left2_dot.center; + set_middle_point(&bottom_border[offset + 1], &bottom_border[offset], + &bottom_border[offset + 2]); + } + if (offset != 3 || bottom_left2_dot.type != SHAPE_CORNER) + goto free_borders; + inc_x = bottom_border[5].x - bottom_border[3].x; + inc_y = bottom_border[5].y - bottom_border[3].y; + bottom_border[2].x = bottom_border[3].x - 0.5 * inc_x; + bottom_border[2].y = bottom_border[3].y - 0.5 * inc_y; + bottom_border[1].x = bottom_border[3].x - inc_x; + bottom_border[1].y = bottom_border[3].y - inc_y; + bottom_border[0].x = bottom_border[3].x - 1.5 * inc_x; + bottom_border[0].y = bottom_border[3].y - 1.5 * inc_y; + + /* Size check */ + if (border_len < 8 + 2 * (1 + 2) || border_len > 65535) + goto free_borders; + bit_side_len = border_len - 2 * (1 + 2); + + bit_len = bit_side_len * bit_side_len; + if (bit_len % 8) + goto free_borders; + byte_len = bit_len / 8; + buf = calloc(byte_len, sizeof(char)); + if (!buf) + goto free_borders; + + idx = 0; + for (y = 3; y <= border_len - 4; y++) { + unsigned x; + for (x = 3; x <= border_len - 4; x++) { + unsigned char bottom_right_color, mixed_color; + float bottom_weight = y / (float)(border_len - 1); + float top_weight = 1 - bottom_weight; + float right_weight = x / (float)(border_len - 1); + float left_weight = 1 - right_weight; + + sq_point top_left_source = { + top_border[x].x + left_border[y].x - left_border[0].x, + top_border[x].y + left_border[y].y - left_border[0].y + }; + + sq_point bottom_right_source = { + bottom_border[x].x + right_border[y].x - + right_border[border_len - 1].x, + bottom_border[x].y + right_border[y].y - + right_border[border_len - 1].y + }; + + const unsigned char *data = img->data; + unsigned sample_x = top_left_source.x; + unsigned sample_y = top_left_source.y; + unsigned char top_left_color = + data[sample_y * img->width + sample_x]; + sample_x = bottom_right_source.x; + sample_y = bottom_right_source.y; + bottom_right_color = data[sample_y * img->width + sample_x]; + + mixed_color = + ((top_weight + left_weight) * top_left_color + + (bottom_weight + right_weight) * bottom_right_color) / + 2; + + if (is_black_color(mixed_color)) + buf[idx / 8] |= 1 << 7 - idx % 8; + idx++; + } + } + error = sq_extract_text(iscn, buf, byte_len); + free(buf); + +free_borders: + free(top_border); + free(left_border); + free(right_border); + free(bottom_border); + return error ? 1 : 0; +} |