-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathpassfun.py
executable file
·179 lines (152 loc) · 5.9 KB
/
passfun.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#! /usr/bin/env python3
# Copyright (c) 2021, juju2013@github
# All rights reserved
# This file is under BSD 2-Clause License, see LICENSE file
import os,sys, random, io
import argparse
from pyzbar import pyzbar
from PIL import Image, ImageDraw
from datetime import datetime
from urllib import request
import qrcode
# Some constants
ccardy=2 # in inch
ccardx=3 # in inch
DPI=600 # print DPI
MAXY = int(ccardy * DPI)
MAXX = int(ccardx * DPI)
LTHREASHOLD = int(255*90/100) # for light mode, all pixel brighter than this will turn to black
testdataroot="https://raw.githubusercontent.com/eu-digital-green-certificates/dgc-testdata/52496b2fa85a9fdb4d5a32ac4abc8c639fb75b20/CH/png/"
class EUDCC:
def __init__(self, inf=None):
self.data=None
self.img = None
self.mask1 = None
self.mask3 = None
if inf:
self.ReadImage(inf)
def Decode(self, im):
d = pyzbar.decode(im)
if len(d) != 1:
raise Exception("%s must contain 1 and only 1 qrcode!"%inf)
self.data=d[0].data
def ReadImage(self, inf):
with Image.open(inf) as im:
self.Decode(im)
# load a random test data
def TestData(self, uri="https://github.com/eu-digital-green-certificates/dgc-testdata/raw/main/CH/png/", tf=15):
random.seed(str(datetime.now()))
n = random.randint(1, tf)
ib = request.urlopen("%s%s.png"%(uri, n)).read()
im = Image.open(io.BytesIO(ib))
self.Decode(im)
def NewQRCode(self, scale=100, qrcode_correction="H"):
error_correction = {
"L": qrcode.constants.ERROR_CORRECT_L,
"M": qrcode.constants.ERROR_CORRECT_M,
"Q": qrcode.constants.ERROR_CORRECT_Q,
"H": qrcode.constants.ERROR_CORRECT_H,
}[qrcode_correction]
qr = qrcode.QRCode(error_correction=error_correction, border=1)
qr.add_data(self.data)
qr.make(fit=True)
im = qr.make_image(back_color="transparent")
# FIXME : bug from PIL ??? erroneous width without convert
im = maxImage(im.convert("RGBA"))
si = (int(im.size[0]*scale/100), int(im.size[1]*scale/100))
self.img = im.resize(si)
def Debug(self):
self.img.save("dbug-qrcode.png")
self.mask1.save("dbug-mask1.png")
self.mask3.save("dbug-mask3.png")
# compute masks with size (x,y) and qrcode at pos% from left
# mask1 is only with qrcode
# mask3 is fade mask
def SetMasks(self, x, y, pos=0, trans=0):
fg=(0,0,0)
fa=int(trans*255/100)
im = Image.new("RGBA",(x,y))
xs = int((x-self.img.width)/100*pos)
ys = int((y-self.img.height)/2)
im.paste(self.img,(xs,ys))
self.mask1=im.copy()
self.mask3=self.mask1.copy().convert("L")
dr = ImageDraw.Draw(self.mask3)
dr.rectangle([xs,ys,xs+self.img.width,ys+self.img.height], fill=fa)
class DstImage:
def __init__(self, inf=None):
self.img = None
if inf:
if inf.lower().startswith("http"):
self.ReadImageUrl(inf)
else:
self.ReadImage(inf)
def ReadImage(self, inf):
with Image.open(inf) as im:
self.img = maxImage(im).convert("RGBA")
def ReadImageUrl(self, url):
r = request.Request(
url,
data = None,
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Linux i686; rv:85.0) Gecko/20100101 Firefox/85.0'
}
)
ib = request.urlopen(r).read()
im = Image.open(io.BytesIO(ib))
self.img = maxImage(im)
def maxImage(img):
rx = MAXX/img.width
ry = MAXY/img.height
r = min(rx,ry)
return img.resize((int(ry*img.width), int(ry*img.height))) # only resize on Y
def main():
apar = argparse.ArgumentParser(description="Make your EU digital COVID certificate fun again.")
apar.add_argument("inputfile", help="The original qrcode")
apar.add_argument("backgroundfile", help="Background image, can start with 'http'")
apar.add_argument("outputfile", help="The new qrcode")
apar.add_argument("--scale", const=100, default=100, nargs='?', type=int, help="Scale of the target qrcode in percent, default=100")
apar.add_argument("--pos", const=50, default=50, nargs='?', type=int, help="Generated qrcode horizontal position, 0=left, 100=right")
apar.add_argument("--trans", type=int, const=80, default=80, nargs='?', help="Background transparency, 0=white, 100=original image")
apar.add_argument("--lmode", default=False, nargs='?', const=True, help="Light mode, background image is light")
apar.add_argument("--qrcode-correction", choices=["L", "M", "Q", "H"], default="H", help="The error correction value of the QR code (default: H)")
apar.add_argument("--test", default=False, nargs='?', const=True, help="Download random test data from dgc-testdata instead of inputfile")
apar.add_argument("--debug", default=False, nargs='?', const=True, help="Debug")
args = apar.parse_args()
cert = EUDCC()
if args.test:
cert.TestData(testdataroot)
else:
cert.ReadImage(args.inputfile)
cert.NewQRCode(scale=args.scale, qrcode_correction=args.qrcode_correction)
bimg = DstImage(args.backgroundfile)
if bimg.img.width < cert.img.width:
raise Exception("Background image is smaller then QRcode")
# place qrcode
cert.SetMasks(bimg.img.width, bimg.img.height, args.pos, args.trans)
if args.debug :
cert.Debug()
im = bimg.img.copy()
# use mask3 to faded background
bimg.img.paste((255,255,255), mask=cert.mask3)
# use past mask1 over
bimg.img.paste(im, mask=cert.mask1)
# fill transparent with black
if bimg.img.mode=="RGBA":
bg = Image.new("RGBA", im.size, (0,0,0))
bimg.img=Image.alpha_composite(bg, bimg.img)
# inverse fill for light mode
if args.lmode :
ip = bimg.img.load()
iq = cert.mask1.load()
w,h =bimg.img.size
for x in range(w):
for y in range(h):
#if ip[x,y] > (LTHREASHOLD,)*3:
if iq[x,y][3] > 0: # black pixel in qrcode
if ip[x,y][0]+ip[x,y][1]+ip[x,y][2] > LTHREASHOLD * 3 : #light pixel in background
ip[x,y] = iq[x,y]
# save the final image
bimg.img.save(args.outputfile)
if __name__ == "__main__":
main()