This commit is contained in:
Jaap Haitsma 2021-06-16 23:36:18 -04:00
commit c266adbc11
14 changed files with 627 additions and 69 deletions

1
.gitignore vendored
View file

@ -9,3 +9,4 @@ libquirc.so*
.*.swp .*.swp
*~ *~
.DS_Store .DS_Store
.idea

View file

@ -1,6 +1,9 @@
quirc -- QR-code recognition library quirc -- QR-code recognition library
Copyright (C) 2010-2012 Daniel Beer <dlbeer@gmail.com> Copyright (C) 2010-2012 Daniel Beer <dlbeer@gmail.com>
ISC License
===========
Permission to use, copy, modify, and/or distribute this software for Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all above copyright notice and this permission notice appear in all

View file

@ -30,10 +30,15 @@ LIB_OBJ = \
DEMO_OBJ = \ DEMO_OBJ = \
demo/camera.o \ demo/camera.o \
demo/mjpeg.o \ demo/mjpeg.o \
demo/convert.o \ demo/convert.o
DEMO_UTIL_OBJ = \
demo/dthash.o \ demo/dthash.o \
demo/demoutil.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 all: libquirc.so qrtest inspect quirc-demo quirc-scanner
qrtest: tests/dbgutil.o tests/qrtest.o libquirc.a 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 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 $(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 quirc-demo: $(DEMO_OBJ) $(DEMO_UTIL_OBJ) demo/demo.o libquirc.a
$(CC) -o $@ $(DEMO_OBJ) demo/demo.o libquirc.a $(LDFLAGS) -lm -ljpeg $(SDL_LIBS) -lSDL_gfx $(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 quirc-demo-opencv: $(DEMO_UTIL_OBJ) demo/demo_opencv.o libquirc.a
$(CC) -o $@ $(DEMO_OBJ) demo/scanner.o libquirc.a $(LDFLAGS) -lm -ljpeg $(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) libquirc.a: $(LIB_OBJ)
rm -f $@ rm -f $@
@ -62,12 +70,17 @@ libquirc.so.$(LIB_VERSION): $(LIB_OBJ)
.c.o: .c.o:
$(CC) $(QUIRC_CFLAGS) -o $@ -c $< $(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: 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 lib/quirc.h $(DESTDIR)$(PREFIX)/include
install -o root -g root -m 0644 libquirc.a $(DESTDIR)$(PREFIX)/lib install -o root -g root -m 0644 libquirc.a $(DESTDIR)$(PREFIX)/lib
install -o root -g root -m 0755 libquirc.so.$(LIB_VERSION) \ install -o root -g root -m 0755 libquirc.so.$(LIB_VERSION) \
$(DESTDIR)$(PREFIX)/lib $(DESTDIR)$(PREFIX)/lib
install -o root -g root -m 0755 quirc-demo $(DESTDIR)$(PREFIX)/bin 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 install -o root -g root -m 0755 quirc-scanner $(DESTDIR)$(PREFIX)/bin
uninstall: uninstall:
@ -75,6 +88,7 @@ uninstall:
rm -f $(DESTDIR)$(PREFIX)/lib/libquirc.so.$(LIB_VERSION) rm -f $(DESTDIR)$(PREFIX)/lib/libquirc.so.$(LIB_VERSION)
rm -f $(DESTDIR)$(PREFIX)/lib/libquirc.a rm -f $(DESTDIR)$(PREFIX)/lib/libquirc.a
rm -f $(DESTDIR)$(PREFIX)/bin/quirc-demo rm -f $(DESTDIR)$(PREFIX)/bin/quirc-demo
rm -f $(DESTDIR)$(PREFIX)/bin/quirc-demo-opencv
rm -f $(DESTDIR)$(PREFIX)/bin/quirc-scanner rm -f $(DESTDIR)$(PREFIX)/bin/quirc-scanner
clean: clean:
@ -85,4 +99,5 @@ clean:
rm -f qrtest rm -f qrtest
rm -f inspect rm -f inspect
rm -f quirc-demo rm -f quirc-demo
rm -f quirc-demo-opencv
rm -f quirc-scanner rm -f quirc-scanner

View file

@ -28,8 +28,7 @@ a good choice for this purpose:
The distribution comes with, in addition to the library, several test programs. The distribution comes with, in addition to the library, several test programs.
While the core library is very portable, these programs have some additional While the core library is very portable, these programs have some additional
dependencies. All of them require libjpeg, and two (`quirc-demo` and `inspect`) dependencies as documented below.
require SDL. The camera demos use Linux-specific APIs:
### quirc-demo ### 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 recognised are highlighted in the image, with the decoded information both
displayed on the image and printed on stdout. 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 ### quirc-scanner
This program turns your camera into a barcode scanner. It's almost the same as 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 the `demo` application, but it doesn't display the video stream, and thus
doesn't require a graphical display. doesn't require a graphical display.
This requires: libjpeg, V4L2
### qrtest ### qrtest
This test is used to evaluate the performance of library. Given a directory 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 codes in each image. Speed and success statistics are collected and printed on
stdout. stdout.
This requires: libjpeg, libpng
### inspect ### inspect
This test is used for debugging. Given a single JPEG image, it will display a 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 diagram showing the internal state of the decoder as well as printing
additional information on stdout. 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 Installation
------------ ------------
To build the library and associated demos/tests, type `make`. If you need to To build the library and associated demos/tests, type `make`. If you need to
@ -75,6 +111,7 @@ are unable to build everything:
* inspect * inspect
* quirc-scanner * quirc-scanner
* quirc-demo * quirc-demo
* quirc-demo-opencv
Library use 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 `quirc_code` and `quirc_data` are flat structures which don't need to be
initialized or freed after use. 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
--------- ---------
Copyright (C) 2010-2012 Daniel Beer <<dlbeer@gmail.com>> Copyright (C) 2010-2012 Daniel Beer <<dlbeer@gmail.com>>

263
demo/demo_opencv.cxx Normal file
View file

@ -0,0 +1,263 @@
/* quirc -- QR-code recognition library
* Copyright (C) 2010-2012 Daniel Beer <dlbeer@gmail.com>
*
* 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 <assert.h>
#include <unistd.h>
#include <quirc.h>
#include <time.h>
#include <getopt.h>
#include <opencv2/videoio.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
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<uint8_t>(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 <timeout> 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 <dlbeer@gmail.com>\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();
}

View file

@ -20,6 +20,10 @@
#include "dthash.h" #include "dthash.h"
#include "quirc.h" #include "quirc.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Check if we've seen the given code, and if not, print it on stdout. /* Check if we've seen the given code, and if not, print it on stdout.
* Include version info if requested. * 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); int parse_size(const char *text, int *video_width, int *video_height);
#ifdef __cplusplus
}
#endif
#endif #endif

View file

@ -40,6 +40,10 @@ struct dthash {
int timeout; int timeout;
}; };
#ifdef __cplusplus
extern "C" {
#endif
/* Initialise a detector hash with the given timeout. */ /* Initialise a detector hash with the given timeout. */
void dthash_init(struct dthash *d, int 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); int dthash_seen(struct dthash *d, const struct quirc_data *data);
#ifdef __cplusplus
}
#endif
#endif #endif

View file

@ -399,7 +399,7 @@ static quirc_decode_error_t correct_format(uint16_t *f_ret)
*/ */
struct datastream { struct datastream {
uint8_t raw[QUIRC_MAX_PAYLOAD]; uint8_t *raw;
int data_bits; int data_bits;
int ptr; int ptr;
@ -409,7 +409,6 @@ struct datastream {
static inline int grid_bit(const struct quirc_code *code, int x, int y) static inline int grid_bit(const struct quirc_code *code, int x, int y)
{ {
int p = y * code->size + x; int p = y * code->size + x;
return (code->cell_bitmap[p >> 3] >> (p & 7)) & 1; 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) if (err)
return 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); read_data(code, data, &ds);
err = codestream_ecc(data, &ds); err = codestream_ecc(data, &ds);
if (err) if (err)
return err; return err;
ds.raw = NULL; /* We've done with this buffer. */
err = decode_payload(data, &ds); err = decode_payload(data, &ds);
if (err) if (err)
return err; return err;
return QUIRC_SUCCESS; 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));
}

View file

@ -16,6 +16,7 @@
#include <limits.h> #include <limits.h>
#include <string.h> #include <string.h>
#include <stdbool.h>
#include <stdlib.h> #include <stdlib.h>
#include <math.h> #include <math.h>
#include "quirc_internal.h" #include "quirc_internal.h"
@ -122,21 +123,23 @@ static void perspective_unmap(const double *c,
* Span-based floodfill routine * Span-based floodfill routine
*/ */
#define FLOOD_FILL_MAX_DEPTH 4096
typedef void (*span_func_t)(void *user_data, int y, int left, int right); 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, span_func_t func, void *user_data,
int depth) int *leftp, int *rightp)
{ {
int left = x; quirc_pixel_t *row;
int right = x; int left;
int right;
int i; int i;
quirc_pixel_t *row = q->pixels + y * q->w;
if (depth >= FLOOD_FILL_MAX_DEPTH) row = q->pixels + y * q->w;
return; QUIRC_ASSERT(row[x] == from);
left = x;
right = x;
while (left > 0 && row[left - 1] == from) while (left > 0 && row[left - 1] == from)
left--; 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++) for (i = left; i <= right; i++)
row[i] = to; row[i] = to;
/* Return the processed range */
*leftp = left;
*rightp = right;
if (func) if (func)
func(user_data, y, left, right); func(user_data, y, left, 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 (y < q->h - 1) { static struct quirc_flood_fill_vars *flood_fill_call_next(
row = q->pixels + (y + 1) * q->w; 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 (direction < 0) {
if (row[i] == from) leftp = &vars->left_up;
flood_fill_seed(q, i, y + 1, from, to, } else {
func, user_data, depth + 1); leftp = &vars->left_down;
}
while (*leftp <= vars->right) {
if (row[*leftp] == from) {
struct quirc_flood_fill_vars *next_vars;
int next_left;
/* 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) static uint8_t otsu(const struct quirc *q)
{ {
int numPixels = q->w * q->h; unsigned int numPixels = q->w * q->h;
// Calculate histogram // Calculate histogram
unsigned int histogram[UINT8_MAX + 1]; unsigned int histogram[UINT8_MAX + 1];
(void)memset(histogram, 0, sizeof(histogram)); (void)memset(histogram, 0, sizeof(histogram));
uint8_t* ptr = q->image; uint8_t* ptr = q->image;
int length = numPixels; unsigned int length = numPixels;
while (length--) { while (length--) {
uint8_t value = *ptr++; uint8_t value = *ptr++;
histogram[value]++; histogram[value]++;
} }
// Calculate weighted sum of histogram values // Calculate weighted sum of histogram values
unsigned int sum = 0; double sum = 0;
unsigned int i = 0; unsigned int i = 0;
for (i = 0; i <= UINT8_MAX; ++i) { for (i = 0; i <= UINT8_MAX; ++i) {
sum += i * histogram[i]; sum += i * histogram[i];
} }
// Compute threshold // Compute threshold
int sumB = 0; double sumB = 0;
int q1 = 0; unsigned int q1 = 0;
double max = 0; double max = 0;
uint8_t threshold = 0; uint8_t threshold = 0;
for (i = 0; i <= UINT8_MAX; ++i) { for (i = 0; i <= UINT8_MAX; ++i) {
@ -208,13 +317,13 @@ static uint8_t otsu(const struct quirc *q)
continue; continue;
// Weighted foreground // Weighted foreground
const int q2 = numPixels - q1; const unsigned int q2 = numPixels - q1;
if (q2 == 0) if (q2 == 0)
break; break;
sumB += i * histogram[i]; sumB += i * histogram[i];
const double m1 = (double)sumB / q1; const double m1 = sumB / q1;
const double m2 = ((double)sum - sumB) / q2; const double m2 = (sum - sumB) / q2;
const double m1m2 = m1 - m2; const double m1m2 = m1 - m2;
const double variance = m1m2 * m1m2 * q1 * q2; const double variance = m1m2 * m1m2 * q1 * q2;
if (variance >= max) { if (variance >= max) {
@ -260,7 +369,7 @@ static int region_code(struct quirc *q, int x, int y)
box->seed.y = y; box->seed.y = y;
box->capstone = -1; 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; return region;
} }
@ -330,7 +439,7 @@ static void find_region_corners(struct quirc *q,
psd.scores[0] = -1; psd.scores[0] = -1;
flood_fill_seed(q, region->seed.x, region->seed.y, flood_fill_seed(q, region->seed.x, region->seed.y,
rcode, QUIRC_PIXEL_BLACK, 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.x = psd.corners[0].x - psd.ref.x;
psd.ref.y = psd.corners[0].y - psd.ref.y; 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, flood_fill_seed(q, region->seed.x, region->seed.y,
QUIRC_PIXEL_BLACK, rcode, QUIRC_PIXEL_BLACK, rcode,
find_other_corners, &psd, 0); find_other_corners, &psd);
} }
static void record_capstone(struct quirc *q, int ring, int stone) 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); 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 ring_right = region_code(q, x - pb[4], y);
int stone = region_code(q, x - pb[4] - pb[3] - pb[2], 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); y);
struct quirc_region *stone_reg; struct quirc_region *stone_reg;
struct quirc_region *ring_reg; struct quirc_region *ring_reg;
int ratio; unsigned int ratio;
if (ring_left < 0 || ring_right < 0 || stone < 0) if (ring_left < 0 || ring_right < 0 || stone < 0)
return; return;
@ -417,14 +527,14 @@ static void test_capstone(struct quirc *q, int x, int y, int *pb)
record_capstone(q, ring_left, stone); 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; quirc_pixel_t *row = q->pixels + y * q->w;
int x; unsigned int x;
int last_color = 0; int last_color = 0;
int run_length = 0; unsigned int run_length = 0;
int run_count = 0; unsigned int run_count = 0;
int pb[5]; unsigned int pb[5];
memset(pb, 0, sizeof(pb)); memset(pb, 0, sizeof(pb));
for (x = 0; x < q->w; x++) { for (x = 0; x < q->w; x++) {
@ -437,17 +547,18 @@ static void finder_scan(struct quirc *q, int y)
run_count++; run_count++;
if (!color && run_count >= 5) { if (!color && run_count >= 5) {
static int check[5] = {1, 1, 3, 1, 1}; const int scale = 16;
int avg, err; static const unsigned int check[5] = {1, 1, 3, 1, 1};
int i; unsigned int avg, err;
unsigned int i;
int ok = 1; 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; err = avg * 3 / 4;
for (i = 0; i < 5; i++) for (i = 0; i < 5; i++)
if (pb[i] < check[i] * avg - err || if (pb[i] * scale < check[i] * avg - err ||
pb[i] > check[i] * avg + err) pb[i] * scale > check[i] * avg + err)
ok = 0; ok = 0;
if (ok) if (ok)
@ -656,8 +767,11 @@ static int measure_timing_pattern(struct quirc *q, int index)
/* Choose the nearest allowable grid size */ /* Choose the nearest allowable grid size */
size = scan * 2 + 13; size = scan * 2 + 13;
ver = (size - 15) / 4; ver = (size - 15) / 4;
qr->grid_size = ver * 4 + 17; if (ver > QUIRC_MAX_VERSION) {
return -1;
}
qr->grid_size = ver * 4 + 17;
return 0; return 0;
} }
@ -961,10 +1075,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, flood_fill_seed(q, reg->seed.x, reg->seed.y,
qr->align_region, QUIRC_PIXEL_BLACK, qr->align_region, QUIRC_PIXEL_BLACK,
NULL, NULL, 0); NULL, NULL);
flood_fill_seed(q, reg->seed.x, reg->seed.y, flood_fill_seed(q, reg->seed.x, reg->seed.y,
QUIRC_PIXEL_BLACK, qr->align_region, QUIRC_PIXEL_BLACK, qr->align_region,
find_leftmost_to_line, &psd, 0); find_leftmost_to_line, &psd);
} }
} }
@ -1021,7 +1135,7 @@ static void test_neighbours(struct quirc *q, int i,
record_qr_grid(q, best_h, i, best_v); record_qr_grid(q, best_h, i, best_v);
} }
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]; struct quirc_capstone *c1 = &q->capstones[i];
int j; int j;
@ -1135,11 +1249,10 @@ void quirc_extract(const struct quirc *q, int index,
for (y = 0; y < qr->grid_size; y++) { for (y = 0; y < qr->grid_size; y++) {
int x; int x;
for (x = 0; x < qr->grid_size; 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)); code->cell_bitmap[i >> 3] |= (1 << (i & 7));
}
i++; i++;
} }
} }

