mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-11-25 23:14:18 +03:00
Add QR parse logic
This commit is contained in:
parent
2777c0c9d3
commit
9d6e248cae
159
py/qr/qrparse.py
Normal file
159
py/qr/qrparse.py
Normal file
@ -0,0 +1,159 @@
|
||||
"""
|
||||
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, bpp, x_offs, x_width):
|
||||
return check_line(line[x_offs:x_offs+x_width])
|
||||
|
||||
|
||||
def check_col(image, bpp, 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, bpp, x_offs, x_width):
|
||||
matching_dark = not is_dark(line[x_offs*bpp:(x_offs+1)*bpp])
|
||||
matched = []
|
||||
for x in range(x_offs, x_offs + x_width):
|
||||
pixel = line[x*bpp:(x+1)*bpp]
|
||||
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, bpp, 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):
|
||||
size = image.size()
|
||||
bpp = image.bytesPerLine() // size.width()
|
||||
|
||||
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.scanLine(min_y + int(6.5 / 7 * max(tl.h, tr.h)))
|
||||
_, line_data = read_line(line, bpp, min_x, width)
|
||||
size = len(line_data) + 12
|
||||
|
||||
# Read QR code data
|
||||
yield read_bits(image, bpp, min_x, min_y, width, height, size)
|
||||
|
||||
|
||||
def locate_finders(image, min_res):
|
||||
bpp = 1
|
||||
finders = set()
|
||||
for y in range(0, image.height, min_res * 3):
|
||||
for (x, w) in check_row(image.get_line(y), bpp, 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, bpp, x_offs, y_offs, y_height), None)
|
||||
if match:
|
||||
(pos, h) = match
|
||||
y2 = y_offs + pos
|
||||
if read_bits(image, bpp, 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
|
@ -1,18 +1,17 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import json
|
||||
import types
|
||||
import re
|
||||
from base64 import b32decode
|
||||
from base64 import b32decode, b64decode
|
||||
from binascii import a2b_hex, b2a_hex
|
||||
|
||||
from ykman.descriptor import get_descriptors
|
||||
from ykman.driver import ModeSwitchError
|
||||
from ykman.util import CAPABILITY, TRANSPORT, Mode, derive_key
|
||||
from ykman.util import CAPABILITY, TRANSPORT, derive_key
|
||||
from ykman.oath import OathController, Credential
|
||||
|
||||
from py.qr import qrparse
|
||||
from py.qr import qrdecode
|
||||
|
||||
NON_FEATURE_CAPABILITIES = [CAPABILITY.CCID, CAPABILITY.NFC]
|
||||
|
||||
@ -64,7 +63,7 @@ class Controller(object):
|
||||
return self._dev_info
|
||||
|
||||
def refresh_credentials(self, timestamp, password_key=None):
|
||||
if password_key != None:
|
||||
if password_key is not None:
|
||||
password_key = a2b_hex(password_key)
|
||||
return [c.to_dict() for c in self._calculate_all(timestamp, password_key)]
|
||||
|
||||
@ -97,7 +96,6 @@ class Controller(object):
|
||||
else:
|
||||
controller.clear_password()
|
||||
|
||||
|
||||
def add_credential(self, name, key, oath_type, digits, algo, touch, password_key):
|
||||
dev = self._descriptor.open_device(TRANSPORT.CCID)
|
||||
controller = OathController(dev.driver)
|
||||
@ -144,4 +142,21 @@ class Controller(object):
|
||||
return b32decode(key)
|
||||
|
||||
|
||||
def parse_qr(self, image):
|
||||
data = b64decode(image['data'])
|
||||
image = PixelImage(data, image['width'], image['height'])
|
||||
x = qrparse.locate_finders(image, 2)
|
||||
return x
|
||||
|
||||
|
||||
class PixelImage(object):
|
||||
|
||||
def __init__(self, data, width, height):
|
||||
self.data = data
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
def get_line(self, line_number):
|
||||
return self.data[self.width * line_number:self.width * (line_number + 1)]
|
||||
|
||||
controller = Controller()
|
||||
|
@ -19,7 +19,7 @@ public:
|
||||
image = image.convertToFormat(QImage::Format_Mono, Qt::ThresholdDither);
|
||||
|
||||
// Iterate over all pixels
|
||||
QByteArray imageArray(4 + 4 + (image.width() * image.height()), 0);
|
||||
QByteArray imageArray;
|
||||
for (int row = 0; row < image.height(); ++row) {
|
||||
for (int col = 0; col < image.width(); ++col) {
|
||||
QRgb px = image.pixel(col, row);
|
||||
|
Loading…
Reference in New Issue
Block a user