diff --git a/.gitignore b/.gitignore index a7494bc..54855db 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ libquirc.so* .*.swp *~ .DS_Store +.idea \ No newline at end of file diff --git a/LICENSE b/LICENSE index d47c026..2db5a8f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,9 @@ quirc -- QR-code recognition library Copyright (C) 2010-2012 Daniel Beer +ISC License +=========== + Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all diff --git a/Makefile b/Makefile index 6f5c2ec..2fc4a14 100644 --- a/Makefile +++ b/Makefile @@ -30,10 +30,15 @@ LIB_OBJ = \ DEMO_OBJ = \ demo/camera.o \ demo/mjpeg.o \ - demo/convert.o \ + demo/convert.o +DEMO_UTIL_OBJ = \ demo/dthash.o \ demo/demoutil.o +OPENCV_CFLAGS != pkg-config --cflags opencv4 +OPENCV_LIBS != pkg-config --libs opencv4 +QUIRC_CXXFLAGS = $(QUIRC_CFLAGS) $(OPENCV_CFLAGS) --std=c++17 + all: libquirc.so qrtest inspect quirc-demo quirc-scanner qrtest: tests/dbgutil.o tests/qrtest.o libquirc.a @@ -42,11 +47,14 @@ qrtest: tests/dbgutil.o tests/qrtest.o libquirc.a inspect: tests/dbgutil.o tests/inspect.o libquirc.a $(CC) -o $@ tests/dbgutil.o tests/inspect.o libquirc.a $(LDFLAGS) -lm -ljpeg -lpng $(SDL_LIBS) -lSDL_gfx -quirc-demo: $(DEMO_OBJ) demo/demo.o libquirc.a - $(CC) -o $@ $(DEMO_OBJ) demo/demo.o libquirc.a $(LDFLAGS) -lm -ljpeg $(SDL_LIBS) -lSDL_gfx +quirc-demo: $(DEMO_OBJ) $(DEMO_UTIL_OBJ) demo/demo.o libquirc.a + $(CC) -o $@ $(DEMO_OBJ) $(DEMO_UTIL_OBJ) demo/demo.o libquirc.a $(LDFLAGS) -lm -ljpeg $(SDL_LIBS) -lSDL_gfx -quirc-scanner: $(DEMO_OBJ) demo/scanner.o libquirc.a - $(CC) -o $@ $(DEMO_OBJ) demo/scanner.o libquirc.a $(LDFLAGS) -lm -ljpeg +quirc-demo-opencv: $(DEMO_UTIL_OBJ) demo/demo_opencv.o libquirc.a + $(CXX) -o $@ $(DEMO_UTIL_OBJ) demo/demo_opencv.o libquirc.a $(LDFLAGS) -lm $(OPENCV_LIBS) + +quirc-scanner: $(DEMO_OBJ) $(DEMO_UTIL_OBJ) demo/scanner.o libquirc.a + $(CC) -o $@ $(DEMO_OBJ) $(DEMO_UTIL_OBJ) demo/scanner.o libquirc.a $(LDFLAGS) -lm -ljpeg libquirc.a: $(LIB_OBJ) rm -f $@ @@ -62,12 +70,17 @@ libquirc.so.$(LIB_VERSION): $(LIB_OBJ) .c.o: $(CC) $(QUIRC_CFLAGS) -o $@ -c $< +.SUFFIXES: .cxx +.cxx.o: + $(CXX) $(QUIRC_CXXFLAGS) -o $@ -c $< + install: libquirc.a libquirc.so.$(LIB_VERSION) quirc-demo quirc-scanner install -o root -g root -m 0644 lib/quirc.h $(DESTDIR)$(PREFIX)/include install -o root -g root -m 0644 libquirc.a $(DESTDIR)$(PREFIX)/lib install -o root -g root -m 0755 libquirc.so.$(LIB_VERSION) \ $(DESTDIR)$(PREFIX)/lib install -o root -g root -m 0755 quirc-demo $(DESTDIR)$(PREFIX)/bin + # install -o root -g root -m 0755 quirc-demo-opencv $(DESTDIR)$(PREFIX)/bin install -o root -g root -m 0755 quirc-scanner $(DESTDIR)$(PREFIX)/bin uninstall: @@ -75,6 +88,7 @@ uninstall: rm -f $(DESTDIR)$(PREFIX)/lib/libquirc.so.$(LIB_VERSION) rm -f $(DESTDIR)$(PREFIX)/lib/libquirc.a rm -f $(DESTDIR)$(PREFIX)/bin/quirc-demo + rm -f $(DESTDIR)$(PREFIX)/bin/quirc-demo-opencv rm -f $(DESTDIR)$(PREFIX)/bin/quirc-scanner clean: @@ -85,4 +99,5 @@ clean: rm -f qrtest rm -f inspect rm -f quirc-demo + rm -f quirc-demo-opencv rm -f quirc-scanner diff --git a/README.md b/README.md index d70c5c3..c965561 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,7 @@ a good choice for this purpose: The distribution comes with, in addition to the library, several test programs. While the core library is very portable, these programs have some additional -dependencies. All of them require libjpeg, and two (`quirc-demo` and `inspect`) -require SDL. The camera demos use Linux-specific APIs: +dependencies as documented below. ### quirc-demo @@ -38,12 +37,23 @@ video stream is displayed on screen as it's received, and any QR codes recognised are highlighted in the image, with the decoded information both displayed on the image and printed on stdout. +This requires: libjpeg, libpng, SDL, V4L2 + +### quirc-demo-opencv + +A demo similar to `quirc-demo`. +But this version uses OpenCV instead of other libraries. + +This requires: OpenCV + ### quirc-scanner This program turns your camera into a barcode scanner. It's almost the same as the `demo` application, but it doesn't display the video stream, and thus doesn't require a graphical display. +This requires: libjpeg, V4L2 + ### qrtest This test is used to evaluate the performance of library. Given a directory @@ -51,12 +61,38 @@ tree containing a bunch of JPEG images, it will attempt to locate and decode QR codes in each image. Speed and success statistics are collected and printed on stdout. +This requires: libjpeg, libpng + ### inspect This test is used for debugging. Given a single JPEG image, it will display a diagram showing the internal state of the decoder as well as printing additional information on stdout. +This requires: libjpeg, libpng, SDL + +Build-time requirements +----------------------- + +### make + +While we are trying to keep our makefiles portable, +it might be incompatible with some versions of make. + +#### GNU make + +Version 4.x and later works. We recommend to use it. + +Version prior to 4.0 doesn't work because it doesn't support `!=`. + +*Note*: macOS's default version of make is GNU make 3.81 as of writing this. + +#### BSD make + +It also works. +You might need to specify the `-r` make option because some of +the default macros like CFLAGS from sys.mk can cause unintended effects. + Installation ------------ To build the library and associated demos/tests, type `make`. If you need to @@ -75,6 +111,7 @@ are unable to build everything: * inspect * quirc-scanner * quirc-demo +* quirc-demo-opencv Library use ----------- @@ -174,6 +211,23 @@ for (i = 0; i < num_codes; i++) { `quirc_code` and `quirc_data` are flat structures which don't need to be initialized or freed after use. +In case you also need to support horizontally flipped QR-codes (mirrored +images according to ISO 18004:2015, pages 6 and 62), you can make a second +decode attempt with the flipped image data whenever you get an ECC failure: + +```C + err = quirc_decode(&code, &data); + if (err == QUIRC_ERROR_DATA_ECC) { + quirc_flip(&code); + err = quirc_decode(&code, &data); + } + + if (err) + printf("DECODE FAILED: %s\n", quirc_strerror(err)); + else + printf("Data: %s\n", data.payload); +``` + Copyright --------- Copyright (C) 2010-2012 Daniel Beer <> diff --git a/demo/demo_opencv.cxx b/demo/demo_opencv.cxx new file mode 100644 index 0000000..d6dbe8b --- /dev/null +++ b/demo/demo_opencv.cxx @@ -0,0 +1,263 @@ +/* quirc -- QR-code recognition library + * Copyright (C) 2010-2012 Daniel Beer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace cv; + +#include "camera.h" +#include "convert.h" +#include "dthash.h" +#include "demoutil.h" + +/* Collected command-line arguments */ +static int want_frame_rate = 0; +static int want_verbose = 0; +static int printer_timeout = 2; + +static const int font = FONT_HERSHEY_PLAIN; +static const int thickness = 2; +static const double font_scale = 1.5; +static Scalar blue = Scalar(255, 0, 8); + +static void fat_text(Mat &screen, int x, int y, const char *text) +{ + putText(screen, text, Point(x, y), font, font_scale, blue, thickness); +} + +static void fat_text_cent(Mat &screen, int x, int y, const char *text) +{ + int baseline; + + Size size = getTextSize(text, font, font_scale, thickness, &baseline); + putText(screen, text, Point(x - size.width / 2, y - size.height /2), + font, font_scale, blue, thickness); +} + +static void draw_qr(Mat &screen, struct quirc *q, struct dthash *dt) +{ + int count = quirc_count(q); + int i; + + for (i = 0; i < count; i++) { + struct quirc_code code; + struct quirc_data data; + quirc_decode_error_t err; + int j; + int xc = 0; + int yc = 0; + char buf[128]; + + quirc_extract(q, i, &code); + + for (j = 0; j < 4; j++) { + struct quirc_point *a = &code.corners[j]; + struct quirc_point *b = &code.corners[(j + 1) % 4]; + + xc += a->x; + yc += a->y; + line(screen, Point(a->x, a->y), Point(b->x, b->y), blue, 8); + } + + xc /= 4; + yc /= 4; + + if (want_verbose) { + snprintf(buf, sizeof(buf), "Code size: %d cells", + code.size); + fat_text_cent(screen, xc, yc - 20, buf); + } + + err = quirc_decode(&code, &data); + + if (err) { + if (want_verbose) + fat_text_cent(screen, xc, yc, quirc_strerror(err)); + } else { + fat_text_cent(screen, xc, yc, (const char *)data.payload); + print_data(&data, dt, want_verbose); + + if (want_verbose) { + snprintf(buf, sizeof(buf), + "Ver: %d, ECC: %c, Mask: %d, Type: %d", + data.version, "MLHQ"[data.ecc_level], + data.mask, data.data_type); + fat_text_cent(screen, xc, yc + 20, buf); + } + } + } +} + +static int main_loop(VideoCapture &cap, struct quirc *q) +{ + time_t last_rate = 0; + int frame_count = 0; + char rate_text[64]; + struct dthash dt; + + rate_text[0] = 0; + dthash_init(&dt, printer_timeout); + + Mat frame; + for (;;) { + time_t now = time(NULL); + + cap.read(frame); + if (frame.empty()) { + perror("empty frame"); + return 0; + } + + int w; + int h; + uint8_t *buf = quirc_begin(q, &w, &h); + + /* convert frame into buf */ + assert(frame.cols == w); + assert(frame.rows == h); + Mat gray; + cvtColor(frame, gray, COLOR_BGR2GRAY, 0); + for (int y = 0; y < gray.rows; y++) { + for (int x = 0; x < gray.cols; x++) { + buf[(y * w + x)] = gray.at(y, x); + } + } + + quirc_end(q); + + draw_qr(frame, q, &dt); + if (want_frame_rate) + fat_text(frame, 20, 20, rate_text); + + imshow("quirc-demo-opencv", frame); + waitKey(5); + + if (now != last_rate) { + snprintf(rate_text, sizeof(rate_text), + "Frame rate: %d fps", frame_count); + frame_count = 0; + last_rate = now; + } + + frame_count++; + } +} + +static int run_demo(void) +{ + struct quirc *qr; + VideoCapture cap(0); + unsigned int width; + unsigned int height; + + if (!cap.isOpened()) { + perror("camera_open"); + goto fail_qr; + } + + width = cap.get(CAP_PROP_FRAME_WIDTH); + height = cap.get(CAP_PROP_FRAME_HEIGHT); + + qr = quirc_new(); + if (!qr) { + perror("couldn't allocate QR decoder"); + goto fail_qr; + } + + if (quirc_resize(qr, width, height) < 0) { + perror("couldn't allocate QR buffer"); + goto fail_qr_resize; + } + + if (main_loop(cap, qr) < 0) { + goto fail_main_loop; + } + + quirc_destroy(qr); + + return 0; + +fail_main_loop: +fail_qr_resize: + quirc_destroy(qr); +fail_qr: + + return -1; +} + +static void usage(const char *progname) +{ + printf("Usage: %s [options]\n\n" +"Valid options are:\n\n" +" -f Show frame rate on screen.\n" +" -v Show extra data for detected codes.\n" +" -p Set printer timeout (seconds).\n" +" --help Show this information.\n" +" --version Show library version information.\n", + progname); +} + +int main(int argc, char **argv) +{ + static const struct option longopts[] = { + {"help", 0, 0, 'H'}, + {"version", 0, 0, 'V'}, + {NULL, 0, 0, 0} + }; + int opt; + + printf("quirc demo with OpenCV\n"); + printf("Copyright (C) 2010-2014 Daniel Beer \n"); + printf("\n"); + + while ((opt = getopt_long(argc, argv, "fvg:p:", + longopts, NULL)) >= 0) + switch (opt) { + case 'V': + printf("Library version: %s\n", quirc_version()); + return 0; + + case 'H': + usage(argv[0]); + return 0; + + case 'v': + want_verbose = 1; + break; + + case 'f': + want_frame_rate = 1; + break; + + case 'p': + printer_timeout = atoi(optarg); + break; + + case '?': + fprintf(stderr, "Try --help for usage information\n"); + return -1; + } + + return run_demo(); +} diff --git a/demo/demoutil.h b/demo/demoutil.h index b6f7dc9..888eddd 100644 --- a/demo/demoutil.h +++ b/demo/demoutil.h @@ -20,6 +20,10 @@ #include "dthash.h" #include "quirc.h" +#ifdef __cplusplus +extern "C" { +#endif + /* Check if we've seen the given code, and if not, print it on stdout. * Include version info if requested. */ @@ -31,4 +35,8 @@ void print_data(const struct quirc_data *data, struct dthash *dt, */ int parse_size(const char *text, int *video_width, int *video_height); +#ifdef __cplusplus +} +#endif + #endif diff --git a/demo/dthash.h b/demo/dthash.h index 661f691..431db1e 100644 --- a/demo/dthash.h +++ b/demo/dthash.h @@ -40,6 +40,10 @@ struct dthash { int timeout; }; +#ifdef __cplusplus +extern "C" { +#endif + /* Initialise a detector hash with the given timeout. */ void dthash_init(struct dthash *d, int timeout); @@ -50,4 +54,8 @@ void dthash_init(struct dthash *d, int timeout); */ int dthash_seen(struct dthash *d, const struct quirc_data *data); +#ifdef __cplusplus +} +#endif + #endif diff --git a/lib/decode.c b/lib/decode.c index 05d3112..1914096 100644 --- a/lib/decode.c +++ b/lib/decode.c @@ -399,7 +399,7 @@ static quirc_decode_error_t correct_format(uint16_t *f_ret) */ struct datastream { - uint8_t raw[QUIRC_MAX_PAYLOAD]; + uint8_t *raw; int data_bits; int ptr; @@ -409,7 +409,6 @@ struct datastream { static inline int grid_bit(const struct quirc_code *code, int x, int y) { int p = y * code->size + x; - return (code->cell_bitmap[p >> 3] >> (p & 7)) & 1; } @@ -906,14 +905,41 @@ quirc_decode_error_t quirc_decode(const struct quirc_code *code, if (err) return err; + /* + * Borrow data->payload to store the raw bits. + * It's only used during read_data + coddestream_ecc below. + * + * This trick saves the size of struct datastream, which we allocate + * on the stack. + */ + + ds.raw = data->payload; + read_data(code, data, &ds); err = codestream_ecc(data, &ds); if (err) return err; + ds.raw = NULL; /* We've done with this buffer. */ + err = decode_payload(data, &ds); if (err) return err; return QUIRC_SUCCESS; } + +void quirc_flip(struct quirc_code *code) +{ + struct quirc_code flipped = {0}; + unsigned int offset = 0; + for (int y = 0; y < code->size; y++) { + for (int x = 0; x < code->size; x++) { + if (grid_bit(code, y, x)) { + flipped.cell_bitmap[offset >> 3u] |= (1u << (offset & 7u)); + } + offset++; + } + } + memcpy(&code->cell_bitmap, &flipped.cell_bitmap, sizeof(flipped.cell_bitmap)); +} diff --git a/lib/identify.c b/lib/identify.c index 88b7948..098e36f 100644 --- a/lib/identify.c +++ b/lib/identify.c @@ -16,6 +16,7 @@ #include #include +#include #include #include #include "quirc_internal.h" @@ -122,21 +123,23 @@ 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, +static void flood_fill_line(struct quirc *q, int x, int y, + int from, int to, span_func_t func, void *user_data, - int depth) + int *leftp, int *rightp) { - int left = x; - int right = x; + quirc_pixel_t *row; + int left; + int right; int i; - quirc_pixel_t *row = q->pixels + y * q->w; - if (depth >= FLOOD_FILL_MAX_DEPTH) - return; + row = q->pixels + y * q->w; + QUIRC_ASSERT(row[x] == from); + + left = x; + right = x; while (left > 0 && row[left - 1] == from) left--; @@ -148,26 +151,132 @@ static void flood_fill_seed(struct quirc *q, int x, int y, int from, int to, for (i = left; i <= right; i++) row[i] = to; + /* Return the processed range */ + *leftp = left; + *rightp = right; + if (func) func(user_data, y, left, right); +} - /* Seed new flood-fills */ - if (y > 0) { - row = q->pixels + (y - 1) * q->w; +static struct quirc_flood_fill_vars *flood_fill_call_next( + struct quirc *q, + quirc_pixel_t *row, + int from, int to, + span_func_t func, void *user_data, + struct quirc_flood_fill_vars *vars, + int direction) +{ + int *leftp; - 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 (direction < 0) { + leftp = &vars->left_up; + } else { + leftp = &vars->left_down; } - if (y < q->h - 1) { - row = q->pixels + (y + 1) * q->w; + while (*leftp <= vars->right) { + if (row[*leftp] == from) { + struct quirc_flood_fill_vars *next_vars; + int next_left; - for (i = left; i <= right; i++) - if (row[i] == from) - flood_fill_seed(q, i, y + 1, from, to, - func, user_data, depth + 1); + /* Set up the next context */ + next_vars = vars + 1; + next_vars->y = vars->y + direction; + + /* Fill the extent */ + flood_fill_line(q, + *leftp, + next_vars->y, + from, to, + func, user_data, + &next_left, + &next_vars->right); + next_vars->left_down = next_left; + next_vars->left_up = next_left; + + return next_vars; + } + (*leftp)++; + } + return NULL; +} + +static void flood_fill_seed(struct quirc *q, + int x0, int y0, + int from, int to, + span_func_t func, void *user_data) +{ + 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]; + + QUIRC_ASSERT(from != to); + QUIRC_ASSERT(q->pixels[y0 * q->w + x0] == from); + + struct quirc_flood_fill_vars *next_vars; + int next_left; + + /* Set up the first context */ + next_vars = stack; + next_vars->y = y0; + + /* Fill the extent */ + flood_fill_line(q, x0, next_vars->y, from, to, + func, user_data, + &next_left, &next_vars->right); + next_vars->left_down = next_left; + next_vars->left_up = next_left; + + while (true) { + struct quirc_flood_fill_vars * const vars = next_vars; + quirc_pixel_t *row; + + if (vars == last_vars) { + /* + * "Stack overflow". + * Just stop and return. + * This can be caused by very complex shapes in + * the image, which is not likely a part of + * a valid QR code anyway. + */ + break; + } + + /* Seed new flood-fills */ + if (vars->y > 0) { + row = q->pixels + (vars->y - 1) * q->w; + + next_vars = flood_fill_call_next(q, row, + from, to, + func, user_data, + vars, -1); + if (next_vars != NULL) { + continue; + } + } + + if (vars->y < q->h - 1) { + row = q->pixels + (vars->y + 1) * q->w; + + next_vars = flood_fill_call_next(q, row, + from, to, + func, user_data, + vars, 1); + if (next_vars != NULL) { + continue; + } + } + + if (vars > stack) { + /* Restore the previous context */ + next_vars = vars - 1; + continue; + } + + /* We've done. */ + break; } } @@ -177,28 +286,28 @@ static void flood_fill_seed(struct quirc *q, int x, int y, int from, int to, static uint8_t otsu(const struct quirc *q) { - int numPixels = q->w * q->h; + unsigned int numPixels = q->w * q->h; // Calculate histogram unsigned int histogram[UINT8_MAX + 1]; (void)memset(histogram, 0, sizeof(histogram)); uint8_t* ptr = q->image; - int length = numPixels; + unsigned int length = numPixels; while (length--) { uint8_t value = *ptr++; histogram[value]++; } // Calculate weighted sum of histogram values - unsigned int sum = 0; + double sum = 0; unsigned int i = 0; for (i = 0; i <= UINT8_MAX; ++i) { sum += i * histogram[i]; } // Compute threshold - int sumB = 0; - int q1 = 0; + double sumB = 0; + unsigned int q1 = 0; double max = 0; uint8_t threshold = 0; for (i = 0; i <= UINT8_MAX; ++i) { @@ -208,13 +317,13 @@ static uint8_t otsu(const struct quirc *q) continue; // Weighted foreground - const int q2 = numPixels - q1; + const unsigned int q2 = numPixels - q1; if (q2 == 0) break; sumB += i * histogram[i]; - const double m1 = (double)sumB / q1; - const double m2 = ((double)sum - sumB) / q2; + const double m1 = sumB / q1; + const double m2 = (sum - sumB) / q2; const double m1m2 = m1 - m2; const double variance = m1m2 * m1m2 * q1 * q2; if (variance >= max) { @@ -260,7 +369,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 +439,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 +457,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) @@ -380,7 +489,8 @@ static void record_capstone(struct quirc *q, int ring, int stone) perspective_map(capstone->c, 3.5, 3.5, &capstone->center); } -static void test_capstone(struct quirc *q, int x, int y, int *pb) +static void test_capstone(struct quirc *q, unsigned int x, unsigned int y, + unsigned int *pb) { int ring_right = region_code(q, x - pb[4], y); int stone = region_code(q, x - pb[4] - pb[3] - pb[2], y); @@ -389,7 +499,7 @@ static void test_capstone(struct quirc *q, int x, int y, int *pb) y); struct quirc_region *stone_reg; struct quirc_region *ring_reg; - int ratio; + unsigned int ratio; if (ring_left < 0 || ring_right < 0 || stone < 0) return; @@ -417,14 +527,14 @@ static void test_capstone(struct quirc *q, int x, int y, int *pb) record_capstone(q, ring_left, stone); } -static void finder_scan(struct quirc *q, int y) +static void finder_scan(struct quirc *q, unsigned int y) { quirc_pixel_t *row = q->pixels + y * q->w; - int x; + unsigned int x; int last_color = 0; - int run_length = 0; - int run_count = 0; - int pb[5]; + unsigned int run_length = 0; + unsigned int run_count = 0; + unsigned int pb[5]; memset(pb, 0, sizeof(pb)); for (x = 0; x < q->w; x++) { @@ -437,17 +547,18 @@ static void finder_scan(struct quirc *q, int y) run_count++; if (!color && run_count >= 5) { - static int check[5] = {1, 1, 3, 1, 1}; - int avg, err; - int i; + const int scale = 16; + static const unsigned int check[5] = {1, 1, 3, 1, 1}; + unsigned int avg, err; + unsigned int i; int ok = 1; - avg = (pb[0] + pb[1] + pb[3] + pb[4]) / 4; + avg = (pb[0] + pb[1] + pb[3] + pb[4]) * scale / 4; err = avg * 3 / 4; for (i = 0; i < 5; i++) - if (pb[i] < check[i] * avg - err || - pb[i] > check[i] * avg + err) + if (pb[i] * scale < check[i] * avg - err || + pb[i] * scale > check[i] * avg + err) ok = 0; if (ok) @@ -862,10 +973,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); } } @@ -907,7 +1018,7 @@ static void test_neighbours(struct quirc *q, int i, } } -static void test_grouping(struct quirc *q, int i) +static void test_grouping(struct quirc *q, unsigned int i) { struct quirc_capstone *c1 = &q->capstones[i]; int j; @@ -1018,11 +1129,10 @@ void quirc_extract(const struct quirc *q, int index, for (y = 0; y < qr->grid_size; y++) { int x; - for (x = 0; x < qr->grid_size; x++) { - if (read_cell(q, index, x, y) > 0) + if (read_cell(q, index, x, y) > 0) { code->cell_bitmap[i >> 3] |= (1 << (i & 7)); - + } i++; } } diff --git a/lib/quirc.c b/lib/quirc.c index bbe1fe9..3cf75b9 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,9 @@ int quirc_resize(struct quirc *q, int w, int h) { uint8_t *image = NULL; quirc_pixel_t *pixels = NULL; + size_t num_vars; + size_t vars_byte_size; + struct quirc_flood_fill_vars *vars = NULL; /* * XXX: w and h should be size_t (or at least unsigned) as negatives @@ -86,6 +90,33 @@ 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. + */ + + if ((size_t)h * 2 / 2 != h) { + goto fail; /* size_t overflow */ + } + num_vars = (size_t)h * 2 / 3; + if (num_vars == 0) { + num_vars = 1; + } + + vars_byte_size = sizeof(*vars) * num_vars; + if (vars_byte_size / sizeof(*vars) != num_vars) { + goto fail; /* size_t overflow */ + } + vars = malloc(vars_byte_size); + if (!vars) + goto fail; + /* alloc succeeded, update `q` with the new size and buffers */ q->w = w; q->h = h; @@ -95,12 +126,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.h b/lib/quirc.h index 0e7cb94..d8d250d 100644 --- a/lib/quirc.h +++ b/lib/quirc.h @@ -78,7 +78,9 @@ typedef enum { const char *quirc_strerror(quirc_decode_error_t err); /* Limits on the maximum size of QR-codes and their content. */ -#define QUIRC_MAX_BITMAP 3917 +#define QUIRC_MAX_VERSION 40 +#define QUIRC_MAX_GRID_SIZE (QUIRC_MAX_VERSION * 4 + 17) +#define QUIRC_MAX_BITMAP (((QUIRC_MAX_GRID_SIZE * QUIRC_MAX_GRID_SIZE) + 7) / 8) #define QUIRC_MAX_PAYLOAD 8896 /* QR-code ECC types. */ @@ -166,6 +168,9 @@ void quirc_extract(const struct quirc *q, int index, quirc_decode_error_t quirc_decode(const struct quirc_code *code, struct quirc_data *data); +/* Flip a QR-code according to optional mirror feature of ISO 18004:2015 */ +void quirc_flip(struct quirc_code *code); + #ifdef __cplusplus } #endif diff --git a/lib/quirc_internal.h b/lib/quirc_internal.h index 2d49a6b..37b4e4d 100644 --- a/lib/quirc_internal.h +++ b/lib/quirc_internal.h @@ -17,8 +17,13 @@ #ifndef QUIRC_INTERNAL_H_ #define QUIRC_INTERNAL_H_ +#include +#include + #include "quirc.h" +#define QUIRC_ASSERT(a) assert(a) + #define QUIRC_PIXEL_WHITE 0 #define QUIRC_PIXEL_BLACK 1 #define QUIRC_PIXEL_REGION 2 @@ -74,6 +79,13 @@ struct quirc_grid { double c[QUIRC_PERSPECTIVE_PARAMS]; }; +struct quirc_flood_fill_vars { + int y; + int right; + int left_up; + int left_down; +}; + struct quirc { uint8_t *image; quirc_pixel_t *pixels; @@ -88,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; }; /************************************************************************ diff --git a/tests/inspect.c b/tests/inspect.c index 866ee16..ce5357a 100644 --- a/tests/inspect.c +++ b/tests/inspect.c @@ -34,6 +34,10 @@ static void dump_info(struct quirc *q) quirc_extract(q, i, &code); err = quirc_decode(&code, &data); + if (err == QUIRC_ERROR_DATA_ECC) { + quirc_flip(&code); + err = quirc_decode(&code, &data); + } dump_cells(&code); printf("\n"); diff --git a/tests/qrtest.c b/tests/qrtest.c index 2e50698..1bb178e 100644 --- a/tests/qrtest.c +++ b/tests/qrtest.c @@ -125,8 +125,15 @@ static int scan_file(const char *path, const char *filename, quirc_extract(decoder, i, &code); - if (!quirc_decode(&code, &data)) + quirc_decode_error_t err = quirc_decode(&code, &data); + if (err == QUIRC_ERROR_DATA_ECC) { + quirc_flip(&code); + err = quirc_decode(&code, &data); + } + + if (!err) { info->decode_count++; + } } (void)clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tp); @@ -150,12 +157,14 @@ static int scan_file(const char *path, const char *filename, if (want_verbose) { struct quirc_data data; - quirc_decode_error_t err = - quirc_decode(&code, &data); + quirc_decode_error_t err = quirc_decode(&code, &data); + if (err == QUIRC_ERROR_DATA_ECC) { + quirc_flip(&code); + err = quirc_decode(&code, &data); + } if (err) { - printf(" ERROR: %s\n\n", - quirc_strerror(err)); + printf(" ERROR: %s\n\n", quirc_strerror(err)); } else { printf(" Decode successful:\n"); dump_data(&data);