View file

@ -41,6 +41,7 @@ void quirc_destroy(struct quirc *q)
same size, so we need to be careful here to avoid a double free */ same size, so we need to be careful here to avoid a double free */
if (!QUIRC_PIXEL_ALIAS_IMAGE) if (!QUIRC_PIXEL_ALIAS_IMAGE)
free(q->pixels); free(q->pixels);
free(q->flood_fill_vars);
free(q); free(q);
} }
@ -48,6 +49,9 @@ int quirc_resize(struct quirc *q, int w, int h)
{ {
uint8_t *image = NULL; uint8_t *image = NULL;
quirc_pixel_t *pixels = 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 * 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; 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 */ /* alloc succeeded, update `q` with the new size and buffers */
q->w = w; q->w = w;
q->h = h; q->h = h;
@ -95,12 +126,16 @@ int quirc_resize(struct quirc *q, int w, int h)
free(q->pixels); free(q->pixels);
q->pixels = pixels; q->pixels = pixels;
} }
free(q->flood_fill_vars);
q->flood_fill_vars = vars;
q->num_flood_fill_vars = num_vars;
return 0; return 0;
/* NOTREACHED */ /* NOTREACHED */
fail: fail:
free(image); free(image);
free(pixels); free(pixels);
free(vars);
return -1; return -1;
} }

