mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-11-24 03:06:45 +03:00
156 lines
4.5 KiB
Python
156 lines
4.5 KiB
Python
"""
|
|
Given an image, locates and parses the pixel data in QR codes.
|
|
"""
|
|
|
|
from __future__ import division
|
|
from collections import namedtuple
|
|
|
|
|
|
__all__ = ['parse_qr_codes']
|
|
|
|
|
|
Box = namedtuple('Box', ['x', 'y', 'w', 'h'])
|
|
|
|
|
|
def is_dark(pixel):
|
|
return pixel == 1
|
|
|
|
|
|
def buffer_matches(matched):
|
|
return len(matched) == 5 \
|
|
and max(matched[:2] + matched[3:]) <= matched[2] // 2 \
|
|
and min(matched[:2] + matched[3:]) >= matched[2] // 6
|
|
|
|
|
|
def check_line(pixels):
|
|
matching_dark = False
|
|
matched = [0, 0, 0, 0, 0]
|
|
for (i, pixel) in enumerate(pixels):
|
|
if is_dark(pixel): # Dark pixel
|
|
if matching_dark:
|
|
matched[-1] += 1
|
|
else:
|
|
matched = matched[1:] + [1]
|
|
matching_dark = True
|
|
else: # Light pixel
|
|
if not matching_dark:
|
|
matched[-1] += 1
|
|
else:
|
|
if buffer_matches(matched):
|
|
width = sum(matched)
|
|
yield i - width, width
|
|
matched = matched[1:] + [1]
|
|
matching_dark = False
|
|
|
|
# Check final state of buffer
|
|
if matching_dark and buffer_matches(matched):
|
|
width = sum(matched)
|
|
yield i - width, width
|
|
|
|
|
|
def check_row(line, x_offs, x_width):
|
|
return check_line(line[x_offs:x_offs+x_width])
|
|
|
|
|
|
def check_col(image, x, y_offs, y_height):
|
|
return check_line(bytes([image.get_line(i)[x]
|
|
for i in range(y_offs, y_offs + y_height)]))
|
|
|
|
|
|
def read_line(line, x_offs, x_width):
|
|
matching_dark = not is_dark(line[x_offs])
|
|
matched = []
|
|
for x in range(x_offs, x_offs + x_width):
|
|
pixel = line[x]
|
|
if is_dark(pixel): # Dark pixel
|
|
if matching_dark:
|
|
matched[-1] += 1
|
|
else:
|
|
matched.append(1)
|
|
matching_dark = True
|
|
else: # Light pixel
|
|
if not matching_dark:
|
|
matched[-1] += 1
|
|
else:
|
|
matched.append(1)
|
|
matching_dark = False
|
|
return matching_dark, matched
|
|
|
|
|
|
def read_bits(image, img_x, img_y, img_w, img_h, size):
|
|
qr_x_w = img_w / size
|
|
qr_y_h = img_h / size
|
|
qr_data = []
|
|
for qr_y in range(size):
|
|
y = img_y + int(qr_y_h / 2 + qr_y * qr_y_h)
|
|
img_line = image.get_line(y)
|
|
qr_line = []
|
|
for qr_x in range(size):
|
|
x = img_x + int(qr_x_w / 2 + qr_x * qr_x_w)
|
|
qr_line.append(is_dark(img_line[x]))
|
|
qr_data.append(qr_line)
|
|
return qr_data
|
|
|
|
|
|
FINDER = [
|
|
[True, True, True, True, True, True, True],
|
|
[True, False, False, False, False, False, True],
|
|
[True, False, True, True, True, False, True],
|
|
[True, False, True, True, True, False, True],
|
|
[True, False, True, True, True, False, True],
|
|
[True, False, False, False, False, False, True],
|
|
[True, True, True, True, True, True, True]
|
|
]
|
|
|
|
|
|
def parse_qr_codes(image, min_res=2):
|
|
finders = locate_finders(image, min_res)
|
|
|
|
# Arrange finders into QR codes and extract data
|
|
for (tl, tr, bl) in identify_groups(finders):
|
|
min_x = min(tl.x, bl.x)
|
|
min_y = min(tl.y, tr.y)
|
|
width = tr.x + tr.w - min_x
|
|
height = bl.y + bl.h - min_y
|
|
|
|
# Determine resolution by reading timing pattern
|
|
line = image.get_line(min_y + int(6.5 / 7 * max(tl.h, tr.h)))
|
|
_, line_data = read_line(line, min_x, width)
|
|
size = len(line_data) + 12
|
|
|
|
# Read QR code data
|
|
yield read_bits(image, min_x, min_y, width, height, size)
|
|
|
|
|
|
def locate_finders(image, min_res):
|
|
finders = set()
|
|
for y in range(0, image.height, min_res * 3):
|
|
for (x, w) in check_row(image.get_line(y), 0, image.width):
|
|
x_offs = x + w // 2
|
|
y_offs = max(0, y - w)
|
|
y_height = min(image.height - y_offs, 2 * w)
|
|
match = next(check_col(image, x_offs, y_offs, y_height), None)
|
|
if match:
|
|
(pos, h) = match
|
|
y2 = y_offs + pos
|
|
if read_bits(image, x, y2, w, h, 7) == FINDER:
|
|
finders.add(Box(x, y2, w, h))
|
|
|
|
return list(finders)
|
|
|
|
|
|
def identify_groups(locators):
|
|
# Find top left
|
|
for tl in locators:
|
|
x_tol = tl.w / 14
|
|
y_tol = tl.h / 14
|
|
|
|
# Find top right
|
|
for tr in locators:
|
|
if tr.x > tl.x and abs(tl.y - tr.y) <= y_tol:
|
|
|
|
# Find bottom left
|
|
for bl in locators:
|
|
if bl.y > tl.y and abs(tl.x - bl.x) <= x_tol:
|
|
yield tl, tr, bl
|