From d2470c2ee6724609e77366c3796bb770b49805f5 Mon Sep 17 00:00:00 2001 From: "Rustem Gimadutdinov (rgimad)" Date: Sun, 7 Feb 2021 17:35:37 +0000 Subject: [PATCH] upload new program QR Tool git-svn-id: svn://kolibrios.org@8585 a494cfbc-eb01-0410-851d-a64ba20cac60 --- data/Tupfile.lua | 1 + programs/media/qr_tool/Makefile | 32 + programs/media/qr_tool/Makefile_lib | 26 + programs/media/qr_tool/lib/decode.c | 933 +++++++++++++++ programs/media/qr_tool/lib/identify.c | 1153 +++++++++++++++++++ programs/media/qr_tool/lib/quirc.c | 130 +++ programs/media/qr_tool/lib/quirc.h | 185 +++ programs/media/qr_tool/lib/quirc_internal.h | 115 ++ programs/media/qr_tool/lib/version_db.c | 421 +++++++ programs/media/qr_tool/libquirc.a | Bin 0 -> 30460 bytes programs/media/qr_tool/qr_tool | Bin 0 -> 50208 bytes programs/media/qr_tool/qr_tool.c | 313 +++++ 12 files changed, 3309 insertions(+) create mode 100644 programs/media/qr_tool/Makefile create mode 100644 programs/media/qr_tool/Makefile_lib create mode 100644 programs/media/qr_tool/lib/decode.c create mode 100644 programs/media/qr_tool/lib/identify.c create mode 100644 programs/media/qr_tool/lib/quirc.c create mode 100644 programs/media/qr_tool/lib/quirc.h create mode 100644 programs/media/qr_tool/lib/quirc_internal.h create mode 100644 programs/media/qr_tool/lib/version_db.c create mode 100644 programs/media/qr_tool/libquirc.a create mode 100755 programs/media/qr_tool/qr_tool create mode 100644 programs/media/qr_tool/qr_tool.c diff --git a/data/Tupfile.lua b/data/Tupfile.lua index 9e59b33fe1..2e075e5e33 100644 --- a/data/Tupfile.lua +++ b/data/Tupfile.lua @@ -694,6 +694,7 @@ tup.append_table(extra_files, { {"kolibrios/develop/TinyBasic/", PROGS .. "/develop/tinybasic/TBuserMan.txt"}, {"kolibrios/utils/teatool", PROGS .. "/other/TEAtool/teatool"}, {"kolibrios/utils/passwordgen", PROGS .. "/other/PasswordGen/passwordgen"}, + {"kolibrios/media/qr_tool", PROGS .. "/media/qr_tool/qr_tool"}, }) end -- tup.getconfig('NO_TCC') ~= 'full' diff --git a/programs/media/qr_tool/Makefile b/programs/media/qr_tool/Makefile new file mode 100644 index 0000000000..d658c1e644 --- /dev/null +++ b/programs/media/qr_tool/Makefile @@ -0,0 +1,32 @@ +CC = kos32-gcc +LD = kos32-ld + +SDK_DIR:= /home/programist/KOS_SVN/contrib/sdk +CLAYER:= /home/programist/KOS_SVN/contrib/C_Layer + +LDFLAGS = -static -S -nostdlib -T $(SDK_DIR)/sources/newlib/app-dynamic.lds \ + --image-base 0 -lquirc -lgcc -ldll -lc.dll + +# caution: without -mno-ms-bitfields below, pagefault occurs (why ? idk) +CFLAGS = -g -U_Win32 -U_WIN32 -U__MINGW32__ -std=c99 -mno-ms-bitfields + +INCLUDES= -I. -Ilib -I$(SDK_DIR)/sources/newlib/libc/include -I$(CLAYER)/INCLUDE +LIBPATH:= -L $(SDK_DIR)/lib -L /home/autobuild/tools/win32/mingw32/lib -L. + +OBJPATH = $(CLAYER)/OBJ + +SOURCES = qr_tool.c + +OBJECTS = $(patsubst %.c, %.o, $(SOURCES)) + +all: qr_tool + +qr_tool: $(OBJECTS) + $(LD) $(LIBPATH) --subsystem windows -o $@ $^ $(OBJPATH)/loadboxlib.obj $(OBJPATH)/loadproclib.obj $(OBJPATH)/loadlibimg.obj $(LDFLAGS) + kos32-objcopy $@ -O binary + +%.o : %.c Makefile + $(CC) -c $(INCLUDES) $(CFLAGS) -o $@ $< + +clean: + rm -f *.o qr_tool diff --git a/programs/media/qr_tool/Makefile_lib b/programs/media/qr_tool/Makefile_lib new file mode 100644 index 0000000000..22268d6a52 --- /dev/null +++ b/programs/media/qr_tool/Makefile_lib @@ -0,0 +1,26 @@ +CC = kos32-gcc +AR = kos32-ar + +SDK_DIR:= /home/programist/KOS_SVN/contrib/sdk + +INCLUDES= -Ilib -I$(SDK_DIR)/sources/newlib/libc/include + +CFLAGS = -O2 -std=c99 -Wall -W #-D_FILE_OFFSET_BITS=64 +ARFLAGS = -rcs + +SOURCES = lib/decode.c lib/identify.c lib/quirc.c lib/version_db.c +HEADERS = lib/quirc.h lib/quirc_internal.h +OBJECTS = $(patsubst %.c, %.o, $(SOURCES)) + +.PHONY: all clean + +all: libquirc.a + +libquirc.a: $(OBJECTS) + $(AR) $(ARFLAGS) $@ $(OBJECTS) + +%.o : %.c $(HEADERS) Makefile + $(CC) -c $(INCLUDES) $(CFLAGS) -o $@ $< + +clean: + rm -f *.o lib/*.o libquirc.a diff --git a/programs/media/qr_tool/lib/decode.c b/programs/media/qr_tool/lib/decode.c new file mode 100644 index 0000000000..0d654c5b85 --- /dev/null +++ b/programs/media/qr_tool/lib/decode.c @@ -0,0 +1,933 @@ +/* quirc -- QR-code recognition library + * Copyright (C) 2010-2012 Daniel Beer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "quirc_internal.h" + +#include +#include + +#define MAX_POLY 64 + +/************************************************************************ + * Galois fields + */ + +struct galois_field { + int p; + const uint8_t *log; + const uint8_t *exp; +}; + +static const uint8_t gf16_exp[16] = { + 0x01, 0x02, 0x04, 0x08, 0x03, 0x06, 0x0c, 0x0b, + 0x05, 0x0a, 0x07, 0x0e, 0x0f, 0x0d, 0x09, 0x01 +}; + +static const uint8_t gf16_log[16] = { + 0x00, 0x0f, 0x01, 0x04, 0x02, 0x08, 0x05, 0x0a, + 0x03, 0x0e, 0x09, 0x07, 0x06, 0x0d, 0x0b, 0x0c +}; + +static const struct galois_field gf16 = { + .p = 15, + .log = gf16_log, + .exp = gf16_exp +}; + +static const uint8_t gf256_exp[256] = { + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, + 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26, + 0x4c, 0x98, 0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, + 0x8f, 0x03, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, + 0x9d, 0x27, 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, + 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23, + 0x46, 0x8c, 0x05, 0x0a, 0x14, 0x28, 0x50, 0xa0, + 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1, + 0x5f, 0xbe, 0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, + 0x65, 0xca, 0x89, 0x0f, 0x1e, 0x3c, 0x78, 0xf0, + 0xfd, 0xe7, 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, + 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2, + 0xd9, 0xaf, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, + 0x0d, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce, + 0x81, 0x1f, 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, + 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc, + 0x85, 0x17, 0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, + 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54, + 0xa8, 0x4d, 0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, + 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73, + 0xe6, 0xd1, 0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, + 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff, + 0xe3, 0xdb, 0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, + 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41, + 0x82, 0x19, 0x32, 0x64, 0xc8, 0x8d, 0x07, 0x0e, + 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6, + 0x51, 0xa2, 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, + 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x09, + 0x12, 0x24, 0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, + 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0x0b, 0x16, + 0x2c, 0x58, 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, + 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x01 +}; + +static const uint8_t gf256_log[256] = { + 0x00, 0xff, 0x01, 0x19, 0x02, 0x32, 0x1a, 0xc6, + 0x03, 0xdf, 0x33, 0xee, 0x1b, 0x68, 0xc7, 0x4b, + 0x04, 0x64, 0xe0, 0x0e, 0x34, 0x8d, 0xef, 0x81, + 0x1c, 0xc1, 0x69, 0xf8, 0xc8, 0x08, 0x4c, 0x71, + 0x05, 0x8a, 0x65, 0x2f, 0xe1, 0x24, 0x0f, 0x21, + 0x35, 0x93, 0x8e, 0xda, 0xf0, 0x12, 0x82, 0x45, + 0x1d, 0xb5, 0xc2, 0x7d, 0x6a, 0x27, 0xf9, 0xb9, + 0xc9, 0x9a, 0x09, 0x78, 0x4d, 0xe4, 0x72, 0xa6, + 0x06, 0xbf, 0x8b, 0x62, 0x66, 0xdd, 0x30, 0xfd, + 0xe2, 0x98, 0x25, 0xb3, 0x10, 0x91, 0x22, 0x88, + 0x36, 0xd0, 0x94, 0xce, 0x8f, 0x96, 0xdb, 0xbd, + 0xf1, 0xd2, 0x13, 0x5c, 0x83, 0x38, 0x46, 0x40, + 0x1e, 0x42, 0xb6, 0xa3, 0xc3, 0x48, 0x7e, 0x6e, + 0x6b, 0x3a, 0x28, 0x54, 0xfa, 0x85, 0xba, 0x3d, + 0xca, 0x5e, 0x9b, 0x9f, 0x0a, 0x15, 0x79, 0x2b, + 0x4e, 0xd4, 0xe5, 0xac, 0x73, 0xf3, 0xa7, 0x57, + 0x07, 0x70, 0xc0, 0xf7, 0x8c, 0x80, 0x63, 0x0d, + 0x67, 0x4a, 0xde, 0xed, 0x31, 0xc5, 0xfe, 0x18, + 0xe3, 0xa5, 0x99, 0x77, 0x26, 0xb8, 0xb4, 0x7c, + 0x11, 0x44, 0x92, 0xd9, 0x23, 0x20, 0x89, 0x2e, + 0x37, 0x3f, 0xd1, 0x5b, 0x95, 0xbc, 0xcf, 0xcd, + 0x90, 0x87, 0x97, 0xb2, 0xdc, 0xfc, 0xbe, 0x61, + 0xf2, 0x56, 0xd3, 0xab, 0x14, 0x2a, 0x5d, 0x9e, + 0x84, 0x3c, 0x39, 0x53, 0x47, 0x6d, 0x41, 0xa2, + 0x1f, 0x2d, 0x43, 0xd8, 0xb7, 0x7b, 0xa4, 0x76, + 0xc4, 0x17, 0x49, 0xec, 0x7f, 0x0c, 0x6f, 0xf6, + 0x6c, 0xa1, 0x3b, 0x52, 0x29, 0x9d, 0x55, 0xaa, + 0xfb, 0x60, 0x86, 0xb1, 0xbb, 0xcc, 0x3e, 0x5a, + 0xcb, 0x59, 0x5f, 0xb0, 0x9c, 0xa9, 0xa0, 0x51, + 0x0b, 0xf5, 0x16, 0xeb, 0x7a, 0x75, 0x2c, 0xd7, + 0x4f, 0xae, 0xd5, 0xe9, 0xe6, 0xe7, 0xad, 0xe8, + 0x74, 0xd6, 0xf4, 0xea, 0xa8, 0x50, 0x58, 0xaf +}; + +static const struct galois_field gf256 = { + .p = 255, + .log = gf256_log, + .exp = gf256_exp +}; + +/************************************************************************ + * Polynomial operations + */ + +static void poly_add(uint8_t *dst, const uint8_t *src, uint8_t c, + int shift, const struct galois_field *gf) +{ + int i; + int log_c = gf->log[c]; + + if (!c) + return; + + for (i = 0; i < MAX_POLY; i++) { + int p = i + shift; + uint8_t v = src[i]; + + if (p < 0 || p >= MAX_POLY) + continue; + if (!v) + continue; + + dst[p] ^= gf->exp[(gf->log[v] + log_c) % gf->p]; + } +} + +static uint8_t poly_eval(const uint8_t *s, uint8_t x, + const struct galois_field *gf) +{ + int i; + uint8_t sum = 0; + uint8_t log_x = gf->log[x]; + + if (!x) + return s[0]; + + for (i = 0; i < MAX_POLY; i++) { + uint8_t c = s[i]; + + if (!c) + continue; + + sum ^= gf->exp[(gf->log[c] + log_x * i) % gf->p]; + } + + return sum; +} + +/************************************************************************ + * Berlekamp-Massey algorithm for finding error locator polynomials. + */ + +static void berlekamp_massey(const uint8_t *s, int N, + const struct galois_field *gf, + uint8_t *sigma) +{ + uint8_t C[MAX_POLY]; + uint8_t B[MAX_POLY]; + int L = 0; + int m = 1; + uint8_t b = 1; + int n; + + memset(B, 0, sizeof(B)); + memset(C, 0, sizeof(C)); + B[0] = 1; + C[0] = 1; + + for (n = 0; n < N; n++) { + uint8_t d = s[n]; + uint8_t mult; + int i; + + for (i = 1; i <= L; i++) { + if (!(C[i] && s[n - i])) + continue; + + d ^= gf->exp[(gf->log[C[i]] + + gf->log[s[n - i]]) % + gf->p]; + } + + mult = gf->exp[(gf->p - gf->log[b] + gf->log[d]) % gf->p]; + + if (!d) { + m++; + } else if (L * 2 <= n) { + uint8_t T[MAX_POLY]; + + memcpy(T, C, sizeof(T)); + poly_add(C, B, mult, m, gf); + memcpy(B, T, sizeof(B)); + L = n + 1 - L; + b = d; + m = 1; + } else { + poly_add(C, B, mult, m, gf); + m++; + } + } + + memcpy(sigma, C, MAX_POLY); +} + +/************************************************************************ + * Code stream error correction + * + * Generator polynomial for GF(2^8) is x^8 + x^4 + x^3 + x^2 + 1 + */ + +static int block_syndromes(const uint8_t *data, int bs, int npar, uint8_t *s) +{ + int nonzero = 0; + int i; + + memset(s, 0, MAX_POLY); + + for (i = 0; i < npar; i++) { + int j; + + for (j = 0; j < bs; j++) { + uint8_t c = data[bs - j - 1]; + + if (!c) + continue; + + s[i] ^= gf256_exp[((int)gf256_log[c] + + i * j) % 255]; + } + + if (s[i]) + nonzero = 1; + } + + return nonzero; +} + +static void eloc_poly(uint8_t *omega, + const uint8_t *s, const uint8_t *sigma, + int npar) +{ + int i; + + memset(omega, 0, MAX_POLY); + + for (i = 0; i < npar; i++) { + const uint8_t a = sigma[i]; + const uint8_t log_a = gf256_log[a]; + int j; + + if (!a) + continue; + + for (j = 0; j + 1 < MAX_POLY; j++) { + const uint8_t b = s[j + 1]; + + if (i + j >= npar) + break; + + if (!b) + continue; + + omega[i + j] ^= + gf256_exp[(log_a + gf256_log[b]) % 255]; + } + } +} + +static quirc_decode_error_t correct_block(uint8_t *data, + const struct quirc_rs_params *ecc) +{ + int npar = ecc->bs - ecc->dw; + uint8_t s[MAX_POLY]; + uint8_t sigma[MAX_POLY]; + uint8_t sigma_deriv[MAX_POLY]; + uint8_t omega[MAX_POLY]; + int i; + + /* Compute syndrome vector */ + if (!block_syndromes(data, ecc->bs, npar, s)) + return QUIRC_SUCCESS; + + berlekamp_massey(s, npar, &gf256, sigma); + + /* Compute derivative of sigma */ + memset(sigma_deriv, 0, MAX_POLY); + for (i = 0; i + 1 < MAX_POLY; i += 2) + sigma_deriv[i] = sigma[i + 1]; + + /* Compute error evaluator polynomial */ + eloc_poly(omega, s, sigma, npar - 1); + + /* Find error locations and magnitudes */ + for (i = 0; i < ecc->bs; i++) { + uint8_t xinv = gf256_exp[255 - i]; + + if (!poly_eval(sigma, xinv, &gf256)) { + uint8_t sd_x = poly_eval(sigma_deriv, xinv, &gf256); + uint8_t omega_x = poly_eval(omega, xinv, &gf256); + uint8_t error = gf256_exp[(255 - gf256_log[sd_x] + + gf256_log[omega_x]) % 255]; + + data[ecc->bs - i - 1] ^= error; + } + } + + if (block_syndromes(data, ecc->bs, npar, s)) + return QUIRC_ERROR_DATA_ECC; + + return QUIRC_SUCCESS; +} + +/************************************************************************ + * Format value error correction + * + * Generator polynomial for GF(2^4) is x^4 + x + 1 + */ + +#define FORMAT_MAX_ERROR 3 +#define FORMAT_SYNDROMES (FORMAT_MAX_ERROR * 2) +#define FORMAT_BITS 15 + +static int format_syndromes(uint16_t u, uint8_t *s) +{ + int i; + int nonzero = 0; + + memset(s, 0, MAX_POLY); + + for (i = 0; i < FORMAT_SYNDROMES; i++) { + int j; + + s[i] = 0; + for (j = 0; j < FORMAT_BITS; j++) + if (u & (1 << j)) + s[i] ^= gf16_exp[((i + 1) * j) % 15]; + + if (s[i]) + nonzero = 1; + } + + return nonzero; +} + +static quirc_decode_error_t correct_format(uint16_t *f_ret) +{ + uint16_t u = *f_ret; + int i; + uint8_t s[MAX_POLY]; + uint8_t sigma[MAX_POLY]; + + /* Evaluate U (received codeword) at each of alpha_1 .. alpha_6 + * to get S_1 .. S_6 (but we index them from 0). + */ + if (!format_syndromes(u, s)) + return QUIRC_SUCCESS; + + berlekamp_massey(s, FORMAT_SYNDROMES, &gf16, sigma); + + /* Now, find the roots of the polynomial */ + for (i = 0; i < 15; i++) + if (!poly_eval(sigma, gf16_exp[15 - i], &gf16)) + u ^= (1 << i); + + if (format_syndromes(u, s)) + return QUIRC_ERROR_FORMAT_ECC; + + *f_ret = u; + return QUIRC_SUCCESS; +} + +/************************************************************************ + * Decoder algorithm + */ + +struct datastream { + uint8_t raw[QUIRC_MAX_PAYLOAD]; + int data_bits; + int ptr; + + uint8_t data[QUIRC_MAX_PAYLOAD]; +}; + +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; +} + +static quirc_decode_error_t read_format(const struct quirc_code *code, + struct quirc_data *data, int which) +{ + int i; + uint16_t format = 0; + uint16_t fdata; + quirc_decode_error_t err; + + if (which) { + for (i = 0; i < 7; i++) + format = (format << 1) | + grid_bit(code, 8, code->size - 1 - i); + for (i = 0; i < 8; i++) + format = (format << 1) | + grid_bit(code, code->size - 8 + i, 8); + } else { + static const int xs[15] = { + 8, 8, 8, 8, 8, 8, 8, 8, 7, 5, 4, 3, 2, 1, 0 + }; + static const int ys[15] = { + 0, 1, 2, 3, 4, 5, 7, 8, 8, 8, 8, 8, 8, 8, 8 + }; + + for (i = 14; i >= 0; i--) + format = (format << 1) | grid_bit(code, xs[i], ys[i]); + } + + format ^= 0x5412; + + err = correct_format(&format); + if (err) + return err; + + fdata = format >> 10; + data->ecc_level = fdata >> 3; + data->mask = fdata & 7; + + return QUIRC_SUCCESS; +} + +static int mask_bit(int mask, int i, int j) +{ + switch (mask) { + case 0: return !((i + j) % 2); + case 1: return !(i % 2); + case 2: return !(j % 3); + case 3: return !((i + j) % 3); + case 4: return !(((i / 2) + (j / 3)) % 2); + case 5: return !((i * j) % 2 + (i * j) % 3); + case 6: return !(((i * j) % 2 + (i * j) % 3) % 2); + case 7: return !(((i * j) % 3 + (i + j) % 2) % 2); + } + + return 0; +} + +static int reserved_cell(int version, int i, int j) +{ + const struct quirc_version_info *ver = &quirc_version_db[version]; + int size = version * 4 + 17; + int ai = -1, aj = -1, a; + + /* Finder + format: top left */ + if (i < 9 && j < 9) + return 1; + + /* Finder + format: bottom left */ + if (i + 8 >= size && j < 9) + return 1; + + /* Finder + format: top right */ + if (i < 9 && j + 8 >= size) + return 1; + + /* Exclude timing patterns */ + if (i == 6 || j == 6) + return 1; + + /* Exclude version info, if it exists. Version info sits adjacent to + * the top-right and bottom-left finders in three rows, bounded by + * the timing pattern. + */ + if (version >= 7) { + if (i < 6 && j + 11 >= size) + return 1; + if (i + 11 >= size && j < 6) + return 1; + } + + /* Exclude alignment patterns */ + for (a = 0; a < QUIRC_MAX_ALIGNMENT && ver->apat[a]; a++) { + int p = ver->apat[a]; + + if (abs(p - i) < 3) + ai = a; + if (abs(p - j) < 3) + aj = a; + } + + if (ai >= 0 && aj >= 0) { + a--; + if (ai > 0 && ai < a) + return 1; + if (aj > 0 && aj < a) + return 1; + if (aj == a && ai == a) + return 1; + } + + return 0; +} + +static void read_bit(const struct quirc_code *code, + struct quirc_data *data, + struct datastream *ds, int i, int j) +{ + int bitpos = ds->data_bits & 7; + int bytepos = ds->data_bits >> 3; + int v = grid_bit(code, j, i); + + if (mask_bit(data->mask, i, j)) + v ^= 1; + + if (v) + ds->raw[bytepos] |= (0x80 >> bitpos); + + ds->data_bits++; +} + +static void read_data(const struct quirc_code *code, + struct quirc_data *data, + struct datastream *ds) +{ + int y = code->size - 1; + int x = code->size - 1; + int dir = -1; + + while (x > 0) { + if (x == 6) + x--; + + if (!reserved_cell(data->version, y, x)) + read_bit(code, data, ds, y, x); + + if (!reserved_cell(data->version, y, x - 1)) + read_bit(code, data, ds, y, x - 1); + + y += dir; + if (y < 0 || y >= code->size) { + dir = -dir; + x -= 2; + y += dir; + } + } +} + +static quirc_decode_error_t codestream_ecc(struct quirc_data *data, + struct datastream *ds) +{ + const struct quirc_version_info *ver = + &quirc_version_db[data->version]; + const struct quirc_rs_params *sb_ecc = &ver->ecc[data->ecc_level]; + struct quirc_rs_params lb_ecc; + const int lb_count = + (ver->data_bytes - sb_ecc->bs * sb_ecc->ns) / (sb_ecc->bs + 1); + const int bc = lb_count + sb_ecc->ns; + const int ecc_offset = sb_ecc->dw * bc + lb_count; + int dst_offset = 0; + int i; + + memcpy(&lb_ecc, sb_ecc, sizeof(lb_ecc)); + lb_ecc.dw++; + lb_ecc.bs++; + + for (i = 0; i < bc; i++) { + uint8_t *dst = ds->data + dst_offset; + const struct quirc_rs_params *ecc = + (i < sb_ecc->ns) ? sb_ecc : &lb_ecc; + const int num_ec = ecc->bs - ecc->dw; + quirc_decode_error_t err; + int j; + + for (j = 0; j < ecc->dw; j++) + dst[j] = ds->raw[j * bc + i]; + for (j = 0; j < num_ec; j++) + dst[ecc->dw + j] = ds->raw[ecc_offset + j * bc + i]; + + err = correct_block(dst, ecc); + if (err) + return err; + + dst_offset += ecc->dw; + } + + ds->data_bits = dst_offset * 8; + + return QUIRC_SUCCESS; +} + +static inline int bits_remaining(const struct datastream *ds) +{ + return ds->data_bits - ds->ptr; +} + +static int take_bits(struct datastream *ds, int len) +{ + int ret = 0; + + while (len && (ds->ptr < ds->data_bits)) { + uint8_t b = ds->data[ds->ptr >> 3]; + int bitpos = ds->ptr & 7; + + ret <<= 1; + if ((b << bitpos) & 0x80) + ret |= 1; + + ds->ptr++; + len--; + } + + return ret; +} + +static int numeric_tuple(struct quirc_data *data, + struct datastream *ds, + int bits, int digits) +{ + int tuple; + int i; + + if (bits_remaining(ds) < bits) + return -1; + + tuple = take_bits(ds, bits); + + for (i = digits - 1; i >= 0; i--) { + data->payload[data->payload_len + i] = tuple % 10 + '0'; + tuple /= 10; + } + + data->payload_len += digits; + return 0; +} + +static quirc_decode_error_t decode_numeric(struct quirc_data *data, + struct datastream *ds) +{ + int bits = 14; + int count; + + if (data->version < 10) + bits = 10; + else if (data->version < 27) + bits = 12; + + count = take_bits(ds, bits); + if (data->payload_len + count + 1 > QUIRC_MAX_PAYLOAD) + return QUIRC_ERROR_DATA_OVERFLOW; + + while (count >= 3) { + if (numeric_tuple(data, ds, 10, 3) < 0) + return QUIRC_ERROR_DATA_UNDERFLOW; + count -= 3; + } + + if (count >= 2) { + if (numeric_tuple(data, ds, 7, 2) < 0) + return QUIRC_ERROR_DATA_UNDERFLOW; + count -= 2; + } + + if (count) { + if (numeric_tuple(data, ds, 4, 1) < 0) + return QUIRC_ERROR_DATA_UNDERFLOW; + count--; + } + + return QUIRC_SUCCESS; +} + +static int alpha_tuple(struct quirc_data *data, + struct datastream *ds, + int bits, int digits) +{ + int tuple; + int i; + + if (bits_remaining(ds) < bits) + return -1; + + tuple = take_bits(ds, bits); + + for (i = 0; i < digits; i++) { + static const char *alpha_map = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"; + + data->payload[data->payload_len + digits - i - 1] = + alpha_map[tuple % 45]; + tuple /= 45; + } + + data->payload_len += digits; + return 0; +} + +static quirc_decode_error_t decode_alpha(struct quirc_data *data, + struct datastream *ds) +{ + int bits = 13; + int count; + + if (data->version < 10) + bits = 9; + else if (data->version < 27) + bits = 11; + + count = take_bits(ds, bits); + if (data->payload_len + count + 1 > QUIRC_MAX_PAYLOAD) + return QUIRC_ERROR_DATA_OVERFLOW; + + while (count >= 2) { + if (alpha_tuple(data, ds, 11, 2) < 0) + return QUIRC_ERROR_DATA_UNDERFLOW; + count -= 2; + } + + if (count) { + if (alpha_tuple(data, ds, 6, 1) < 0) + return QUIRC_ERROR_DATA_UNDERFLOW; + count--; + } + + return QUIRC_SUCCESS; +} + +static quirc_decode_error_t decode_byte(struct quirc_data *data, + struct datastream *ds) +{ + int bits = 16; + int count; + int i; + + if (data->version < 10) + bits = 8; + + count = take_bits(ds, bits); + if (data->payload_len + count + 1 > QUIRC_MAX_PAYLOAD) + return QUIRC_ERROR_DATA_OVERFLOW; + if (bits_remaining(ds) < count * 8) + return QUIRC_ERROR_DATA_UNDERFLOW; + + for (i = 0; i < count; i++) + data->payload[data->payload_len++] = take_bits(ds, 8); + + return QUIRC_SUCCESS; +} + +static quirc_decode_error_t decode_kanji(struct quirc_data *data, + struct datastream *ds) +{ + int bits = 12; + int count; + int i; + + if (data->version < 10) + bits = 8; + else if (data->version < 27) + bits = 10; + + count = take_bits(ds, bits); + if (data->payload_len + count * 2 + 1 > QUIRC_MAX_PAYLOAD) + return QUIRC_ERROR_DATA_OVERFLOW; + if (bits_remaining(ds) < count * 13) + return QUIRC_ERROR_DATA_UNDERFLOW; + + for (i = 0; i < count; i++) { + int d = take_bits(ds, 13); + int msB = d / 0xc0; + int lsB = d % 0xc0; + int intermediate = (msB << 8) | lsB; + uint16_t sjw; + + if (intermediate + 0x8140 <= 0x9ffc) { + /* bytes are in the range 0x8140 to 0x9FFC */ + sjw = intermediate + 0x8140; + } else { + /* bytes are in the range 0xE040 to 0xEBBF */ + sjw = intermediate + 0xc140; + } + + data->payload[data->payload_len++] = sjw >> 8; + data->payload[data->payload_len++] = sjw & 0xff; + } + + return QUIRC_SUCCESS; +} + +static quirc_decode_error_t decode_eci(struct quirc_data *data, + struct datastream *ds) +{ + if (bits_remaining(ds) < 8) + return QUIRC_ERROR_DATA_UNDERFLOW; + + data->eci = take_bits(ds, 8); + + if ((data->eci & 0xc0) == 0x80) { + if (bits_remaining(ds) < 8) + return QUIRC_ERROR_DATA_UNDERFLOW; + + data->eci = (data->eci << 8) | take_bits(ds, 8); + } else if ((data->eci & 0xe0) == 0xc0) { + if (bits_remaining(ds) < 16) + return QUIRC_ERROR_DATA_UNDERFLOW; + + data->eci = (data->eci << 16) | take_bits(ds, 16); + } + + return QUIRC_SUCCESS; +} + +static quirc_decode_error_t decode_payload(struct quirc_data *data, + struct datastream *ds) +{ + while (bits_remaining(ds) >= 4) { + quirc_decode_error_t err = QUIRC_SUCCESS; + int type = take_bits(ds, 4); + + switch (type) { + case QUIRC_DATA_TYPE_NUMERIC: + err = decode_numeric(data, ds); + break; + + case QUIRC_DATA_TYPE_ALPHA: + err = decode_alpha(data, ds); + break; + + case QUIRC_DATA_TYPE_BYTE: + err = decode_byte(data, ds); + break; + + case QUIRC_DATA_TYPE_KANJI: + err = decode_kanji(data, ds); + break; + + case 7: + err = decode_eci(data, ds); + break; + + default: + goto done; + } + + if (err) + return err; + + if (!(type & (type - 1)) && (type > data->data_type)) + data->data_type = type; + } +done: + + /* Add nul terminator to all payloads */ + if (data->payload_len >= (int) sizeof(data->payload)) + data->payload_len--; + data->payload[data->payload_len] = 0; + + return QUIRC_SUCCESS; +} + +quirc_decode_error_t quirc_decode(const struct quirc_code *code, + struct quirc_data *data) +{ + quirc_decode_error_t err; + struct datastream ds; + + if ((code->size - 17) % 4) + return QUIRC_ERROR_INVALID_GRID_SIZE; + + memset(data, 0, sizeof(*data)); + memset(&ds, 0, sizeof(ds)); + + data->version = (code->size - 17) / 4; + + if (data->version < 1 || + data->version > QUIRC_MAX_VERSION) + return QUIRC_ERROR_INVALID_VERSION; + + /* Read format information -- try both locations */ + err = read_format(code, data, 0); + if (err) + err = read_format(code, data, 1); + if (err) + return err; + + read_data(code, data, &ds); + err = codestream_ecc(data, &ds); + if (err) + return err; + + err = decode_payload(data, &ds); + if (err) + return err; + + return QUIRC_SUCCESS; +} + +void quirc_flip(struct quirc_code *code) +{ + struct quirc_code flipped = {0}; + unsigned int offset = 0; + for (int y = 0; y < code->size; y++) { + for (int x = 0; x < code->size; x++) { + if (grid_bit(code, y, x)) { + flipped.cell_bitmap[offset >> 3u] |= (1u << (offset & 7u)); + } + offset++; + } + } + memcpy(&code->cell_bitmap, &flipped.cell_bitmap, sizeof(flipped.cell_bitmap)); +} diff --git a/programs/media/qr_tool/lib/identify.c b/programs/media/qr_tool/lib/identify.c new file mode 100644 index 0000000000..37f989161b --- /dev/null +++ b/programs/media/qr_tool/lib/identify.c @@ -0,0 +1,1153 @@ +/* quirc - QR-code recognition library + * Copyright (C) 2010-2012 Daniel Beer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/*#ifndef INT_MAX +#define INT_MAX 2147483647 +#endif*/ +#include +#include +#include +#include +#include "quirc_internal.h" + +/*----------------------------------------------------------------------------------------*/ + +/************************************************************************ + * Linear algebra routines + */ + +static int line_intersect(const struct quirc_point *p0, + const struct quirc_point *p1, + const struct quirc_point *q0, + const struct quirc_point *q1, + struct quirc_point *r) +{ + /* (a, b) is perpendicular to line p */ + int a = -(p1->y - p0->y); + int b = p1->x - p0->x; + + /* (c, d) is perpendicular to line q */ + int c = -(q1->y - q0->y); + int d = q1->x - q0->x; + + /* e and f are dot products of the respective vectors with p and q */ + int e = a * p1->x + b * p1->y; + int f = c * q1->x + d * q1->y; + + /* Now we need to solve: + * [a b] [rx] [e] + * [c d] [ry] = [f] + * + * We do this by inverting the matrix and applying it to (e, f): + * [ d -b] [e] [rx] + * 1/det [-c a] [f] = [ry] + */ + int det = (a * d) - (b * c); + + if (!det) + return 0; + + r->x = (d * e - b * f) / det; + r->y = (-c * e + a * f) / det; + + return 1; +} + +static void perspective_setup(double *c, + const struct quirc_point *rect, + double w, double h) +{ + double x0 = rect[0].x; + double y0 = rect[0].y; + double x1 = rect[1].x; + double y1 = rect[1].y; + double x2 = rect[2].x; + double y2 = rect[2].y; + double x3 = rect[3].x; + double y3 = rect[3].y; + + double wden = w * (x2*y3 - x3*y2 + (x3-x2)*y1 + x1*(y2-y3)); + double hden = h * (x2*y3 + x1*(y2-y3) - x3*y2 + (x3-x2)*y1); + + c[0] = (x1*(x2*y3-x3*y2) + x0*(-x2*y3+x3*y2+(x2-x3)*y1) + + x1*(x3-x2)*y0) / wden; + c[1] = -(x0*(x2*y3+x1*(y2-y3)-x2*y1) - x1*x3*y2 + x2*x3*y1 + + (x1*x3-x2*x3)*y0) / hden; + c[2] = x0; + c[3] = (y0*(x1*(y3-y2)-x2*y3+x3*y2) + y1*(x2*y3-x3*y2) + + x0*y1*(y2-y3)) / wden; + c[4] = (x0*(y1*y3-y2*y3) + x1*y2*y3 - x2*y1*y3 + + y0*(x3*y2-x1*y2+(x2-x3)*y1)) / hden; + c[5] = y0; + c[6] = (x1*(y3-y2) + x0*(y2-y3) + (x2-x3)*y1 + (x3-x2)*y0) / wden; + c[7] = (-x2*y3 + x1*y3 + x3*y2 + x0*(y1-y2) - x3*y1 + (x2-x1)*y0) / + hden; +} + +static void perspective_map(const double *c, + double u, double v, struct quirc_point *ret) +{ + double den = c[6]*u + c[7]*v + 1.0; + double x = (c[0]*u + c[1]*v + c[2]) / den; + double y = (c[3]*u + c[4]*v + c[5]) / den; + + ret->x = (int) rint(x); + ret->y = (int) rint(y); +} + +static void perspective_unmap(const double *c, + const struct quirc_point *in, + double *u, double *v) +{ + double x = in->x; + double y = in->y; + double den = -c[0]*c[7]*y + c[1]*c[6]*y + (c[3]*c[7]-c[4]*c[6])*x + + c[0]*c[4] - c[1]*c[3]; + + *u = -(c[1]*(y-c[5]) - c[2]*c[7]*y + (c[5]*c[7]-c[4])*x + c[2]*c[4]) / + den; + *v = (c[0]*(y-c[5]) - c[2]*c[6]*y + (c[5]*c[6]-c[3])*x + c[2]*c[3]) / + den; +} + +/************************************************************************ + * 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, + span_func_t func, void *user_data, + int depth) +{ + int left = x; + int right = x; + int i; + quirc_pixel_t *row = q->pixels + y * q->w; + + if (depth >= FLOOD_FILL_MAX_DEPTH) + return; + + while (left > 0 && row[left - 1] == from) + left--; + + while (right < q->w - 1 && row[right + 1] == from) + right++; + + /* Fill the extent */ + for (i = left; i <= right; i++) + row[i] = to; + + 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; + + for (i = left; i <= right; i++) + if (row[i] == from) + flood_fill_seed(q, i, y + 1, from, to, + func, user_data, depth + 1); + } +} + +/************************************************************************ + * Adaptive thresholding + */ + +static uint8_t otsu(const struct quirc *q) +{ + 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; + while (length--) { + uint8_t value = *ptr++; + histogram[value]++; + } + + // Calculate weighted sum of histogram values + unsigned int 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 max = 0; + uint8_t threshold = 0; + for (i = 0; i <= UINT8_MAX; ++i) { + // Weighted background + q1 += histogram[i]; + if (q1 == 0) + continue; + + // Weighted foreground + const 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 m1m2 = m1 - m2; + const double variance = m1m2 * m1m2 * q1 * q2; + if (variance >= max) { + threshold = i; + max = variance; + } + } + + return threshold; +} + +static void area_count(void *user_data, int y, int left, int right) +{ + ((struct quirc_region *)user_data)->count += right - left + 1; +} + +static int region_code(struct quirc *q, int x, int y) +{ + int pixel; + struct quirc_region *box; + int region; + + if (x < 0 || y < 0 || x >= q->w || y >= q->h) + return -1; + + pixel = q->pixels[y * q->w + x]; + + if (pixel >= QUIRC_PIXEL_REGION) + return pixel; + + if (pixel == QUIRC_PIXEL_WHITE) + return -1; + + if (q->num_regions >= QUIRC_MAX_REGIONS) + return -1; + + region = q->num_regions; + box = &q->regions[q->num_regions++]; + + memset(box, 0, sizeof(*box)); + + box->seed.x = x; + box->seed.y = y; + box->capstone = -1; + + flood_fill_seed(q, x, y, pixel, region, area_count, box, 0); + + return region; +} + +struct polygon_score_data { + struct quirc_point ref; + + int scores[4]; + struct quirc_point *corners; +}; + +static void find_one_corner(void *user_data, int y, int left, int right) +{ + struct polygon_score_data *psd = + (struct polygon_score_data *)user_data; + int xs[2] = {left, right}; + int dy = y - psd->ref.y; + int i; + + for (i = 0; i < 2; i++) { + int dx = xs[i] - psd->ref.x; + int d = dx * dx + dy * dy; + + if (d > psd->scores[0]) { + psd->scores[0] = d; + psd->corners[0].x = xs[i]; + psd->corners[0].y = y; + } + } +} + +static void find_other_corners(void *user_data, int y, int left, int right) +{ + struct polygon_score_data *psd = + (struct polygon_score_data *)user_data; + int xs[2] = {left, right}; + int i; + + for (i = 0; i < 2; i++) { + int up = xs[i] * psd->ref.x + y * psd->ref.y; + int right = xs[i] * -psd->ref.y + y * psd->ref.x; + int scores[4] = {up, right, -up, -right}; + int j; + + for (j = 0; j < 4; j++) { + if (scores[j] > psd->scores[j]) { + psd->scores[j] = scores[j]; + psd->corners[j].x = xs[i]; + psd->corners[j].y = y; + } + } + } +} + +static void find_region_corners(struct quirc *q, + int rcode, const struct quirc_point *ref, + struct quirc_point *corners) +{ + struct quirc_region *region = &q->regions[rcode]; + struct polygon_score_data psd; + int i; + + memset(&psd, 0, sizeof(psd)); + psd.corners = corners; + + memcpy(&psd.ref, ref, sizeof(psd.ref)); + psd.scores[0] = -1; + flood_fill_seed(q, region->seed.x, region->seed.y, + rcode, QUIRC_PIXEL_BLACK, + find_one_corner, &psd, 0); + + psd.ref.x = psd.corners[0].x - psd.ref.x; + psd.ref.y = psd.corners[0].y - psd.ref.y; + + for (i = 0; i < 4; i++) + memcpy(&psd.corners[i], ®ion->seed, + sizeof(psd.corners[i])); + + i = region->seed.x * psd.ref.x + region->seed.y * psd.ref.y; + psd.scores[0] = i; + psd.scores[2] = -i; + i = region->seed.x * -psd.ref.y + region->seed.y * psd.ref.x; + psd.scores[1] = i; + psd.scores[3] = -i; + + flood_fill_seed(q, region->seed.x, region->seed.y, + QUIRC_PIXEL_BLACK, rcode, + find_other_corners, &psd, 0); +} + +static void record_capstone(struct quirc *q, int ring, int stone) +{ + struct quirc_region *stone_reg = &q->regions[stone]; + struct quirc_region *ring_reg = &q->regions[ring]; + struct quirc_capstone *capstone; + int cs_index; + + if (q->num_capstones >= QUIRC_MAX_CAPSTONES) + return; + + cs_index = q->num_capstones; + capstone = &q->capstones[q->num_capstones++]; + + memset(capstone, 0, sizeof(*capstone)); + + capstone->qr_grid = -1; + capstone->ring = ring; + capstone->stone = stone; + stone_reg->capstone = cs_index; + ring_reg->capstone = cs_index; + + /* Find the corners of the ring */ + find_region_corners(q, ring, &stone_reg->seed, capstone->corners); + + /* Set up the perspective transform and find the center */ + perspective_setup(capstone->c, capstone->corners, 7.0, 7.0); + perspective_map(capstone->c, 3.5, 3.5, &capstone->center); +} + +static void test_capstone(struct quirc *q, int x, int y, 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); + int ring_left = region_code(q, x - pb[4] - pb[3] - + pb[2] - pb[1] - pb[0], + y); + struct quirc_region *stone_reg; + struct quirc_region *ring_reg; + int ratio; + + if (ring_left < 0 || ring_right < 0 || stone < 0) + return; + + /* Left and ring of ring should be connected */ + if (ring_left != ring_right) + return; + + /* Ring should be disconnected from stone */ + if (ring_left == stone) + return; + + stone_reg = &q->regions[stone]; + ring_reg = &q->regions[ring_left]; + + /* Already detected */ + if (stone_reg->capstone >= 0 || ring_reg->capstone >= 0) + return; + + /* Ratio should ideally be 37.5 */ + ratio = stone_reg->count * 100 / ring_reg->count; + if (ratio < 10 || ratio > 70) + return; + + record_capstone(q, ring_left, stone); +} + +static void finder_scan(struct quirc *q, int y) +{ + quirc_pixel_t *row = q->pixels + y * q->w; + int x; + int last_color = 0; + int run_length = 0; + int run_count = 0; + int pb[5]; + + memset(pb, 0, sizeof(pb)); + for (x = 0; x < q->w; x++) { + int color = row[x] ? 1 : 0; + + if (x && color != last_color) { + memmove(pb, pb + 1, sizeof(pb[0]) * 4); + pb[4] = run_length; + run_length = 0; + run_count++; + + if (!color && run_count >= 5) { + static int check[5] = {1, 1, 3, 1, 1}; + int avg, err; + int i; + int ok = 1; + + avg = (pb[0] + pb[1] + pb[3] + pb[4]) / 4; + err = avg * 3 / 4; + + for (i = 0; i < 5; i++) + if (pb[i] < check[i] * avg - err || + pb[i] > check[i] * avg + err) + ok = 0; + + if (ok) + test_capstone(q, x, y, pb); + } + } + + run_length++; + last_color = color; + } +} + +static void find_alignment_pattern(struct quirc *q, int index) +{ + struct quirc_grid *qr = &q->grids[index]; + struct quirc_capstone *c0 = &q->capstones[qr->caps[0]]; + struct quirc_capstone *c2 = &q->capstones[qr->caps[2]]; + struct quirc_point a; + struct quirc_point b; + struct quirc_point c; + int size_estimate; + int step_size = 1; + int dir = 0; + double u, v; + + /* Grab our previous estimate of the alignment pattern corner */ + memcpy(&b, &qr->align, sizeof(b)); + + /* Guess another two corners of the alignment pattern so that we + * can estimate its size. + */ + perspective_unmap(c0->c, &b, &u, &v); + perspective_map(c0->c, u, v + 1.0, &a); + perspective_unmap(c2->c, &b, &u, &v); + perspective_map(c2->c, u + 1.0, v, &c); + + size_estimate = abs((a.x - b.x) * -(c.y - b.y) + + (a.y - b.y) * (c.x - b.x)); + + /* Spiral outwards from the estimate point until we find something + * roughly the right size. Don't look too far from the estimate + * point. + */ + while (step_size * step_size < size_estimate * 100) { + static const int dx_map[] = {1, 0, -1, 0}; + static const int dy_map[] = {0, -1, 0, 1}; + int i; + + for (i = 0; i < step_size; i++) { + int code = region_code(q, b.x, b.y); + + if (code >= 0) { + struct quirc_region *reg = &q->regions[code]; + + if (reg->count >= size_estimate / 2 && + reg->count <= size_estimate * 2) { + qr->align_region = code; + return; + } + } + + b.x += dx_map[dir]; + b.y += dy_map[dir]; + } + + dir = (dir + 1) % 4; + if (!(dir & 1)) + step_size++; + } +} + +static void find_leftmost_to_line(void *user_data, int y, int left, int right) +{ + struct polygon_score_data *psd = + (struct polygon_score_data *)user_data; + int xs[2] = {left, right}; + int i; + + for (i = 0; i < 2; i++) { + int d = -psd->ref.y * xs[i] + psd->ref.x * y; + + if (d < psd->scores[0]) { + psd->scores[0] = d; + psd->corners[0].x = xs[i]; + psd->corners[0].y = y; + } + } +} + +/* Do a Bresenham scan from one point to another and count the number + * of black/white transitions. + */ +static int timing_scan(const struct quirc *q, + const struct quirc_point *p0, + const struct quirc_point *p1) +{ + int n = p1->x - p0->x; + int d = p1->y - p0->y; + int x = p0->x; + int y = p0->y; + int *dom, *nondom; + int dom_step; + int nondom_step; + int a = 0; + int i; + int run_length = 0; + int count = 0; + + if (p0->x < 0 || p0->y < 0 || p0->x >= q->w || p0->y >= q->h) + return -1; + if (p1->x < 0 || p1->y < 0 || p1->x >= q->w || p1->y >= q->h) + return -1; + + if (abs(n) > abs(d)) { + int swap = n; + + n = d; + d = swap; + + dom = &x; + nondom = &y; + } else { + dom = &y; + nondom = &x; + } + + if (n < 0) { + n = -n; + nondom_step = -1; + } else { + nondom_step = 1; + } + + if (d < 0) { + d = -d; + dom_step = -1; + } else { + dom_step = 1; + } + + x = p0->x; + y = p0->y; + for (i = 0; i <= d; i++) { + int pixel; + + if (y < 0 || y >= q->h || x < 0 || x >= q->w) + break; + + pixel = q->pixels[y * q->w + x]; + + if (pixel) { + if (run_length >= 2) + count++; + run_length = 0; + } else { + run_length++; + } + + a += n; + *dom += dom_step; + if (a >= d) { + *nondom += nondom_step; + a -= d; + } + } + + return count; +} + +/* Try the measure the timing pattern for a given QR code. This does + * not require the global perspective to have been set up, but it + * does require that the capstone corners have been set to their + * canonical rotation. + * + * For each capstone, we find a point in the middle of the ring band + * which is nearest the centre of the code. Using these points, we do + * a horizontal and a vertical timing scan. + */ +static int measure_timing_pattern(struct quirc *q, int index) +{ + struct quirc_grid *qr = &q->grids[index]; + int i; + int scan; + int ver; + int size; + + for (i = 0; i < 3; i++) { + static const double us[] = {6.5, 6.5, 0.5}; + static const double vs[] = {0.5, 6.5, 6.5}; + struct quirc_capstone *cap = &q->capstones[qr->caps[i]]; + + perspective_map(cap->c, us[i], vs[i], &qr->tpep[i]); + } + + qr->hscan = timing_scan(q, &qr->tpep[1], &qr->tpep[2]); + qr->vscan = timing_scan(q, &qr->tpep[1], &qr->tpep[0]); + + scan = qr->hscan; + if (qr->vscan > scan) + scan = qr->vscan; + + /* If neither scan worked, we can't go any further. */ + if (scan < 0) + return -1; + + /* Choose the nearest allowable grid size */ + size = scan * 2 + 13; + ver = (size - 15) / 4; + if (ver > QUIRC_MAX_VERSION) { + return -1; + } + + qr->grid_size = ver * 4 + 17; + return 0; +} + +/* Read a cell from a grid using the currently set perspective + * transform. Returns +/- 1 for black/white, 0 for cells which are + * out of image bounds. + */ +static int read_cell(const struct quirc *q, int index, int x, int y) +{ + const struct quirc_grid *qr = &q->grids[index]; + struct quirc_point p; + + perspective_map(qr->c, x + 0.5, y + 0.5, &p); + if (p.y < 0 || p.y >= q->h || p.x < 0 || p.x >= q->w) + return 0; + + return q->pixels[p.y * q->w + p.x] ? 1 : -1; +} + +static int fitness_cell(const struct quirc *q, int index, int x, int y) +{ + const struct quirc_grid *qr = &q->grids[index]; + int score = 0; + int u, v; + + for (v = 0; v < 3; v++) + for (u = 0; u < 3; u++) { + static const double offsets[] = {0.3, 0.5, 0.7}; + struct quirc_point p; + + perspective_map(qr->c, x + offsets[u], + y + offsets[v], &p); + if (p.y < 0 || p.y >= q->h || p.x < 0 || p.x >= q->w) + continue; + + if (q->pixels[p.y * q->w + p.x]) + score++; + else + score--; + } + + return score; +} + +static int fitness_ring(const struct quirc *q, int index, int cx, int cy, + int radius) +{ + int i; + int score = 0; + + for (i = 0; i < radius * 2; i++) { + score += fitness_cell(q, index, cx - radius + i, cy - radius); + score += fitness_cell(q, index, cx - radius, cy + radius - i); + score += fitness_cell(q, index, cx + radius, cy - radius + i); + score += fitness_cell(q, index, cx + radius - i, cy + radius); + } + + return score; +} + +static int fitness_apat(const struct quirc *q, int index, int cx, int cy) +{ + return fitness_cell(q, index, cx, cy) - + fitness_ring(q, index, cx, cy, 1) + + fitness_ring(q, index, cx, cy, 2); +} + +static int fitness_capstone(const struct quirc *q, int index, int x, int y) +{ + x += 3; + y += 3; + + return fitness_cell(q, index, x, y) + + fitness_ring(q, index, x, y, 1) - + fitness_ring(q, index, x, y, 2) + + fitness_ring(q, index, x, y, 3); +} + +/* Compute a fitness score for the currently configured perspective + * transform, using the features we expect to find by scanning the + * grid. + */ +static int fitness_all(const struct quirc *q, int index) +{ + const struct quirc_grid *qr = &q->grids[index]; + int version = (qr->grid_size - 17) / 4; + const struct quirc_version_info *info = &quirc_version_db[version]; + int score = 0; + int i, j; + int ap_count; + + /* Check the timing pattern */ + for (i = 0; i < qr->grid_size - 14; i++) { + int expect = (i & 1) ? 1 : -1; + + score += fitness_cell(q, index, i + 7, 6) * expect; + score += fitness_cell(q, index, 6, i + 7) * expect; + } + + /* Check capstones */ + score += fitness_capstone(q, index, 0, 0); + score += fitness_capstone(q, index, qr->grid_size - 7, 0); + score += fitness_capstone(q, index, 0, qr->grid_size - 7); + + if (version < 0 || version > QUIRC_MAX_VERSION) + return score; + + /* Check alignment patterns */ + ap_count = 0; + while ((ap_count < QUIRC_MAX_ALIGNMENT) && info->apat[ap_count]) + ap_count++; + + for (i = 1; i + 1 < ap_count; i++) { + score += fitness_apat(q, index, 6, info->apat[i]); + score += fitness_apat(q, index, info->apat[i], 6); + } + + for (i = 1; i < ap_count; i++) + for (j = 1; j < ap_count; j++) + score += fitness_apat(q, index, + info->apat[i], info->apat[j]); + + return score; +} + +static void jiggle_perspective(struct quirc *q, int index) +{ + struct quirc_grid *qr = &q->grids[index]; + int best = fitness_all(q, index); + int pass; + double adjustments[8]; + int i; + + for (i = 0; i < 8; i++) + adjustments[i] = qr->c[i] * 0.02; + + for (pass = 0; pass < 5; pass++) { + for (i = 0; i < 16; i++) { + int j = i >> 1; + int test; + double old = qr->c[j]; + double step = adjustments[j]; + double new; + + if (i & 1) + new = old + step; + else + new = old - step; + + qr->c[j] = new; + test = fitness_all(q, index); + + if (test > best) + best = test; + else + qr->c[j] = old; + } + + for (i = 0; i < 8; i++) + adjustments[i] *= 0.5; + } +} + +/* Once the capstones are in place and an alignment point has been + * chosen, we call this function to set up a grid-reading perspective + * transform. + */ +static void setup_qr_perspective(struct quirc *q, int index) +{ + struct quirc_grid *qr = &q->grids[index]; + struct quirc_point rect[4]; + + /* Set up the perspective map for reading the grid */ + memcpy(&rect[0], &q->capstones[qr->caps[1]].corners[0], + sizeof(rect[0])); + memcpy(&rect[1], &q->capstones[qr->caps[2]].corners[0], + sizeof(rect[0])); + memcpy(&rect[2], &qr->align, sizeof(rect[0])); + memcpy(&rect[3], &q->capstones[qr->caps[0]].corners[0], + sizeof(rect[0])); + perspective_setup(qr->c, rect, qr->grid_size - 7, qr->grid_size - 7); + + jiggle_perspective(q, index); +} + +/* Rotate the capstone with so that corner 0 is the leftmost with respect + * to the given reference line. + */ +static void rotate_capstone(struct quirc_capstone *cap, + const struct quirc_point *h0, + const struct quirc_point *hd) +{ + struct quirc_point copy[4]; + int j; + int best = 0; + int best_score = INT_MAX; + + for (j = 0; j < 4; j++) { + struct quirc_point *p = &cap->corners[j]; + int score = (p->x - h0->x) * -hd->y + + (p->y - h0->y) * hd->x; + + if (!j || score < best_score) { + best = j; + best_score = score; + } + } + + /* Rotate the capstone */ + for (j = 0; j < 4; j++) + memcpy(©[j], &cap->corners[(j + best) % 4], + sizeof(copy[j])); + memcpy(cap->corners, copy, sizeof(cap->corners)); + perspective_setup(cap->c, cap->corners, 7.0, 7.0); +} + +static void record_qr_grid(struct quirc *q, int a, int b, int c) +{ + struct quirc_point h0, hd; + int i; + int qr_index; + struct quirc_grid *qr; + + if (q->num_grids >= QUIRC_MAX_GRIDS) + return; + + /* Construct the hypotenuse line from A to C. B should be to + * the left of this line. + */ + memcpy(&h0, &q->capstones[a].center, sizeof(h0)); + hd.x = q->capstones[c].center.x - q->capstones[a].center.x; + hd.y = q->capstones[c].center.y - q->capstones[a].center.y; + + /* Make sure A-B-C is clockwise */ + if ((q->capstones[b].center.x - h0.x) * -hd.y + + (q->capstones[b].center.y - h0.y) * hd.x > 0) { + int swap = a; + + a = c; + c = swap; + hd.x = -hd.x; + hd.y = -hd.y; + } + + /* Record the grid and its components */ + qr_index = q->num_grids; + qr = &q->grids[q->num_grids++]; + + memset(qr, 0, sizeof(*qr)); + qr->caps[0] = a; + qr->caps[1] = b; + qr->caps[2] = c; + qr->align_region = -1; + + /* Rotate each capstone so that corner 0 is top-left with respect + * to the grid. + */ + for (i = 0; i < 3; i++) { + struct quirc_capstone *cap = &q->capstones[qr->caps[i]]; + + rotate_capstone(cap, &h0, &hd); + cap->qr_grid = qr_index; + } + + /* Check the timing pattern. This doesn't require a perspective + * transform. + */ + if (measure_timing_pattern(q, qr_index) < 0) + goto fail; + + /* Make an estimate based for the alignment pattern based on extending + * lines from capstones A and C. + */ + if (!line_intersect(&q->capstones[a].corners[0], + &q->capstones[a].corners[1], + &q->capstones[c].corners[0], + &q->capstones[c].corners[3], + &qr->align)) + goto fail; + + /* On V2+ grids, we should use the alignment pattern. */ + if (qr->grid_size > 21) { + /* Try to find the actual location of the alignment pattern. */ + find_alignment_pattern(q, qr_index); + + /* Find the point of the alignment pattern closest to the + * top-left of the QR grid. + */ + if (qr->align_region >= 0) { + struct polygon_score_data psd; + struct quirc_region *reg = + &q->regions[qr->align_region]; + + /* Start from some point inside the alignment pattern */ + memcpy(&qr->align, ®->seed, sizeof(qr->align)); + + memcpy(&psd.ref, &hd, sizeof(psd.ref)); + psd.corners = &qr->align; + psd.scores[0] = -hd.y * qr->align.x + + hd.x * qr->align.y; + + flood_fill_seed(q, reg->seed.x, reg->seed.y, + qr->align_region, QUIRC_PIXEL_BLACK, + NULL, NULL, 0); + flood_fill_seed(q, reg->seed.x, reg->seed.y, + QUIRC_PIXEL_BLACK, qr->align_region, + find_leftmost_to_line, &psd, 0); + } + } + + setup_qr_perspective(q, qr_index); + return; + +fail: + /* We've been unable to complete setup for this grid. Undo what we've + * recorded and pretend it never happened. + */ + for (i = 0; i < 3; i++) + q->capstones[qr->caps[i]].qr_grid = -1; + q->num_grids--; +} + +struct neighbour { + int index; + double distance; +}; + +struct neighbour_list { + struct neighbour n[QUIRC_MAX_CAPSTONES]; + int count; +}; + +static void test_neighbours(struct quirc *q, int i, + const struct neighbour_list *hlist, + const struct neighbour_list *vlist) +{ + int j, k; + double best_score = 0.0; + int best_h = -1, best_v = -1; + + /* Test each possible grouping */ + for (j = 0; j < hlist->count; j++) + for (k = 0; k < vlist->count; k++) { + const struct neighbour *hn = &hlist->n[j]; + const struct neighbour *vn = &vlist->n[k]; + double score = fabs(1.0 - hn->distance / vn->distance); + + if (score > 2.5) + continue; + + if (best_h < 0 || score < best_score) { + best_h = hn->index; + best_v = vn->index; + best_score = score; + } + } + + if (best_h < 0 || best_v < 0) + return; + + record_qr_grid(q, best_h, i, best_v); +} + +static void test_grouping(struct quirc *q, int i) +{ + struct quirc_capstone *c1 = &q->capstones[i]; + int j; + struct neighbour_list hlist; + struct neighbour_list vlist; + + if (c1->qr_grid >= 0) + return; + + hlist.count = 0; + vlist.count = 0; + + /* Look for potential neighbours by examining the relative gradients + * from this capstone to others. + */ + for (j = 0; j < q->num_capstones; j++) { + struct quirc_capstone *c2 = &q->capstones[j]; + double u, v; + + if (i == j || c2->qr_grid >= 0) + continue; + + perspective_unmap(c1->c, &c2->center, &u, &v); + + u = fabs(u - 3.5); + v = fabs(v - 3.5); + + if (u < 0.2 * v) { + struct neighbour *n = &hlist.n[hlist.count++]; + + n->index = j; + n->distance = v; + } + + if (v < 0.2 * u) { + struct neighbour *n = &vlist.n[vlist.count++]; + + n->index = j; + n->distance = u; + } + } + + if (!(hlist.count && vlist.count)) + return; + + test_neighbours(q, i, &hlist, &vlist); +} + +static void pixels_setup(struct quirc *q, uint8_t threshold) +{ + if (QUIRC_PIXEL_ALIAS_IMAGE) { + q->pixels = (quirc_pixel_t *)q->image; + } + + uint8_t* source = q->image; + quirc_pixel_t* dest = q->pixels; + int length = q->w * q->h; + while (length--) { + uint8_t value = *source++; + *dest++ = (value < threshold) ? QUIRC_PIXEL_BLACK : QUIRC_PIXEL_WHITE; + } +} + +uint8_t *quirc_begin(struct quirc *q, int *w, int *h) +{ + q->num_regions = QUIRC_PIXEL_REGION; + q->num_capstones = 0; + q->num_grids = 0; + + if (w) + *w = q->w; + if (h) + *h = q->h; + + return q->image; +} + +void quirc_end(struct quirc *q) +{ + int i; + + uint8_t threshold = otsu(q); + pixels_setup(q, threshold); + + for (i = 0; i < q->h; i++) + finder_scan(q, i); + + for (i = 0; i < q->num_capstones; i++) + test_grouping(q, i); +} + +void quirc_extract(const struct quirc *q, int index, + struct quirc_code *code) +{ + const struct quirc_grid *qr = &q->grids[index]; + int y; + int i = 0; + + if (index < 0 || index > q->num_grids) + return; + + memset(code, 0, sizeof(*code)); + + perspective_map(qr->c, 0.0, 0.0, &code->corners[0]); + perspective_map(qr->c, qr->grid_size, 0.0, &code->corners[1]); + perspective_map(qr->c, qr->grid_size, qr->grid_size, + &code->corners[2]); + perspective_map(qr->c, 0.0, qr->grid_size, &code->corners[3]); + + code->size = qr->grid_size; + + 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) { + code->cell_bitmap[i >> 3] |= (1 << (i & 7)); + } + i++; + } + } +} diff --git a/programs/media/qr_tool/lib/quirc.c b/programs/media/qr_tool/lib/quirc.c new file mode 100644 index 0000000000..bbe1fe961c --- /dev/null +++ b/programs/media/qr_tool/lib/quirc.c @@ -0,0 +1,130 @@ +/* quirc -- QR-code recognition library + * Copyright (C) 2010-2012 Daniel Beer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include "quirc_internal.h" + +const char *quirc_version(void) +{ + return "1.0"; +} + +struct quirc *quirc_new(void) +{ + struct quirc *q = malloc(sizeof(*q)); + + if (!q) + return NULL; + + memset(q, 0, sizeof(*q)); + return q; +} + +void quirc_destroy(struct quirc *q) +{ + free(q->image); + /* q->pixels may alias q->image when their type representation is of the + same size, so we need to be careful here to avoid a double free */ + if (!QUIRC_PIXEL_ALIAS_IMAGE) + free(q->pixels); + free(q); +} + +int quirc_resize(struct quirc *q, int w, int h) +{ + uint8_t *image = NULL; + quirc_pixel_t *pixels = NULL; + + /* + * XXX: w and h should be size_t (or at least unsigned) as negatives + * values would not make much sense. The downside is that it would break + * both the API and ABI. Thus, at the moment, let's just do a sanity + * check. + */ + if (w < 0 || h < 0) + goto fail; + + /* + * alloc a new buffer for q->image. We avoid realloc(3) because we want + * on failure to be leave `q` in a consistant, unmodified state. + */ + image = calloc(w, h); + if (!image) + goto fail; + + /* compute the "old" (i.e. currently allocated) and the "new" + (i.e. requested) image dimensions */ + size_t olddim = q->w * q->h; + size_t newdim = w * h; + size_t min = (olddim < newdim ? olddim : newdim); + + /* + * copy the data into the new buffer, avoiding (a) to read beyond the + * old buffer when the new size is greater and (b) to write beyond the + * new buffer when the new size is smaller, hence the min computation. + */ + (void)memcpy(image, q->image, min); + + /* alloc a new buffer for q->pixels if needed */ + if (!QUIRC_PIXEL_ALIAS_IMAGE) { + pixels = calloc(newdim, sizeof(quirc_pixel_t)); + if (!pixels) + goto fail; + } + + /* alloc succeeded, update `q` with the new size and buffers */ + q->w = w; + q->h = h; + free(q->image); + q->image = image; + if (!QUIRC_PIXEL_ALIAS_IMAGE) { + free(q->pixels); + q->pixels = pixels; + } + + return 0; + /* NOTREACHED */ +fail: + free(image); + free(pixels); + + return -1; +} + +int quirc_count(const struct quirc *q) +{ + return q->num_grids; +} + +static const char *const error_table[] = { + [QUIRC_SUCCESS] = "Success", + [QUIRC_ERROR_INVALID_GRID_SIZE] = "Invalid grid size", + [QUIRC_ERROR_INVALID_VERSION] = "Invalid version", + [QUIRC_ERROR_FORMAT_ECC] = "Format data ECC failure", + [QUIRC_ERROR_DATA_ECC] = "ECC failure", + [QUIRC_ERROR_UNKNOWN_DATA_TYPE] = "Unknown data type", + [QUIRC_ERROR_DATA_OVERFLOW] = "Data overflow", + [QUIRC_ERROR_DATA_UNDERFLOW] = "Data underflow" +}; + +const char *quirc_strerror(quirc_decode_error_t err) +{ + if (err >= 0 && err < sizeof(error_table) / sizeof(error_table[0])) + return error_table[err]; + + return "Unknown error"; +} diff --git a/programs/media/qr_tool/lib/quirc.h b/programs/media/qr_tool/lib/quirc.h new file mode 100644 index 0000000000..f21833317b --- /dev/null +++ b/programs/media/qr_tool/lib/quirc.h @@ -0,0 +1,185 @@ +/* quirc -- QR-code recognition library + * Copyright (C) 2010-2012 Daniel Beer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef QUIRC_H_ +#define QUIRC_H_ + +#include + +/*#ifndef UINT8_MAX +#define UINT8_MAX (255) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535) +#endif*/ + +#ifdef __cplusplus +extern "C" { +#endif + +struct quirc; + +/* Obtain the library version string. */ +const char *quirc_version(void); + +/* Construct a new QR-code recognizer. This function will return NULL + * if sufficient memory could not be allocated. + */ +struct quirc *quirc_new(void); + +/* Destroy a QR-code recognizer. */ +void quirc_destroy(struct quirc *q); + +/* Resize the QR-code recognizer. The size of an image must be + * specified before codes can be analyzed. + * + * This function returns 0 on success, or -1 if sufficient memory could + * not be allocated. + */ +int quirc_resize(struct quirc *q, int w, int h); + +/* These functions are used to process images for QR-code recognition. + * quirc_begin() must first be called to obtain access to a buffer into + * which the input image should be placed. Optionally, the current + * width and height may be returned. + * + * After filling the buffer, quirc_end() should be called to process + * the image for QR-code recognition. The locations and content of each + * code may be obtained using accessor functions described below. + */ +uint8_t *quirc_begin(struct quirc *q, int *w, int *h); +void quirc_end(struct quirc *q); + +/* This structure describes a location in the input image buffer. */ +struct quirc_point { + int x; + int y; +}; + +/* This enum describes the various decoder errors which may occur. */ +typedef enum { + QUIRC_SUCCESS = 0, + QUIRC_ERROR_INVALID_GRID_SIZE, + QUIRC_ERROR_INVALID_VERSION, + QUIRC_ERROR_FORMAT_ECC, + QUIRC_ERROR_DATA_ECC, + QUIRC_ERROR_UNKNOWN_DATA_TYPE, + QUIRC_ERROR_DATA_OVERFLOW, + QUIRC_ERROR_DATA_UNDERFLOW +} quirc_decode_error_t; + +/* Return a string error message for an error code. */ +const char *quirc_strerror(quirc_decode_error_t err); + +/* Limits on the maximum size of QR-codes and their content. */ +#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. */ +#define QUIRC_ECC_LEVEL_M 0 +#define QUIRC_ECC_LEVEL_L 1 +#define QUIRC_ECC_LEVEL_H 2 +#define QUIRC_ECC_LEVEL_Q 3 + +/* QR-code data types. */ +#define QUIRC_DATA_TYPE_NUMERIC 1 +#define QUIRC_DATA_TYPE_ALPHA 2 +#define QUIRC_DATA_TYPE_BYTE 4 +#define QUIRC_DATA_TYPE_KANJI 8 + +/* Common character encodings */ +#define QUIRC_ECI_ISO_8859_1 1 +#define QUIRC_ECI_IBM437 2 +#define QUIRC_ECI_ISO_8859_2 4 +#define QUIRC_ECI_ISO_8859_3 5 +#define QUIRC_ECI_ISO_8859_4 6 +#define QUIRC_ECI_ISO_8859_5 7 +#define QUIRC_ECI_ISO_8859_6 8 +#define QUIRC_ECI_ISO_8859_7 9 +#define QUIRC_ECI_ISO_8859_8 10 +#define QUIRC_ECI_ISO_8859_9 11 +#define QUIRC_ECI_WINDOWS_874 13 +#define QUIRC_ECI_ISO_8859_13 15 +#define QUIRC_ECI_ISO_8859_15 17 +#define QUIRC_ECI_SHIFT_JIS 20 +#define QUIRC_ECI_UTF_8 26 + +/* This structure is used to return information about detected QR codes + * in the input image. + */ +struct quirc_code { + /* The four corners of the QR-code, from top left, clockwise */ + struct quirc_point corners[4]; + + /* The number of cells across in the QR-code. The cell bitmap + * is a bitmask giving the actual values of cells. If the cell + * at (x, y) is black, then the following bit is set: + * + * cell_bitmap[i >> 3] & (1 << (i & 7)) + * + * where i = (y * size) + x. + */ + int size; + uint8_t cell_bitmap[QUIRC_MAX_BITMAP]; +}; + +/* This structure holds the decoded QR-code data */ +struct quirc_data { + /* Various parameters of the QR-code. These can mostly be + * ignored if you only care about the data. + */ + int version; + int ecc_level; + int mask; + + /* This field is the highest-valued data type found in the QR + * code. + */ + int data_type; + + /* Data payload. For the Kanji datatype, payload is encoded as + * Shift-JIS. For all other datatypes, payload is ASCII text. + */ + uint8_t payload[QUIRC_MAX_PAYLOAD]; + int payload_len; + + /* ECI assignment number */ + uint32_t eci; +}; + +/* Return the number of QR-codes identified in the last processed + * image. + */ +int quirc_count(const struct quirc *q); + +/* Extract the QR-code specified by the given index. */ +void quirc_extract(const struct quirc *q, int index, + struct quirc_code *code); + +/* Decode a QR-code, returning the payload data. */ +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 + +#endif diff --git a/programs/media/qr_tool/lib/quirc_internal.h b/programs/media/qr_tool/lib/quirc_internal.h new file mode 100644 index 0000000000..371572f113 --- /dev/null +++ b/programs/media/qr_tool/lib/quirc_internal.h @@ -0,0 +1,115 @@ +/* quirc -- QR-code recognition library + * Copyright (C) 2010-2012 Daniel Beer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef QUIRC_INTERNAL_H_ +#define QUIRC_INTERNAL_H_ + +#include "quirc.h" + +#define QUIRC_PIXEL_WHITE 0 +#define QUIRC_PIXEL_BLACK 1 +#define QUIRC_PIXEL_REGION 2 + +#ifndef QUIRC_MAX_REGIONS +#define QUIRC_MAX_REGIONS 254 +#endif +#define QUIRC_MAX_CAPSTONES 32 +#define QUIRC_MAX_GRIDS 8 +#define QUIRC_PERSPECTIVE_PARAMS 8 + +#if QUIRC_MAX_REGIONS < UINT8_MAX +#define QUIRC_PIXEL_ALIAS_IMAGE 1 +typedef uint8_t quirc_pixel_t; +#elif QUIRC_MAX_REGIONS < UINT16_MAX +#define QUIRC_PIXEL_ALIAS_IMAGE 0 +typedef uint16_t quirc_pixel_t; +#else +#error "QUIRC_MAX_REGIONS > 65534 is not supported" +#endif + +struct quirc_region { + struct quirc_point seed; + int count; + int capstone; +}; + +struct quirc_capstone { + int ring; + int stone; + + struct quirc_point corners[4]; + struct quirc_point center; + double c[QUIRC_PERSPECTIVE_PARAMS]; + + int qr_grid; +}; + +struct quirc_grid { + /* Capstone indices */ + int caps[3]; + + /* Alignment pattern region and corner */ + int align_region; + struct quirc_point align; + + /* Timing pattern endpoints */ + struct quirc_point tpep[3]; + int hscan; + int vscan; + + /* Grid size and perspective transform */ + int grid_size; + double c[QUIRC_PERSPECTIVE_PARAMS]; +}; + +struct quirc { + uint8_t *image; + quirc_pixel_t *pixels; + int w; + int h; + + int num_regions; + struct quirc_region regions[QUIRC_MAX_REGIONS]; + + int num_capstones; + struct quirc_capstone capstones[QUIRC_MAX_CAPSTONES]; + + int num_grids; + struct quirc_grid grids[QUIRC_MAX_GRIDS]; +}; + +/************************************************************************ + * QR-code version information database + */ + +#define QUIRC_MAX_VERSION 40 +#define QUIRC_MAX_ALIGNMENT 7 + +struct quirc_rs_params { + int bs; /* Small block size */ + int dw; /* Small data words */ + int ns; /* Number of small blocks */ +}; + +struct quirc_version_info { + int data_bytes; + int apat[QUIRC_MAX_ALIGNMENT]; + struct quirc_rs_params ecc[4]; +}; + +extern const struct quirc_version_info quirc_version_db[QUIRC_MAX_VERSION + 1]; + +#endif diff --git a/programs/media/qr_tool/lib/version_db.c b/programs/media/qr_tool/lib/version_db.c new file mode 100644 index 0000000000..fea8146dc6 --- /dev/null +++ b/programs/media/qr_tool/lib/version_db.c @@ -0,0 +1,421 @@ +/* quirc -- QR-code recognition library + * Copyright (C) 2010-2012 Daniel Beer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "quirc_internal.h" + +const struct quirc_version_info quirc_version_db[QUIRC_MAX_VERSION + 1] = { + {0}, + { /* Version 1 */ + .data_bytes = 26, + .apat = {0}, + .ecc = { + {.bs = 26, .dw = 16, .ns = 1}, + {.bs = 26, .dw = 19, .ns = 1}, + {.bs = 26, .dw = 9, .ns = 1}, + {.bs = 26, .dw = 13, .ns = 1} + } + }, + { /* Version 2 */ + .data_bytes = 44, + .apat = {6, 18, 0}, + .ecc = { + {.bs = 44, .dw = 28, .ns = 1}, + {.bs = 44, .dw = 34, .ns = 1}, + {.bs = 44, .dw = 16, .ns = 1}, + {.bs = 44, .dw = 22, .ns = 1} + } + }, + { /* Version 3 */ + .data_bytes = 70, + .apat = {6, 22, 0}, + .ecc = { + {.bs = 70, .dw = 44, .ns = 1}, + {.bs = 70, .dw = 55, .ns = 1}, + {.bs = 35, .dw = 13, .ns = 2}, + {.bs = 35, .dw = 17, .ns = 2} + } + }, + { /* Version 4 */ + .data_bytes = 100, + .apat = {6, 26, 0}, + .ecc = { + {.bs = 50, .dw = 32, .ns = 2}, + {.bs = 100, .dw = 80, .ns = 1}, + {.bs = 25, .dw = 9, .ns = 4}, + {.bs = 50, .dw = 24, .ns = 2} + } + }, + { /* Version 5 */ + .data_bytes = 134, + .apat = {6, 30, 0}, + .ecc = { + {.bs = 67, .dw = 43, .ns = 2}, + {.bs = 134, .dw = 108, .ns = 1}, + {.bs = 33, .dw = 11, .ns = 2}, + {.bs = 33, .dw = 15, .ns = 2} + } + }, + { /* Version 6 */ + .data_bytes = 172, + .apat = {6, 34, 0}, + .ecc = { + {.bs = 43, .dw = 27, .ns = 4}, + {.bs = 86, .dw = 68, .ns = 2}, + {.bs = 43, .dw = 15, .ns = 4}, + {.bs = 43, .dw = 19, .ns = 4} + } + }, + { /* Version 7 */ + .data_bytes = 196, + .apat = {6, 22, 38, 0}, + .ecc = { + {.bs = 49, .dw = 31, .ns = 4}, + {.bs = 98, .dw = 78, .ns = 2}, + {.bs = 39, .dw = 13, .ns = 4}, + {.bs = 32, .dw = 14, .ns = 2} + } + }, + { /* Version 8 */ + .data_bytes = 242, + .apat = {6, 24, 42, 0}, + .ecc = { + {.bs = 60, .dw = 38, .ns = 2}, + {.bs = 121, .dw = 97, .ns = 2}, + {.bs = 40, .dw = 14, .ns = 4}, + {.bs = 40, .dw = 18, .ns = 4} + } + }, + { /* Version 9 */ + .data_bytes = 292, + .apat = {6, 26, 46, 0}, + .ecc = { + {.bs = 58, .dw = 36, .ns = 3}, + {.bs = 146, .dw = 116, .ns = 2}, + {.bs = 36, .dw = 12, .ns = 4}, + {.bs = 36, .dw = 16, .ns = 4} + } + }, + { /* Version 10 */ + .data_bytes = 346, + .apat = {6, 28, 50, 0}, + .ecc = { + {.bs = 69, .dw = 43, .ns = 4}, + {.bs = 86, .dw = 68, .ns = 2}, + {.bs = 43, .dw = 15, .ns = 6}, + {.bs = 43, .dw = 19, .ns = 6} + } + }, + { /* Version 11 */ + .data_bytes = 404, + .apat = {6, 30, 54, 0}, + .ecc = { + {.bs = 80, .dw = 50, .ns = 1}, + {.bs = 101, .dw = 81, .ns = 4}, + {.bs = 36, .dw = 12, .ns = 3}, + {.bs = 50, .dw = 22, .ns = 4} + } + }, + { /* Version 12 */ + .data_bytes = 466, + .apat = {6, 32, 58, 0}, + .ecc = { + {.bs = 58, .dw = 36, .ns = 6}, + {.bs = 116, .dw = 92, .ns = 2}, + {.bs = 42, .dw = 14, .ns = 7}, + {.bs = 46, .dw = 20, .ns = 4} + } + }, + { /* Version 13 */ + .data_bytes = 532, + .apat = {6, 34, 62, 0}, + .ecc = { + {.bs = 59, .dw = 37, .ns = 8}, + {.bs = 133, .dw = 107, .ns = 4}, + {.bs = 33, .dw = 11, .ns = 12}, + {.bs = 44, .dw = 20, .ns = 8} + } + }, + { /* Version 14 */ + .data_bytes = 581, + .apat = {6, 26, 46, 66, 0}, + .ecc = { + {.bs = 64, .dw = 40, .ns = 4}, + {.bs = 145, .dw = 115, .ns = 3}, + {.bs = 36, .dw = 12, .ns = 11}, + {.bs = 36, .dw = 16, .ns = 11} + } + }, + { /* Version 15 */ + .data_bytes = 655, + .apat = {6, 26, 48, 70, 0}, + .ecc = { + {.bs = 65, .dw = 41, .ns = 5}, + {.bs = 109, .dw = 87, .ns = 5}, + {.bs = 36, .dw = 12, .ns = 11}, + {.bs = 54, .dw = 24, .ns = 5} + } + }, + { /* Version 16 */ + .data_bytes = 733, + .apat = {6, 26, 50, 74, 0}, + .ecc = { + {.bs = 73, .dw = 45, .ns = 7}, + {.bs = 122, .dw = 98, .ns = 5}, + {.bs = 45, .dw = 15, .ns = 3}, + {.bs = 43, .dw = 19, .ns = 15} + } + }, + { /* Version 17 */ + .data_bytes = 815, + .apat = {6, 30, 54, 78, 0}, + .ecc = { + {.bs = 74, .dw = 46, .ns = 10}, + {.bs = 135, .dw = 107, .ns = 1}, + {.bs = 42, .dw = 14, .ns = 2}, + {.bs = 50, .dw = 22, .ns = 1} + } + }, + { /* Version 18 */ + .data_bytes = 901, + .apat = {6, 30, 56, 82, 0}, + .ecc = { + {.bs = 69, .dw = 43, .ns = 9}, + {.bs = 150, .dw = 120, .ns = 5}, + {.bs = 42, .dw = 14, .ns = 2}, + {.bs = 50, .dw = 22, .ns = 17} + } + }, + { /* Version 19 */ + .data_bytes = 991, + .apat = {6, 30, 58, 86, 0}, + .ecc = { + {.bs = 70, .dw = 44, .ns = 3}, + {.bs = 141, .dw = 113, .ns = 3}, + {.bs = 39, .dw = 13, .ns = 9}, + {.bs = 47, .dw = 21, .ns = 17} + } + }, + { /* Version 20 */ + .data_bytes = 1085, + .apat = {6, 34, 62, 90, 0}, + .ecc = { + {.bs = 67, .dw = 41, .ns = 3}, + {.bs = 135, .dw = 107, .ns = 3}, + {.bs = 43, .dw = 15, .ns = 15}, + {.bs = 54, .dw = 24, .ns = 15} + } + }, + { /* Version 21 */ + .data_bytes = 1156, + .apat = {6, 28, 50, 72, 92, 0}, + .ecc = { + {.bs = 68, .dw = 42, .ns = 17}, + {.bs = 144, .dw = 116, .ns = 4}, + {.bs = 46, .dw = 16, .ns = 19}, + {.bs = 50, .dw = 22, .ns = 17} + } + }, + { /* Version 22 */ + .data_bytes = 1258, + .apat = {6, 26, 50, 74, 98, 0}, + .ecc = { + {.bs = 74, .dw = 46, .ns = 17}, + {.bs = 139, .dw = 111, .ns = 2}, + {.bs = 37, .dw = 13, .ns = 34}, + {.bs = 54, .dw = 24, .ns = 7} + } + }, + { /* Version 23 */ + .data_bytes = 1364, + .apat = {6, 30, 54, 78, 102, 0}, + .ecc = { + {.bs = 75, .dw = 47, .ns = 4}, + {.bs = 151, .dw = 121, .ns = 4}, + {.bs = 45, .dw = 15, .ns = 16}, + {.bs = 54, .dw = 24, .ns = 11} + } + }, + { /* Version 24 */ + .data_bytes = 1474, + .apat = {6, 28, 54, 80, 106, 0}, + .ecc = { + {.bs = 73, .dw = 45, .ns = 6}, + {.bs = 147, .dw = 117, .ns = 6}, + {.bs = 46, .dw = 16, .ns = 30}, + {.bs = 54, .dw = 24, .ns = 11} + } + }, + { /* Version 25 */ + .data_bytes = 1588, + .apat = {6, 32, 58, 84, 110, 0}, + .ecc = { + {.bs = 75, .dw = 47, .ns = 8}, + {.bs = 132, .dw = 106, .ns = 8}, + {.bs = 45, .dw = 15, .ns = 22}, + {.bs = 54, .dw = 24, .ns = 7} + } + }, + { /* Version 26 */ + .data_bytes = 1706, + .apat = {6, 30, 58, 86, 114, 0}, + .ecc = { + {.bs = 74, .dw = 46, .ns = 19}, + {.bs = 142, .dw = 114, .ns = 10}, + {.bs = 46, .dw = 16, .ns = 33}, + {.bs = 50, .dw = 22, .ns = 28} + } + }, + { /* Version 27 */ + .data_bytes = 1828, + .apat = {6, 34, 62, 90, 118, 0}, + .ecc = { + {.bs = 73, .dw = 45, .ns = 22}, + {.bs = 152, .dw = 122, .ns = 8}, + {.bs = 45, .dw = 15, .ns = 12}, + {.bs = 53, .dw = 23, .ns = 8} + } + }, + { /* Version 28 */ + .data_bytes = 1921, + .apat = {6, 26, 50, 74, 98, 122, 0}, + .ecc = { + {.bs = 73, .dw = 45, .ns = 3}, + {.bs = 147, .dw = 117, .ns = 3}, + {.bs = 45, .dw = 15, .ns = 11}, + {.bs = 54, .dw = 24, .ns = 4} + } + }, + { /* Version 29 */ + .data_bytes = 2051, + .apat = {6, 30, 54, 78, 102, 126, 0}, + .ecc = { + {.bs = 73, .dw = 45, .ns = 21}, + {.bs = 146, .dw = 116, .ns = 7}, + {.bs = 45, .dw = 15, .ns = 19}, + {.bs = 53, .dw = 23, .ns = 1} + } + }, + { /* Version 30 */ + .data_bytes = 2185, + .apat = {6, 26, 52, 78, 104, 130, 0}, + .ecc = { + {.bs = 75, .dw = 47, .ns = 19}, + {.bs = 145, .dw = 115, .ns = 5}, + {.bs = 45, .dw = 15, .ns = 23}, + {.bs = 54, .dw = 24, .ns = 15} + } + }, + { /* Version 31 */ + .data_bytes = 2323, + .apat = {6, 30, 56, 82, 108, 134, 0}, + .ecc = { + {.bs = 74, .dw = 46, .ns = 2}, + {.bs = 145, .dw = 115, .ns = 13}, + {.bs = 45, .dw = 15, .ns = 23}, + {.bs = 54, .dw = 24, .ns = 42} + } + }, + { /* Version 32 */ + .data_bytes = 2465, + .apat = {6, 34, 60, 86, 112, 138, 0}, + .ecc = { + {.bs = 74, .dw = 46, .ns = 10}, + {.bs = 145, .dw = 115, .ns = 17}, + {.bs = 45, .dw = 15, .ns = 19}, + {.bs = 54, .dw = 24, .ns = 10} + } + }, + { /* Version 33 */ + .data_bytes = 2611, + .apat = {6, 30, 58, 86, 114, 142, 0}, + .ecc = { + {.bs = 74, .dw = 46, .ns = 14}, + {.bs = 145, .dw = 115, .ns = 17}, + {.bs = 45, .dw = 15, .ns = 11}, + {.bs = 54, .dw = 24, .ns = 29} + } + }, + { /* Version 34 */ + .data_bytes = 2761, + .apat = {6, 34, 62, 90, 118, 146, 0}, + .ecc = { + {.bs = 74, .dw = 46, .ns = 14}, + {.bs = 145, .dw = 115, .ns = 13}, + {.bs = 46, .dw = 16, .ns = 59}, + {.bs = 54, .dw = 24, .ns = 44} + } + }, + { /* Version 35 */ + .data_bytes = 2876, + .apat = {6, 30, 54, 78, 102, 126, 150}, + .ecc = { + {.bs = 75, .dw = 47, .ns = 12}, + {.bs = 151, .dw = 121, .ns = 12}, + {.bs = 45, .dw = 15, .ns = 22}, + {.bs = 54, .dw = 24, .ns = 39} + } + }, + { /* Version 36 */ + .data_bytes = 3034, + .apat = {6, 24, 50, 76, 102, 128, 154}, + .ecc = { + {.bs = 75, .dw = 47, .ns = 6}, + {.bs = 151, .dw = 121, .ns = 6}, + {.bs = 45, .dw = 15, .ns = 2}, + {.bs = 54, .dw = 24, .ns = 46} + } + }, + { /* Version 37 */ + .data_bytes = 3196, + .apat = {6, 28, 54, 80, 106, 132, 158}, + .ecc = { + {.bs = 74, .dw = 46, .ns = 29}, + {.bs = 152, .dw = 122, .ns = 17}, + {.bs = 45, .dw = 15, .ns = 24}, + {.bs = 54, .dw = 24, .ns = 49} + } + }, + { /* Version 38 */ + .data_bytes = 3362, + .apat = {6, 32, 58, 84, 110, 136, 162}, + .ecc = { + {.bs = 74, .dw = 46, .ns = 13}, + {.bs = 152, .dw = 122, .ns = 4}, + {.bs = 45, .dw = 15, .ns = 42}, + {.bs = 54, .dw = 24, .ns = 48} + } + }, + { /* Version 39 */ + .data_bytes = 3532, + .apat = {6, 26, 54, 82, 110, 138, 166}, + .ecc = { + {.bs = 75, .dw = 47, .ns = 40}, + {.bs = 147, .dw = 117, .ns = 20}, + {.bs = 45, .dw = 15, .ns = 10}, + {.bs = 54, .dw = 24, .ns = 43} + } + }, + { /* Version 40 */ + .data_bytes = 3706, + .apat = {6, 30, 58, 86, 114, 142, 170}, + .ecc = { + {.bs = 75, .dw = 47, .ns = 18}, + {.bs = 148, .dw = 118, .ns = 19}, + {.bs = 45, .dw = 15, .ns = 20}, + {.bs = 54, .dw = 24, .ns = 34} + } + } +}; diff --git a/programs/media/qr_tool/libquirc.a b/programs/media/qr_tool/libquirc.a new file mode 100644 index 0000000000000000000000000000000000000000..d580b347763311a8be6e662a753761b5885a9a12 GIT binary patch literal 30460 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 g^PzwEXTOL=z3?HO5n?1C^rKdTKaE z^d!RRVIZ~G;w`ptwY|35LR$(!Q8P(McvA!Tk`NLQQO_`_5fQ?Rcugu6U#|8&Sd0Q61ogAW@-O}_U&^)3fU z(@R~V^dS}t3ICnSP*32E~tX6uizrV2gFrpm@{Hw2jtgNa46y8+!*iS5; zXvh=6i;#;@gy2J{MOcOKBElAgc7($SeFzSl#S)K@gph$S31J$74`C_7Dufpi8W6T2 zv?J_CIE-){!4_k&I1xMuX$Uz8c?i=GN)f6N>Jio;tVd`;*pJYM;2eUs5fTv65GEl^ zMJPw8MOcHd9$^c@HiUN&4kGj+IAfuw2nh(O2w4bu2t^202(<_+5ne#pfY5^Q4#HuC z;|O*;+D1q~$UxvFl)|>Hy*~RnEOFv%buPn9MHPkf##jc@0tq1M>&wc0v&!Z~fGmU3MoabemD4NAN=qLrtSFc( zFtOg?JQHvrZ|1ka%`7nCiXQX(d}x$Z5to@Z%O5YE<1b)y20$cj07TM-(|xn%7W%-B z`U$g2i^o@#&8sYqR68%v#61{}to4-^6czdk3&=atVy35;78h0^1L;|FO3DfbrG-S0 zZ=w`+*cB7jKc}e7z$NNd6i3o?%8H5$3X6&eE8-L%2{7PA-i&X_D=IE6W=BzPy1$~} zvEmt#_#8^#$S*Ihm>a=8y|k>dxS*`Oc#eP-xJ!%YqC?XoFiaN}52PP2n_FDqFOQUq zbWzznpkUhqM|s(-0@BFN4W`@7CqZW6g>?q?wXzURJzo4c+bS(9EHdLnjqHNTSx*#O zatnPkC(kUKS5RJ2TwYi~QP)TE4E^vG7ZpII3M&1NKL*;t1N3)rUMV^?C~tb%9HcSS z<`?)1A1f6&2|vAXP63G$ePY_w0mLKxXWY$q70@#yZ@M2k zV#dqLg-tQj<%N}E44LVO{u=NR{f(r-J{TbrzGT*%BC}6yACf99oUWG(`lgqamH~+g zGo!4)S5{D7Rym8d$5LNXQTTYV!PWALvKbY{m6d~rfBoP*Gj1uVum6UB@aWKQ@eigT z^Z%ZIun4Wax(OefSK;H8JecJgvt|{Rmd((I)1Y*vudqVc@tIl=;jr3fn#)*!r$ z(15TDp#$MC!f^x(d{h^L8zBiH4Iu}CfAslHdyEEXWGZGnW~m>DkFhY0`3}>to?kp? zAU?gKSU3|pOr@`)Y`y_6;svFJ^Er(OeACPPbA-54L61g9Q1 zOLcg2E+H(YB4J1wH|8kOKI|c;LfuVZS>^OXOd=)q1KxfB9B^A|g8h=kQuBeOx4-{Q zFV-CC)?m@|bS{gO5#*h+tLCT$PICi?r}E+Ee*C@ZRWq+DdL9{;nidv@3a5eXcbB<~ z=al(p%yeTaE~}XDE-9;UV~|as?Jg`WEt_5_Ot)C0O{SbIuW(7A?0wT)|L`NtT#x9v zT+73 zX{15`a=n34J20tt37AK9Wb#pG!*zNY`xR_-D%pK%cAwnhG&)X*gaL>Pidjm#_oi3L zKC5P*-A-mPlbrfS=0$@tgPL6cPnM6`(50o>eGRvv_*F7q+Bl_8*^RDXajTO7pzx&t z3*=grDRyb2MS8tav3ayt!AWEzqqfn1pk83CIhq3`fozA8eO%2xu4H-a0Kvl+sN04` zAM1tZ0V0z64Y25S0a#yeNIaS3U{PnmQuGzFAi`VuXabhfTzYCzRnQ2w6Hx;zBz#

