|
#!/usr/bin/env python |
|
# -*- coding: utf-8 vi:noet |
|
# What if we made crazy QR codes with extra data in chroma... |
|
|
|
""" |
|
To reduce flicker, the extra data can be gray-encoded |
|
""" |
|
|
|
import sys, struct, ctypes |
|
import cv2 |
|
import numpy as np |
|
|
|
|
|
def gray_enc(value): |
|
return (value^(value>>1)) |
|
|
|
def gray_dec(value, bits): |
|
out = 0 |
|
for idx_bit in range(bits-1, -1, -1): |
|
out |= (((value >> idx_bit) & 1) ^ ((out >> (idx_bit+1)) & 1)) << idx_bit |
|
return out |
|
|
|
def sbits(value, bits): |
|
return bin(value | (1<<bits))[3:] |
|
|
|
bits = 8 |
|
for i in range(1<<bits): |
|
e = gray_enc(i) |
|
d = gray_dec(e, bits) |
|
assert d == i |
|
continue |
|
print("%s %s %s" % ( |
|
sbits(i, bits), |
|
sbits(e, bits), |
|
sbits(d, bits), |
|
)) |
|
|
|
|
|
def enc_flt(x): |
|
b = struct.pack("<f", float(x)) |
|
i = struct.unpack("<I", b)[0] |
|
g = gray_enc(i) |
|
return struct.pack("<I", g) |
|
|
|
class QREncoder(object): |
|
def __init__(self): |
|
self._lib = ctypes.CDLL("libqrencode.so") |
|
self._lib.QRcode_encodeData.argtypes = ctypes.c_int, ctypes.c_char_p, ctypes.c_int, ctypes.c_int |
|
class QRCode(ctypes.Structure): |
|
_fields_ = [ |
|
("version", ctypes.c_int), |
|
("width", ctypes.c_int), |
|
("data", ctypes.POINTER(ctypes.c_uint8)), |
|
] |
|
self._lib.QRcode_encodeData.restype = ctypes.POINTER(QRCode) |
|
def encode(self, value): |
|
assert isinstance(value, bytes) |
|
version = 2 |
|
level = 3 |
|
qrcode = self._lib.QRcode_encodeData(len(value), value, version, level) |
|
res = qrcode.contents |
|
version = int(res.version) |
|
size = int(res.width) |
|
data = bytearray(size*size) |
|
for idx_data in range(size*size): |
|
data[idx_data] = (1 - (res.data[idx_data] & 1)) * 255 |
|
data = bytes(data) |
|
self._lib.QRcode_free(qrcode) |
|
return version, size, data |
|
|
|
def make_samesize(datas): |
|
# make the QR codes the same size (somehow) |
|
sizes = [None] * len(datas) |
|
payloads = [None] * len(datas) |
|
encoder = QREncoder() |
|
while sizes[0] is None or not reduce(lambda x, y: x and y, map(lambda x: x == sizes[0], sizes)): |
|
|
|
if sizes[0] is not None: |
|
for idx_data, data in enumerate(datas): |
|
if sizes[idx_data] < max(sizes): |
|
datas[idx_data] += b" " |
|
|
|
|
|
for idx_data, data in enumerate(datas): |
|
version, size, data = encoder.encode(data) |
|
payloads[idx_data] = data |
|
sizes[idx_data] = size |
|
|
|
return size, payloads |
|
|
|
def example_encode_luma(): |
|
|
|
datas = [] |
|
datas.append("http://zougloub.eu") |
|
datas.append("hello world!") |
|
datas.append("hello again!") |
|
datas.append("hello ter!") |
|
|
|
size, payloads = make_samesize(datas) |
|
|
|
scale = 16 |
|
img = np.zeros((size+2, size+2), dtype=np.uint8) + 255 |
|
|
|
layer_weights = 32, 16, 8, 4, 2 |
|
|
|
assert len(datas) <= len(layer_weights) |
|
|
|
img_upd = img[1:-1,1:-1] |
|
img_qr0 = np.fromstring(payloads[0], dtype=np.uint8).reshape((size, size)) |
|
img_upd[:] = img_qr0 |
|
|
|
for idx_data, data in enumerate(payloads[1:]): |
|
img_qr = np.int32(np.fromstring(data, dtype=np.uint8).reshape((size, size))) |
|
weight = layer_weights[idx_data] |
|
comp = img_qr0 != img_qr |
|
compimg = np.ones_like(img_upd) |
|
compimg[comp] = weight |
|
cv2.imwrite("comp-%d.png" % idx_data, compimg) |
|
img_upd[img_upd >= 128] -= compimg[img_upd >= 128] |
|
img_upd[img_upd < 128] += compimg[img_upd < 128] |
|
cv2.imwrite("upd-%d.png" % idx_data, img) |
|
|
|
img = cv2.resize(img, ((size+2)*scale, (size+2)*scale), interpolation=cv2.INTER_NEAREST) |
|
cv2.imwrite("qrcode-luma.png", img) |
|
|
|
|
|
def example_encode_chroma(): |
|
|
|
datas = [] |
|
|
|
datas.append("http://zougloub.eu") |
|
|
|
# some structure... |
|
extradata = {"x": 1.33} |
|
# more data with some string encoding |
|
|
|
datas.append("f" + enc_flt(1.33)) |
|
|
|
# more data as a string |
|
datas.append("hello world!") |
|
|
|
assert len(datas) == 3, "Unimplemented" |
|
|
|
size, payloads = make_samesize(datas) |
|
|
|
scale = 16 |
|
img = np.zeros((size+2, size+2), dtype=np.uint8) + 255 |
|
|
|
img_upd = img[1:-1,1:-1] |
|
img_qr0 = np.fromstring(payloads[0], dtype=np.uint8).reshape((size, size)) |
|
img_upd[:] = img_qr0 |
|
img = cv2.merge((img, img, img)) |
|
|
|
img_yuv = cv2.cvtColor(img, cv2.COLOR_RGB2YUV) |
|
|
|
for idx_plane in range(1, 3): |
|
info = payloads[idx_plane] |
|
img_qr = np.fromstring(info, dtype=np.dtype('B')).reshape((size, size)) |
|
img_upd = img_yuv[1:-1,1:-1,idx_plane] |
|
img_upd[img_qr0 != img_qr] -= 80 |
|
|
|
img = cv2.cvtColor(img_yuv, cv2.COLOR_YUV2RGB) |
|
img = cv2.resize(img, ((size+2)*scale, (size+2)*scale), interpolation=cv2.INTER_NEAREST) |
|
cv2.imwrite("qrcode-chroma.png", img) |
|
|
|
if __name__ == '__main__': |
|
example_encode_chroma() |
|
example_encode_luma() |
|
|