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 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
|
from typing import TYPE_CHECKING
from PIL import Image, ImageDraw
from qrcode.image.styles.moduledrawers.base import QRModuleDrawer
if TYPE_CHECKING:
from qrcode.image.styledpil import StyledPilImage
from qrcode.main import ActiveWithNeighbors
# When drawing antialiased things, make them bigger and then shrink them down
# to size after the geometry has been drawn.
ANTIALIASING_FACTOR = 4
class StyledPilQRModuleDrawer(QRModuleDrawer):
"""
A base class for StyledPilImage module drawers.
NOTE: the color that this draws in should be whatever is equivalent to
black in the color space, and the specified QRColorMask will handle adding
colors as necessary to the image
"""
img: "StyledPilImage"
class SquareModuleDrawer(StyledPilQRModuleDrawer):
"""
Draws the modules as simple squares
"""
def initialize(self, *args, **kwargs):
super().initialize(*args, **kwargs)
self.imgDraw = ImageDraw.Draw(self.img._img)
def drawrect(self, box, is_active: bool):
if is_active:
self.imgDraw.rectangle(box, fill=self.img.paint_color)
class GappedSquareModuleDrawer(StyledPilQRModuleDrawer):
"""
Draws the modules as simple squares that are not contiguous.
The size_ratio determines how wide the squares are relative to the width of
the space they are printed in
"""
def __init__(self, size_ratio=0.8):
self.size_ratio = size_ratio
def initialize(self, *args, **kwargs):
super().initialize(*args, **kwargs)
self.imgDraw = ImageDraw.Draw(self.img._img)
self.delta = (1 - self.size_ratio) * self.img.box_size / 2
def drawrect(self, box, is_active: bool):
if is_active:
smaller_box = (
box[0][0] + self.delta,
box[0][1] + self.delta,
box[1][0] - self.delta,
box[1][1] - self.delta,
)
self.imgDraw.rectangle(smaller_box, fill=self.img.paint_color)
class CircleModuleDrawer(StyledPilQRModuleDrawer):
"""
Draws the modules as circles
"""
circle = None
def initialize(self, *args, **kwargs):
super().initialize(*args, **kwargs)
box_size = self.img.box_size
fake_size = box_size * ANTIALIASING_FACTOR
self.circle = Image.new(
self.img.mode,
(fake_size, fake_size),
self.img.color_mask.back_color,
)
ImageDraw.Draw(self.circle).ellipse(
(0, 0, fake_size, fake_size), fill=self.img.paint_color
)
self.circle = self.circle.resize((box_size, box_size), Image.Resampling.LANCZOS)
def drawrect(self, box, is_active: bool):
if is_active:
self.img._img.paste(self.circle, (box[0][0], box[0][1]))
class RoundedModuleDrawer(StyledPilQRModuleDrawer):
"""
Draws the modules with all 90 degree corners replaced with rounded edges.
radius_ratio determines the radius of the rounded edges - a value of 1
means that an isolated module will be drawn as a circle, while a value of 0
means that the radius of the rounded edge will be 0 (and thus back to 90
degrees again).
"""
needs_neighbors = True
def __init__(self, radius_ratio=1):
self.radius_ratio = radius_ratio
def initialize(self, *args, **kwargs):
super().initialize(*args, **kwargs)
self.corner_width = int(self.img.box_size / 2)
self.setup_corners()
def setup_corners(self):
mode = self.img.mode
back_color = self.img.color_mask.back_color
front_color = self.img.paint_color
self.SQUARE = Image.new(
mode, (self.corner_width, self.corner_width), front_color
)
fake_width = self.corner_width * ANTIALIASING_FACTOR
radius = self.radius_ratio * fake_width
diameter = radius * 2
base = Image.new(
mode, (fake_width, fake_width), back_color
) # make something 4x bigger for antialiasing
base_draw = ImageDraw.Draw(base)
base_draw.ellipse((0, 0, diameter, diameter), fill=front_color)
base_draw.rectangle((radius, 0, fake_width, fake_width), fill=front_color)
base_draw.rectangle((0, radius, fake_width, fake_width), fill=front_color)
self.NW_ROUND = base.resize(
(self.corner_width, self.corner_width), Image.Resampling.LANCZOS
)
self.SW_ROUND = self.NW_ROUND.transpose(Image.Transpose.FLIP_TOP_BOTTOM)
self.SE_ROUND = self.NW_ROUND.transpose(Image.Transpose.ROTATE_180)
self.NE_ROUND = self.NW_ROUND.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
def drawrect(self, box: list[list[int]], is_active: "ActiveWithNeighbors"):
if not is_active:
return
# find rounded edges
nw_rounded = not is_active.W and not is_active.N
ne_rounded = not is_active.N and not is_active.E
se_rounded = not is_active.E and not is_active.S
sw_rounded = not is_active.S and not is_active.W
nw = self.NW_ROUND if nw_rounded else self.SQUARE
ne = self.NE_ROUND if ne_rounded else self.SQUARE
se = self.SE_ROUND if se_rounded else self.SQUARE
sw = self.SW_ROUND if sw_rounded else self.SQUARE
self.img._img.paste(nw, (box[0][0], box[0][1]))
self.img._img.paste(ne, (box[0][0] + self.corner_width, box[0][1]))
self.img._img.paste(
se, (box[0][0] + self.corner_width, box[0][1] + self.corner_width)
)
self.img._img.paste(sw, (box[0][0], box[0][1] + self.corner_width))
class VerticalBarsDrawer(StyledPilQRModuleDrawer):
"""
Draws vertically contiguous groups of modules as long rounded rectangles,
with gaps between neighboring bands (the size of these gaps is inversely
proportional to the horizontal_shrink).
"""
needs_neighbors = True
def __init__(self, horizontal_shrink=0.8):
self.horizontal_shrink = horizontal_shrink
def initialize(self, *args, **kwargs):
super().initialize(*args, **kwargs)
self.half_height = int(self.img.box_size / 2)
self.delta = int((1 - self.horizontal_shrink) * self.half_height)
self.setup_edges()
def setup_edges(self):
mode = self.img.mode
back_color = self.img.color_mask.back_color
front_color = self.img.paint_color
height = self.half_height
width = height * 2
shrunken_width = int(width * self.horizontal_shrink)
self.SQUARE = Image.new(mode, (shrunken_width, height), front_color)
fake_width = width * ANTIALIASING_FACTOR
fake_height = height * ANTIALIASING_FACTOR
base = Image.new(
mode, (fake_width, fake_height), back_color
) # make something 4x bigger for antialiasing
base_draw = ImageDraw.Draw(base)
base_draw.ellipse((0, 0, fake_width, fake_height * 2), fill=front_color)
self.ROUND_TOP = base.resize((shrunken_width, height), Image.Resampling.LANCZOS)
self.ROUND_BOTTOM = self.ROUND_TOP.transpose(Image.Transpose.FLIP_TOP_BOTTOM)
def drawrect(self, box, is_active: "ActiveWithNeighbors"):
if is_active:
# find rounded edges
top_rounded = not is_active.N
bottom_rounded = not is_active.S
top = self.ROUND_TOP if top_rounded else self.SQUARE
bottom = self.ROUND_BOTTOM if bottom_rounded else self.SQUARE
self.img._img.paste(top, (box[0][0] + self.delta, box[0][1]))
self.img._img.paste(
bottom, (box[0][0] + self.delta, box[0][1] + self.half_height)
)
class HorizontalBarsDrawer(StyledPilQRModuleDrawer):
"""
Draws horizontally contiguous groups of modules as long rounded rectangles,
with gaps between neighboring bands (the size of these gaps is inversely
proportional to the vertical_shrink).
"""
needs_neighbors = True
def __init__(self, vertical_shrink=0.8):
self.vertical_shrink = vertical_shrink
def initialize(self, *args, **kwargs):
super().initialize(*args, **kwargs)
self.half_width = int(self.img.box_size / 2)
self.delta = int((1 - self.vertical_shrink) * self.half_width)
self.setup_edges()
def setup_edges(self):
mode = self.img.mode
back_color = self.img.color_mask.back_color
front_color = self.img.paint_color
width = self.half_width
height = width * 2
shrunken_height = int(height * self.vertical_shrink)
self.SQUARE = Image.new(mode, (width, shrunken_height), front_color)
fake_width = width * ANTIALIASING_FACTOR
fake_height = height * ANTIALIASING_FACTOR
base = Image.new(
mode, (fake_width, fake_height), back_color
) # make something 4x bigger for antialiasing
base_draw = ImageDraw.Draw(base)
base_draw.ellipse((0, 0, fake_width * 2, fake_height), fill=front_color)
self.ROUND_LEFT = base.resize(
(width, shrunken_height), Image.Resampling.LANCZOS
)
self.ROUND_RIGHT = self.ROUND_LEFT.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
def drawrect(self, box, is_active: "ActiveWithNeighbors"):
if is_active:
# find rounded edges
left_rounded = not is_active.W
right_rounded = not is_active.E
left = self.ROUND_LEFT if left_rounded else self.SQUARE
right = self.ROUND_RIGHT if right_rounded else self.SQUARE
self.img._img.paste(left, (box[0][0], box[0][1] + self.delta))
self.img._img.paste(
right, (box[0][0] + self.half_width, box[0][1] + self.delta)
)
|