_An!nopY596;Dr*0ie@Wva)aO!C;3 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 literal 0 HcmV?d00001 diff --git a/programs/media/qr_tool/qr_tool.c b/programs/media/qr_tool/qr_tool.c new file mode 100644 index 0000000000..7e946c8fc2 --- /dev/null +++ b/programs/media/qr_tool/qr_tool.c @@ -0,0 +1,313 @@ +/* Copyright (C) 2021- Rustem Gimadutdinov (rgimad), GPLv2 */ +#include +#include +#include +#include +#include + +#include "kolibri_gui.h" +#include "kolibri_opendialog.h" +#include "kolibri_libimg.h" + +#include "quirc.h" + +/* ------------------------------------------------------- */ + +#define X_W(X, W) ((X<<16)+W) +#define Y_H X_W + +#define WINDOW_WIDTH 650 +#define WINDOW_HEIGHT 320 + +#define WINDOW_ROW1_Y 34 +#define WINDOW_COL2_X 280 + +#define TEDIT1_MAXCHAR 2048 +#define TEDIT1_W 340 +#define TEDIT1_H 150 + +#define IMG1_DISPLAY_W 256 +#define IMG1_DISPLAY_H 256 + +const char WINDOW_TITLE[] = "QR Tool 0.2b"; + +kolibri_system_colors sys_color_table; +const color_t DRAWTEXT_FLAG_DEFAULT = 0x90000000; + +enum MYCOLORS { + COL_GREEN = 0x067D06, + COL_BLUE = 0x0000FF, + COL_RED = 0xFF0000, + COL_BLACK = 0x000000, + COL_WHITE = 0xFFFFFF, + COL_GREY = 0x919191 +}; + +enum MYBUTTONS { + MYBTN_QUIT = 1, + MYBTN_OPEN = 10/*, + MYBTN_CLEAR = 20*/ + // +}; + +editor *tedit1; +void *tedit1_lock; + +open_dialog *op_dialog1; + +Image *img1 = NULL; +Image *img1_grayscale = NULL; +char *img1_path; + +char cur_dir_path[256]; + + +char* load_img(char* fname, uint32_t* read_sz) { + FILE *f = fopen(fname, "rb"); + if (!f) { + printf("Can't open file: %s\n", fname); + exit(0); + } + if (fseek(f, 0, SEEK_END)) { + printf("Can't SEEK_END file: %s\n", fname); + exit(0); + } + int filesize = ftell(f); + rewind(f); + char* fdata = malloc(filesize); + if(!fdata) { + printf("No memory for file %s\n", fname); + exit(0); + } + *read_sz = fread(fdata, 1, filesize, f); + if (ferror(f)) { + printf("Error reading file %s\n", fname); + exit(0); + } + fclose(f); + return fdata; +} + +/* mode: 0 - ASCII, 1 - SCAN*/ +void set_os_keyb_mode(int mode) { __asm__ __volatile__("int $0x40"::"a"(66), "b"(1), "c"(mode)); } + +void redraw_window() { + pos_t win_pos = get_mouse_pos(0); + begin_draw(); + sys_create_window(win_pos.x, win_pos.y, WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE, sys_color_table.color_work_area, 0x14); + + //draw_text_sys("", /*x*/, /*y*/, 0, DRAWTEXT_FLAG_DEFAULT | sys_color_table.work_text); + + define_button(X_W(WINDOW_COL2_X,100), Y_H(WINDOW_ROW1_Y,30), MYBTN_OPEN, sys_color_table.color_work_button); + draw_text_sys("Open image", WINDOW_COL2_X + 12, WINDOW_ROW1_Y + 7, 0, DRAWTEXT_FLAG_DEFAULT | sys_color_table.color_work_button_text); + + //define_button(X_W(WINDOW_COL2_X + 120,100), Y_H(WINDOW_ROW1_Y,30), MYBTN_CLEAR, sys_color_table.color_work_button); + + draw_text_sys("Recognition results below.", WINDOW_COL2_X, WINDOW_ROW1_Y + 40, 0, DRAWTEXT_FLAG_DEFAULT | sys_color_table.color_work_text); + draw_text_sys("Selection & Ctrl+C available.", WINDOW_COL2_X, WINDOW_ROW1_Y + 60, 0, DRAWTEXT_FLAG_DEFAULT | sys_color_table.color_work_text); + + ted_draw(tedit1); + + if (img1 != NULL) { + //printf("drawing...\n"); + img_draw(img1, 10, WINDOW_ROW1_Y, IMG1_DISPLAY_W, IMG1_DISPLAY_H , 0, 0); + } else { + draw_bar(10, WINDOW_ROW1_Y, IMG1_DISPLAY_W, IMG1_DISPLAY_H, COL_GREY); + } + + end_draw(); +} + +void tedit1_print(const char *text, int text_len, int do_newline) { + if (text_len == -1) { text_len = strlen(text); } + ted_text_add(tedit1, (char *)text, text_len, 1); + if (do_newline != 0 ) { ted_text_add(tedit1, "\r", 1, 1); } +} + +void create_components() { + tedit1 = kolibri_new_editor(X_W(WINDOW_COL2_X, TEDIT1_W), Y_H(WINDOW_ROW1_Y + IMG1_DISPLAY_H - TEDIT1_H - 16, TEDIT1_H), 0/*0x11*/, TEDIT1_MAXCHAR, &tedit1_lock); // 0x11 font 8x16 sized x2, 0 - default (8x16) + tedit1_lock = tedit1; + tedit1->mode_invis = 0; /* dont show invisible characters */ + + op_dialog1 = kolibri_new_open_dialog(OPEN, 10, 10, 420, 320); + op_dialog1->dir_default_path = cur_dir_path; + op_dialog1->draw_window = redraw_window; + OpenDialog_init(op_dialog1); +} + +void recognize_qr() { + puts("Starting QUIRC...\n"); + struct quirc *qr; + + qr = quirc_new(); + if (!qr) { + puts("Failed to allocate memory"); + exit(-1); + } + + if (quirc_resize(qr, img1_grayscale->Width, img1_grayscale->Height) < 0) { + puts("Failed to allocate video memory"); + exit(-1); + } + + void *gsbuf = img1_grayscale->Data; + uint32_t gsbuf_size = img1_grayscale->Width*img1_grayscale->Height; + + uint8_t *buffer; + int w, h; + puts("quirc_begin()...\n"); + buffer = quirc_begin(qr, &w, &h); + //printf("buffer = %x qr = %x\n", buffer, qr); + //printf("w = %d h = %d\n", w, h); + memcpy(buffer, gsbuf, gsbuf_size); + puts("quirc_end()...\n"); + quirc_end(qr); + + ted_clear(tedit1, 1); /* cleaning output text area */ + + int num_codes, i; + puts("quirc_count()...\n"); + num_codes = quirc_count(qr); + printf(" NUM_CODES = %d\n", num_codes); + if (num_codes == 0) { + tedit1_print("DECODE FAILED: NO CODES FOUND", -1, 1); + } + for (i = 0; i < num_codes; i++) { + struct quirc_code code; + struct quirc_data data; + quirc_decode_error_t err; + + quirc_extract(qr, i, &code); + + // decoding stage + err = quirc_decode(&code, &data); + if (err) { + //printf(" DECODE FAILED: %s\n", quirc_strerror(err)); + tedit1_print("DECODE FAILED: ", -1, 0); + tedit1_print(quirc_strerror(err), -1, 1); + } else { + //printf("\n RECOGNIZED DATA: %s\n", data.payload); + tedit1_print("RECOGNIZED: ", -1, 0); + tedit1_print(data.payload, data.payload_len, 1); + } + } + + puts("\nquirc_destroy()...\n"); + quirc_destroy(qr); +} + +void on_btn_open() { + //ted_text_add(tedit1, "Hello world!\r", strlen("Hello world!\r"), 1); + op_dialog1->mode = OPEN; + OpenDialog_start(op_dialog1); + if (op_dialog1->status != 2 && op_dialog1->status != 0) {// fail or cancel + img1_path = op_dialog1->openfile_path; + //ted_text_add(tedit1, img1_path, strlen(img1_path), 1); + //ted_text_add(tedit1, "\r", 1, 1); // newline + } else { + return; + } + Image *oldimg; + if (img1 != NULL) { img_destroy(img1); } + if (img1_grayscale != NULL) { img_destroy(img1_grayscale); } + uint32_t file_size; + void *file_data = load_img(img1_path, &file_size); // Get RAW data and size + img1 = img_decode(file_data, file_size, 0); // Decode RAW data to Image data + img1_grayscale = img_decode(file_data, file_size, 0); // Decode RAW data to Image data + free(file_data); // + printf("original: image->Width = %d, Image->Height = %d,\n original image type = %d\n\n", img1->Width, img1->Height, img1->Type); + if (img1->Type != IMAGE_BPP24) { + oldimg = img1; // + img1 = img_convert(img1, NULL, IMAGE_BPP24, 0, 0); // Convert image to RGB 24 + img_destroy(oldimg); // + if (!img1) { + printf("Сonvert img1 to BPP24 error!: \n"); + exit(-1); + } + } + if (img1_grayscale->Type != IMAGE_BPP24) { + oldimg = img1_grayscale; // + img1_grayscale = img_convert(img1_grayscale, NULL, IMAGE_BPP24, 0, 0); // Convert image to RGB + img_destroy(oldimg); // + if (!img1_grayscale) { + printf("Сonvert img1_grayscale to BPP24 error!: \n"); + exit(-1); + } + } + if (img1_grayscale->Type != IMAGE_BPP8g) { + oldimg = img1_grayscale; // + img1_grayscale = img_convert(img1_grayscale, NULL, IMAGE_BPP8g, 0, 0); // Convert image to grayscale + img_destroy(oldimg); // + if (!img1_grayscale) { + printf("Сonvert img1_grayscale to BPP8g error!: \n"); + exit(-1); + } + } + oldimg = img1; // + img1 = img_scale(img1, 0, 0, img1->Width, img1->Height, NULL, LIBIMG_SCALE_STRETCH , LIBIMG_INTER_BILINEAR, IMG1_DISPLAY_W, IMG1_DISPLAY_H); + img_destroy(oldimg); // + + recognize_qr(); +} + +int main(int argc, char *argv[]) { + int gui_event; /* variable for storing event */ + uint32_t pressed_button = 0; /* code of button pressed in window */ + unsigned int keyval; /* for saving pressed key */ + oskey_t key; + + ((void)argc); + strcpy(cur_dir_path, argv[0] + 2); char *pc = strrchr(cur_dir_path, '/'); if (pc) { *pc = '\0'; } + printf("cur_dir_path = %s\n", cur_dir_path); + + kolibri_boxlib_init(); + kolibri_proclib_init(); // opensave && color dialogs + kolibri_libimg_init(); + + set_wanted_events_mask(0xC0000027); + set_os_keyb_mode(1); // scan code mode needed for editor + + kolibri_get_system_colors(&sys_color_table); // Get system colors theme + + create_components(); + + do { + gui_event = get_os_event(); + switch(gui_event) { + case KOLIBRI_EVENT_NONE: + break; + case KOLIBRI_EVENT_REDRAW: + redraw_window(); + break; + case KOLIBRI_EVENT_MOUSE: + ted_mouse(tedit1); + break; + case KOLIBRI_EVENT_KEY: + key = get_key(); + if (tedit1_lock == tedit1) { editor_key(tedit1, key); } + break; + case KOLIBRI_EVENT_BUTTON: + pressed_button = get_os_button(); + switch (pressed_button) + { + case MYBTN_OPEN: + { + on_btn_open(); + redraw_window(); + break; + } + + /*case MYBTN_CLEAR: + ted_clear(tedit1, 1); + redraw_window(); + break; */ + + case MYBTN_QUIT: + exit(0); + break; + } + } + } while (1); + + exit(0); +}