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
|
import qrcode.image.base
from PIL import Image
from qrcode.image.styles.colormasks import QRColorMask, SolidFillColorMask
from qrcode.image.styles.moduledrawers import SquareModuleDrawer
class StyledPilImage(qrcode.image.base.BaseImageWithDrawer):
"""
Styled PIL image builder, default format is PNG.
This differs from the PilImage in that there is a module_drawer, a
color_mask, and an optional image
The module_drawer should extend the QRModuleDrawer class and implement the
drawrect_context(self, box, active, context), and probably also the
initialize function. This will draw an individual "module" or square on
the QR code.
The color_mask will extend the QRColorMask class and will at very least
implement the get_fg_pixel(image, x, y) function, calculating a color to
put on the image at the pixel location (x,y) (more advanced functionality
can be gotten by instead overriding other functions defined in the
QRColorMask class)
The Image can be specified either by path or with a Pillow Image, and if it
is there will be placed in the middle of the QR code. No effort is done to
ensure that the QR code is still legible after the image has been placed
there; Q or H level error correction levels are recommended to maintain
data integrity A resampling filter can be specified (defaulting to
PIL.Image.Resampling.LANCZOS) for resizing; see PIL.Image.resize() for possible
options for this parameter.
The image size can be controlled by `embedded_image_ratio` which is a ratio
between 0 and 1 that's set in relation to the overall width of the QR code.
"""
kind = "PNG"
needs_processing = True
color_mask: QRColorMask
default_drawer_class = SquareModuleDrawer
def __init__(self, *args, **kwargs):
self.color_mask = kwargs.get("color_mask", SolidFillColorMask())
# allow embeded_ parameters with typos for backwards compatibility
embedded_image_path = kwargs.get(
"embedded_image_path", kwargs.get("embeded_image_path", None)
)
self.embedded_image = kwargs.get(
"embedded_image", kwargs.get("embeded_image", None)
)
self.embedded_image_ratio = kwargs.get(
"embedded_image_ratio", kwargs.get("embeded_image_ratio", 0.25)
)
self.embedded_image_resample = kwargs.get(
"embedded_image_resample",
kwargs.get("embeded_image_resample", Image.Resampling.LANCZOS),
)
if not self.embedded_image and embedded_image_path:
self.embedded_image = Image.open(embedded_image_path)
# the paint_color is the color the module drawer will use to draw upon
# a canvas During the color mask process, pixels that are paint_color
# are replaced by a newly-calculated color
self.paint_color = tuple(0 for i in self.color_mask.back_color)
if self.color_mask.has_transparency:
self.paint_color = tuple([*self.color_mask.back_color[:3], 255])
super().__init__(*args, **kwargs)
def new_image(self, **kwargs):
mode = (
"RGBA"
if (
self.color_mask.has_transparency
or (self.embedded_image and "A" in self.embedded_image.getbands())
)
else "RGB"
)
# This is the background color. Should be white or whiteish
back_color = self.color_mask.back_color
return Image.new(mode, (self.pixel_size, self.pixel_size), back_color)
def init_new_image(self):
self.color_mask.initialize(self, self._img)
super().init_new_image()
def process(self):
self.color_mask.apply_mask(self._img)
if self.embedded_image:
self.draw_embedded_image()
def draw_embedded_image(self):
if not self.embedded_image:
return
total_width, _ = self._img.size
total_width = int(total_width)
logo_width_ish = int(total_width * self.embedded_image_ratio)
logo_offset = (
int((int(total_width / 2) - int(logo_width_ish / 2)) / self.box_size)
* self.box_size
) # round the offset to the nearest module
logo_position = (logo_offset, logo_offset)
logo_width = total_width - logo_offset * 2
region = self.embedded_image
region = region.resize((logo_width, logo_width), self.embedded_image_resample)
if "A" in region.getbands():
self._img.alpha_composite(region, logo_position)
else:
self._img.paste(region, logo_position)
def save(self, stream, format=None, **kwargs):
if format is None:
format = kwargs.get("kind", self.kind)
if "kind" in kwargs:
del kwargs["kind"]
self._img.save(stream, format=format, **kwargs)
def __getattr__(self, name):
return getattr(self._img, name)
|