updates
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,220 @@
|
||||
# Needed on case-insensitive filesystems
|
||||
from __future__ import absolute_import
|
||||
|
||||
import math
|
||||
|
||||
from qrcode.compat.pil import Image
|
||||
|
||||
|
||||
class QRColorMask:
|
||||
"""
|
||||
QRColorMask is used to color in the QRCode.
|
||||
|
||||
By the time apply_mask is called, the QRModuleDrawer of the StyledPilImage
|
||||
will have drawn all of the modules on the canvas (the color of these
|
||||
modules will be mostly black, although antialiasing may result in
|
||||
gradients) In the base class, apply_mask is implemented such that the
|
||||
background color will remain, but the foreground pixels will be replaced by
|
||||
a color determined by a call to get_fg_pixel. There is additional
|
||||
calculation done to preserve the gradient artifacts of antialiasing.
|
||||
|
||||
All QRColorMask objects should be careful about RGB vs RGBA color spaces.
|
||||
|
||||
For examples of what these look like, see doc/color_masks.png
|
||||
"""
|
||||
|
||||
back_color = (255, 255, 255)
|
||||
has_transparency = False
|
||||
paint_color = back_color
|
||||
|
||||
def initialize(self, styledPilImage, image):
|
||||
self.paint_color = styledPilImage.paint_color
|
||||
|
||||
def apply_mask(self, image):
|
||||
width, height = image.size
|
||||
for x in range(width):
|
||||
for y in range(height):
|
||||
norm = self.extrap_color(
|
||||
self.back_color, self.paint_color, image.getpixel((x, y))
|
||||
)
|
||||
if norm is not None:
|
||||
image.putpixel(
|
||||
(x, y),
|
||||
self.interp_color(
|
||||
self.get_bg_pixel(image, x, y),
|
||||
self.get_fg_pixel(image, x, y),
|
||||
norm,
|
||||
),
|
||||
)
|
||||
else:
|
||||
image.putpixel((x, y), self.get_bg_pixel(image, x, y))
|
||||
|
||||
def get_fg_pixel(self, image, x, y):
|
||||
raise NotImplementedError("QRModuleDrawer.paint_fg_pixel")
|
||||
|
||||
def get_bg_pixel(self, image, x, y):
|
||||
return self.back_color
|
||||
|
||||
# The following functions are helpful for color calculation:
|
||||
|
||||
# interpolate a number between two numbers
|
||||
def interp_num(self, n1, n2, norm):
|
||||
return int(n2 * norm + n1 * (1 - norm))
|
||||
|
||||
# interpolate a color between two colorrs
|
||||
def interp_color(self, col1, col2, norm):
|
||||
return tuple(self.interp_num(col1[i], col2[i], norm) for i in range(len(col1)))
|
||||
|
||||
# find the interpolation coefficient between two numbers
|
||||
def extrap_num(self, n1, n2, interped_num):
|
||||
if n2 == n1:
|
||||
return None
|
||||
else:
|
||||
return (interped_num - n1) / (n2 - n1)
|
||||
|
||||
# find the interpolation coefficient between two numbers
|
||||
def extrap_color(self, col1, col2, interped_color):
|
||||
normed = []
|
||||
for c1, c2, ci in zip(col1, col2, interped_color):
|
||||
extrap = self.extrap_num(c1, c2, ci)
|
||||
if extrap is not None:
|
||||
normed.append(extrap)
|
||||
if not normed:
|
||||
return None
|
||||
return sum(normed) / len(normed)
|
||||
|
||||
|
||||
class SolidFillColorMask(QRColorMask):
|
||||
"""
|
||||
Just fills in the background with one color and the foreground with another
|
||||
"""
|
||||
|
||||
def __init__(self, back_color=(255, 255, 255), front_color=(0, 0, 0)):
|
||||
self.back_color = back_color
|
||||
self.front_color = front_color
|
||||
self.has_transparency = len(self.back_color) == 4
|
||||
|
||||
def apply_mask(self, image):
|
||||
if self.back_color == (255, 255, 255) and self.front_color == (0, 0, 0):
|
||||
# Optimization: the image is already drawn by QRModuleDrawer in
|
||||
# black and white, so if these are also our mask colors we don't
|
||||
# need to do anything. This is much faster than actually applying a
|
||||
# mask.
|
||||
pass
|
||||
else:
|
||||
# TODO there's probably a way to use PIL.ImageMath instead of doing
|
||||
# the individual pixel comparisons that the base class uses, which
|
||||
# would be a lot faster. (In fact doing this would probably remove
|
||||
# the need for the B&W optimization above.)
|
||||
QRColorMask.apply_mask(self, image)
|
||||
|
||||
def get_fg_pixel(self, image, x, y):
|
||||
return self.front_color
|
||||
|
||||
|
||||
class RadialGradiantColorMask(QRColorMask):
|
||||
"""
|
||||
Fills in the foreground with a radial gradient from the center to the edge
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, back_color=(255, 255, 255), center_color=(0, 0, 0), edge_color=(0, 0, 255)
|
||||
):
|
||||
self.back_color = back_color
|
||||
self.center_color = center_color
|
||||
self.edge_color = edge_color
|
||||
self.has_transparency = len(self.back_color) == 4
|
||||
|
||||
def get_fg_pixel(self, image, x, y):
|
||||
width, _ = image.size
|
||||
normedDistanceToCenter = math.sqrt(
|
||||
(x - width / 2) ** 2 + (y - width / 2) ** 2
|
||||
) / (math.sqrt(2) * width / 2)
|
||||
return self.interp_color(
|
||||
self.center_color, self.edge_color, normedDistanceToCenter
|
||||
)
|
||||
|
||||
|
||||
class SquareGradiantColorMask(QRColorMask):
|
||||
"""
|
||||
Fills in the foreground with a square gradient from the center to the edge
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, back_color=(255, 255, 255), center_color=(0, 0, 0), edge_color=(0, 0, 255)
|
||||
):
|
||||
self.back_color = back_color
|
||||
self.center_color = center_color
|
||||
self.edge_color = edge_color
|
||||
self.has_transparency = len(self.back_color) == 4
|
||||
|
||||
def get_fg_pixel(self, image, x, y):
|
||||
width, _ = image.size
|
||||
normedDistanceToCenter = max(abs(x - width / 2), abs(y - width / 2)) / (
|
||||
width / 2
|
||||
)
|
||||
return self.interp_color(
|
||||
self.center_color, self.edge_color, normedDistanceToCenter
|
||||
)
|
||||
|
||||
|
||||
class HorizontalGradiantColorMask(QRColorMask):
|
||||
"""
|
||||
Fills in the foreground with a gradient sweeping from the left to the right
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, back_color=(255, 255, 255), left_color=(0, 0, 0), right_color=(0, 0, 255)
|
||||
):
|
||||
self.back_color = back_color
|
||||
self.left_color = left_color
|
||||
self.right_color = right_color
|
||||
self.has_transparency = len(self.back_color) == 4
|
||||
|
||||
def get_fg_pixel(self, image, x, y):
|
||||
width, _ = image.size
|
||||
return self.interp_color(self.left_color, self.right_color, x / width)
|
||||
|
||||
|
||||
class VerticalGradiantColorMask(QRColorMask):
|
||||
"""
|
||||
Fills in the forefround with a gradient sweeping from the top to the bottom
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, back_color=(255, 255, 255), top_color=(0, 0, 0), bottom_color=(0, 0, 255)
|
||||
):
|
||||
self.back_color = back_color
|
||||
self.top_color = top_color
|
||||
self.bottom_color = bottom_color
|
||||
self.has_transparency = len(self.back_color) == 4
|
||||
|
||||
def get_fg_pixel(self, image, x, y):
|
||||
width, _ = image.size
|
||||
return self.interp_color(self.top_color, self.bottom_color, y / width)
|
||||
|
||||
|
||||
class ImageColorMask(QRColorMask):
|
||||
"""
|
||||
Fills in the foreground with pixels from another image, either passed by
|
||||
path or passed by image object.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, back_color=(255, 255, 255), color_mask_path=None, color_mask_image=None
|
||||
):
|
||||
self.back_color = back_color
|
||||
if color_mask_image:
|
||||
self.color_img = color_mask_image
|
||||
else:
|
||||
self.color_img = Image.open(color_mask_path)
|
||||
|
||||
self.has_transparency = len(self.back_color) == 4
|
||||
|
||||
def initialize(self, styledPilImage, image):
|
||||
self.paint_color = styledPilImage.paint_color
|
||||
self.color_img = self.color_img.resize(image.size)
|
||||
|
||||
def get_fg_pixel(self, image, x, y):
|
||||
width, _ = image.size
|
||||
return self.color_img.getpixel((x, y))
|
||||
@@ -0,0 +1,10 @@
|
||||
# For backwards compatibility, importing the PIL drawers here.
|
||||
try:
|
||||
from .pil import CircleModuleDrawer # noqa: F401
|
||||
from .pil import GappedSquareModuleDrawer # noqa: F401
|
||||
from .pil import HorizontalBarsDrawer # noqa: F401
|
||||
from .pil import RoundedModuleDrawer # noqa: F401
|
||||
from .pil import SquareModuleDrawer # noqa: F401
|
||||
from .pil import VerticalBarsDrawer # noqa: F401
|
||||
except ImportError:
|
||||
pass
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,36 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import abc
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from qrcode.image.base import BaseImage
|
||||
|
||||
|
||||
class QRModuleDrawer(abc.ABC):
|
||||
"""
|
||||
QRModuleDrawer exists to draw the modules of the QR Code onto images.
|
||||
|
||||
For this, technically all that is necessary is a ``drawrect(self, box,
|
||||
is_active)`` function which takes in the box in which it is to draw,
|
||||
whether or not the box is "active" (a module exists there). If
|
||||
``needs_neighbors`` is set to True, then the method should also accept a
|
||||
``neighbors`` kwarg (the neighboring pixels).
|
||||
|
||||
It is frequently necessary to also implement an "initialize" function to
|
||||
set up values that only the containing Image class knows about.
|
||||
|
||||
For examples of what these look like, see doc/module_drawers.png
|
||||
"""
|
||||
|
||||
needs_neighbors = False
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
pass
|
||||
|
||||
def initialize(self, img: "BaseImage") -> None:
|
||||
self.img = img
|
||||
|
||||
@abc.abstractmethod
|
||||
def drawrect(self, box, is_active) -> None:
|
||||
...
|
||||
@@ -0,0 +1,266 @@
|
||||
# Needed on case-insensitive filesystems
|
||||
from __future__ import absolute_import
|
||||
|
||||
from typing import TYPE_CHECKING, List
|
||||
|
||||
from qrcode.compat.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)
|
||||
)
|
||||
@@ -0,0 +1,141 @@
|
||||
import abc
|
||||
from decimal import Decimal
|
||||
from typing import TYPE_CHECKING, NamedTuple
|
||||
|
||||
from qrcode.image.styles.moduledrawers.base import QRModuleDrawer
|
||||
from qrcode.compat.etree import ET
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from qrcode.image.svg import SvgFragmentImage, SvgPathImage
|
||||
|
||||
ANTIALIASING_FACTOR = 4
|
||||
|
||||
|
||||
class Coords(NamedTuple):
|
||||
x0: Decimal
|
||||
y0: Decimal
|
||||
x1: Decimal
|
||||
y1: Decimal
|
||||
xh: Decimal
|
||||
yh: Decimal
|
||||
|
||||
|
||||
class BaseSvgQRModuleDrawer(QRModuleDrawer):
|
||||
img: "SvgFragmentImage"
|
||||
|
||||
def __init__(self, *, size_ratio: Decimal = Decimal(1), **kwargs):
|
||||
self.size_ratio = size_ratio
|
||||
|
||||
def initialize(self, *args, **kwargs) -> None:
|
||||
super().initialize(*args, **kwargs)
|
||||
self.box_delta = (1 - self.size_ratio) * self.img.box_size / 2
|
||||
self.box_size = Decimal(self.img.box_size) * self.size_ratio
|
||||
self.box_half = self.box_size / 2
|
||||
|
||||
def coords(self, box) -> Coords:
|
||||
row, col = box[0]
|
||||
x = row + self.box_delta
|
||||
y = col + self.box_delta
|
||||
|
||||
return Coords(
|
||||
x,
|
||||
y,
|
||||
x + self.box_size,
|
||||
y + self.box_size,
|
||||
x + self.box_half,
|
||||
y + self.box_half,
|
||||
)
|
||||
|
||||
|
||||
class SvgQRModuleDrawer(BaseSvgQRModuleDrawer):
|
||||
tag = "rect"
|
||||
|
||||
def initialize(self, *args, **kwargs) -> None:
|
||||
super().initialize(*args, **kwargs)
|
||||
self.tag_qname = ET.QName(self.img._SVG_namespace, self.tag)
|
||||
|
||||
def drawrect(self, box, is_active: bool):
|
||||
if not is_active:
|
||||
return
|
||||
self.img._img.append(self.el(box))
|
||||
|
||||
@abc.abstractmethod
|
||||
def el(self, box):
|
||||
...
|
||||
|
||||
|
||||
class SvgSquareDrawer(SvgQRModuleDrawer):
|
||||
def initialize(self, *args, **kwargs) -> None:
|
||||
super().initialize(*args, **kwargs)
|
||||
self.unit_size = self.img.units(self.box_size)
|
||||
|
||||
def el(self, box):
|
||||
coords = self.coords(box)
|
||||
return ET.Element(
|
||||
self.tag_qname, # type: ignore
|
||||
x=self.img.units(coords.x0),
|
||||
y=self.img.units(coords.y0),
|
||||
width=self.unit_size,
|
||||
height=self.unit_size,
|
||||
)
|
||||
|
||||
|
||||
class SvgCircleDrawer(SvgQRModuleDrawer):
|
||||
tag = "circle"
|
||||
|
||||
def initialize(self, *args, **kwargs) -> None:
|
||||
super().initialize(*args, **kwargs)
|
||||
self.radius = self.img.units(self.box_half)
|
||||
|
||||
def el(self, box):
|
||||
coords = self.coords(box)
|
||||
return ET.Element(
|
||||
self.tag_qname, # type: ignore
|
||||
cx=self.img.units(coords.xh),
|
||||
cy=self.img.units(coords.yh),
|
||||
r=self.radius,
|
||||
)
|
||||
|
||||
|
||||
class SvgPathQRModuleDrawer(BaseSvgQRModuleDrawer):
|
||||
img: "SvgPathImage"
|
||||
|
||||
def drawrect(self, box, is_active: bool):
|
||||
if not is_active:
|
||||
return
|
||||
self.img._subpaths.append(self.subpath(box))
|
||||
|
||||
@abc.abstractmethod
|
||||
def subpath(self, box) -> str:
|
||||
...
|
||||
|
||||
|
||||
class SvgPathSquareDrawer(SvgPathQRModuleDrawer):
|
||||
def subpath(self, box) -> str:
|
||||
coords = self.coords(box)
|
||||
x0 = self.img.units(coords.x0, text=False)
|
||||
y0 = self.img.units(coords.y0, text=False)
|
||||
x1 = self.img.units(coords.x1, text=False)
|
||||
y1 = self.img.units(coords.y1, text=False)
|
||||
|
||||
return f"M{x0},{y0}H{x1}V{y1}H{x0}z"
|
||||
|
||||
|
||||
class SvgPathCircleDrawer(SvgPathQRModuleDrawer):
|
||||
def initialize(self, *args, **kwargs) -> None:
|
||||
super().initialize(*args, **kwargs)
|
||||
|
||||
def subpath(self, box) -> str:
|
||||
coords = self.coords(box)
|
||||
x0 = self.img.units(coords.x0, text=False)
|
||||
yh = self.img.units(coords.yh, text=False)
|
||||
h = self.img.units(self.box_half - self.box_delta, text=False)
|
||||
x1 = self.img.units(coords.x1, text=False)
|
||||
|
||||
# rx,ry is the centerpoint of the arc
|
||||
# 1? is the x-axis-rotation
|
||||
# 2? is the large-arc-flag
|
||||
# 3? is the sweep flag
|
||||
# x,y is the point the arc is drawn to
|
||||
|
||||
return f"M{x0},{yh}A{h},{h} 0 0 0 {x1},{yh}A{h},{h} 0 0 0 {x0},{yh}z"
|
||||
Reference in New Issue
Block a user