View file

@ -78,7 +78,9 @@ typedef enum {
const char *quirc_strerror(quirc_decode_error_t err); const char *quirc_strerror(quirc_decode_error_t err);
/* Limits on the maximum size of QR-codes and their content. */ /* 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 #define QUIRC_MAX_PAYLOAD 8896
/* QR-code ECC types. */ /* 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, quirc_decode_error_t quirc_decode(const struct quirc_code *code,
struct quirc_data *data); 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 #ifdef __cplusplus
} }
#endif #endif

View file

@ -17,8 +17,13 @@
#ifndef QUIRC_INTERNAL_H_ #ifndef QUIRC_INTERNAL_H_
#define QUIRC_INTERNAL_H_ #define QUIRC_INTERNAL_H_
#include <assert.h>
#include <stdlib.h>
#include "quirc.h" #include "quirc.h"
#define QUIRC_ASSERT(a) assert(a)
#define QUIRC_PIXEL_WHITE 0 #define QUIRC_PIXEL_WHITE 0
#define QUIRC_PIXEL_BLACK 1 #define QUIRC_PIXEL_BLACK 1
#define QUIRC_PIXEL_REGION 2 #define QUIRC_PIXEL_REGION 2
@ -28,7 +33,6 @@
#endif #endif
#define QUIRC_MAX_CAPSTONES 32 #define QUIRC_MAX_CAPSTONES 32
#define QUIRC_MAX_GRIDS 8 #define QUIRC_MAX_GRIDS 8
#define QUIRC_PERSPECTIVE_PARAMS 8 #define QUIRC_PERSPECTIVE_PARAMS 8
#if QUIRC_MAX_REGIONS < UINT8_MAX #if QUIRC_MAX_REGIONS < UINT8_MAX
@ -76,6 +80,13 @@ struct quirc_grid {
double c[QUIRC_PERSPECTIVE_PARAMS]; double c[QUIRC_PERSPECTIVE_PARAMS];
}; };
struct quirc_flood_fill_vars {
int y;
int right;
int left_up;
int left_down;
};
struct quirc { struct quirc {
uint8_t *image; uint8_t *image;
quirc_pixel_t *pixels; quirc_pixel_t *pixels;
@ -90,6 +101,9 @@ struct quirc {
int num_grids; int num_grids;
struct quirc_grid grids[QUIRC_MAX_GRIDS]; struct quirc_grid grids[QUIRC_MAX_GRIDS];
size_t num_flood_fill_vars;
struct quirc_flood_fill_vars *flood_fill_vars;
}; };
/************************************************************************ /************************************************************************

View file

@ -34,6 +34,10 @@ static void dump_info(struct quirc *q)
quirc_extract(q, i, &code); quirc_extract(q, i, &code);
err = quirc_decode(&code, &data); err = quirc_decode(&code, &data);
if (err == QUIRC_ERROR_DATA_ECC) {
quirc_flip(&code);
err = quirc_decode(&code, &data);
}
dump_cells(&code); dump_cells(&code);
printf("\n"); printf("\n");

View file

@ -125,9 +125,16 @@ static int scan_file(const char *path, const char *filename,
quirc_extract(decoder, i, &code); 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++; info->decode_count++;
} }
}
(void)clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tp); (void)clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tp);
info->total_time += MS(tp) - total_start; info->total_time += MS(tp) - total_start;
@ -150,12 +157,14 @@ static int scan_file(const char *path, const char *filename,
if (want_verbose) { if (want_verbose) {
struct quirc_data data; struct quirc_data data;
quirc_decode_error_t err = quirc_decode_error_t err = quirc_decode(&code, &data);
quirc_decode(&code, &data); if (err == QUIRC_ERROR_DATA_ECC) {
quirc_flip(&code);
err = quirc_decode(&code, &data);
}
if (err) { if (err) {
printf(" ERROR: %s\n\n", printf(" ERROR: %s\n\n", quirc_strerror(err));
quirc_strerror(err));
} else { } else {
printf(" Decode successful:\n"); printf(" Decode successful:\n");
dump_data(&data); dump_data(&data);