Merge branch 'master' of https://github.com/dlbeer/quirc
This commit is contained in:
commit
c266adbc11
14 changed files with 627 additions and 69 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -9,3 +9,4 @@ libquirc.so*
|
|||
.*.swp
|
||||
*~
|
||||
.DS_Store
|
||||
.idea
|
3
LICENSE
3
LICENSE
|
@ -1,6 +1,9 @@
|
|||
quirc -- QR-code recognition library
|
||||
Copyright (C) 2010-2012 Daniel Beer <dlbeer@gmail.com>
|
||||
|
||||
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
|
||||
|
|
25
Makefile
25
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
|
||||
|
|
58
README.md
58
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 <<dlbeer@gmail.com>>
|
||||
|
|
263
demo/demo_opencv.cxx
Normal file
263
demo/demo_opencv.cxx
Normal 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();
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
30
lib/decode.c
30
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));
|
||||
}
|
||||
|
|
223
lib/identify.c
223
lib/identify.c
|
@ -16,6 +16,7 @@
|
|||
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#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;
|
||||
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
|
@ -656,8 +767,11 @@ static int measure_timing_pattern(struct quirc *q, int index)
|
|||
/* Choose the nearest allowable grid size */
|
||||
size = scan * 2 + 13;
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1021,7 +1135,7 @@ static void test_neighbours(struct quirc *q, int i,
|
|||
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];
|
||||
int j;
|
||||
|
@ -1135,11 +1249,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++;
|
||||
}
|
||||
}
|
||||
|
|
35
lib/quirc.c
35
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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -17,8 +17,13 @@
|
|||
#ifndef QUIRC_INTERNAL_H_
|
||||
#define QUIRC_INTERNAL_H_
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "quirc.h"
|
||||
|
||||
#define QUIRC_ASSERT(a) assert(a)
|
||||
|
||||
#define QUIRC_PIXEL_WHITE 0
|
||||
#define QUIRC_PIXEL_BLACK 1
|
||||
#define QUIRC_PIXEL_REGION 2
|
||||
|
@ -28,7 +33,6 @@
|
|||
#endif
|
||||
#define QUIRC_MAX_CAPSTONES 32
|
||||
#define QUIRC_MAX_GRIDS 8
|
||||
|
||||
#define QUIRC_PERSPECTIVE_PARAMS 8
|
||||
|
||||
#if QUIRC_MAX_REGIONS < UINT8_MAX
|
||||
|
@ -76,6 +80,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;
|
||||
|
@ -90,6 +101,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;
|
||||
};
|
||||
|
||||
/************************************************************************
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -125,9 +125,16 @@ 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);
|
||||
info->total_time += MS(tp) - total_start;
|
||||
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue