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
|
.*.swp
|
||||||
*~
|
*~
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.idea
|
3
LICENSE
3
LICENSE
|
@ -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
|
||||||
|
|
25
Makefile
25
Makefile
|
@ -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
|
||||||
|
|
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.
|
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
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 "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
|
||||||
|
|
|
@ -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
|
||||||
|
|
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 {
|
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));
|
||||||
|
}
|
||||||
|
|
223
lib/identify.c
223
lib/identify.c
|
@ -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++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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 */
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
/************************************************************************
|
/************************************************************************
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue