lincolnloop / python-qrcode

Python QR Code image generator
https://pypi.python.org/pypi/qrcode
Other
4.34k stars 664 forks source link

Custom eyes? #237

Open dovy opened 2 years ago

dovy commented 2 years ago

Is there anyway to do a custom eye with this framework? Something like this service offers: https://www.qrcode-monkey.com/qr-code-api-with-logo/

It would be great to have this feature. Set our own SVG and we can go to town. :)

JayPalm commented 2 years ago

This would be very cool. Found this issue after using QRcode-monkey as well. I really like some of the rounded square options.

JayPalm commented 2 years ago

Just figured out that you can use the custom module drawers on the eyes like this:

import qrcode
from qrcode.image.styledpil import StyledPilImage
from qrcode.image.styles.moduledrawers.pil import RoundedModuleDrawer
from qrcode.image.styles.colormasks import RadialGradiantColorMask

qr = qrcode.QRCode(error_correction=qrcode.constants.ERROR_CORRECT_L)
qr.add_data("Some data")

img_1 = qr.make_image(
    image_factory=StyledPilImage,
    module_drawer=RoundedModuleDrawer(),
    eye_drawer=RoundedModuleDrawer(),
)

Haven't yet figured out how to use the different eye shapes though, like a dot, or a partially rounded square, for example.

kevin-finvault commented 2 years ago

I was just looking into using the QR Code Monkey API too for more customisations, but it is paid.

Is there any way to change the colour of the eyes?

anbachduong commented 2 years ago

Just figured out that you can use the custom module drawers on the eyes like this:

import qrcode
from qrcode.image.styledpil import StyledPilImage
from qrcode.image.styles.moduledrawers.pil import RoundedModuleDrawer
from qrcode.image.styles.colormasks import RadialGradiantColorMask

qr = qrcode.QRCode(error_correction=qrcode.constants.ERROR_CORRECT_L)
qr.add_data("Some data")

img_1 = qr.make_image(
    image_factory=StyledPilImage,
    module_drawer=RoundedModuleDrawer(),
    eye_drawer=RoundedModuleDrawer(),
)

Haven't yet figured out how to use the different eye shapes though, like a dot, or a partially rounded square, for example.

I did it this way! I want to change color QR code?

kevin-finvault commented 2 years ago

I want to change color QR code?

Add the color_mask parameter to qr.make_image()

img = qr.make_image(
    image_factory=StyledPilImage,
    module_drawer=RoundedModuleDrawer(radius_ratio=1),
    eye_drawer=RoundedModuleDrawer(radius_ratio=1),
    color_mask=SolidFillColorMask(back_color=(255, 255, 255), front_color=(0, 0, 0)),
    embeded_image_path="path/to/image",
)
Louw-Reegan commented 2 years ago

I wrote a custom function to do this for me based on the standard positions of the Finder Patterns on QR codes:

from PIL import Image, ImageDraw
def style_eyes(img):
  img_size = img.size[0]
  eye_size = 70 #default
  quiet_zone = 40 #default
  mask = Image.new('L', img.size, 0)
  draw = ImageDraw.Draw(mask)
  draw.rectangle((40, 40, 110, 110), fill=255)
  draw.rectangle((img_size-110, 40, img_size-40, 110), fill=255)
  draw.rectangle((40, img_size-110, 110, img_size-40), fill=255)
  return mask

Then masked the two images together:

mask = style_eyes(qr_img)
final_img = Image.composite(qr_eyes_img, qr_img, mask)
final_img

I made the two images with the standard code form the README doc:

qr = qrcode.QRCode(error_correction=qrcode.constants.ERROR_CORRECT_H)
qr.add_data('http://www.amazon.com')

qr_eyes_img = qr.make_image(image_factory=StyledPilImage,
                            eye_drawer=RoundedModuleDrawer(radius_ratio=1.2),
                            color_mask=SolidFillColorMask(back_color=(255, 255, 255), front_color=(255, 110, 0)))

qr_img = qr.make_image(image_factory=StyledPilImage,
                       module_drawer=CircleModuleDrawer(),
                       color_mask=SolidFillColorMask(front_color=(59, 89, 152)),
                       embeded_image_path="/content/Facebook-logo (1).png")

qr_eyes_img image qr_img image mask image final_img image

PS this works for all size QR codes.

xtream1101 commented 1 year ago

I was trying to change the eye/location marker colors as well to be different colors. @Louw-Reegan 's solution takes ~1 second to run which seemed a bit slow. I found the real issue is the color_mask's that slow things down.

I recreated the moduledrawers into my own library that can be imported and used instead of the default moduledrawers. https://github.com/xtream1101/qrcode-xcolor

This can replicate the above output in only ~0.043 seconds vs the ~1 second the other solution takes.

I hope others find this helpful.

dhruv2983 commented 1 year ago

Updated the code for custom box and border size

import qrcode 
from qrcode.image.styledpil import StyledPilImage
from qrcode.image.styles.moduledrawers.pil import RoundedModuleDrawer
from qrcode.image.styles.colormasks import SolidFillColorMask
from PIL import Image, ImageDraw

qr = qrcode.QRCode(error_correction=qrcode.constants.ERROR_CORRECT_H , box_size=20  ,border= 2)
qr.add_data('data')

qr_eyes_img = qr.make_image(
image_factory=StyledPilImage,
eye_drawer=RoundedModuleDrawer(radius_ratio=1.2),
color_mask=SolidFillColorMask(back_color=(255, 255, 255), front_color=(199 , 8 ,8 ))
)

qr_img = qr.make_image(
image_factory=StyledPilImage,
module_drawer=RoundedModuleDrawer(),
embeded_image_path='logo.png'
)

def style_eyes(img):
    img_size = img.size[0]
    box_size = img.box_size
    border_size = (img_size/box_size - 33 ) /2
    quiet_zone = box_size * border_size
    eye_size = 7 * box_size
    quiet_eye_size = eye_size + quiet_zone
    mask = Image.new('L', img.size, 0)
    draw = ImageDraw.Draw(mask)
    draw.rectangle((quiet_zone, quiet_zone,quiet_eye_size,quiet_eye_size), fill=255)
    draw.rectangle((img_size-quiet_eye_size, quiet_zone, img_size-quiet_zone, quiet_eye_size), fill=255)
    draw.rectangle((quiet_zone, img_size-quiet_eye_size, quiet_eye_size, img_size-quiet_zone), fill=255)
    return mask

mask = style_eyes(qr_eyes_img)

img_rounded = Image.composite(qr_eyes_img, qr_img, mask)

img_rounded.save('qr_rounded.png')
TechnoRahmon commented 1 year ago

I tried to create a new custom class to use it in eye_drawer parameter in qr.make_image() method,

here is a class that will insert a custom image for each cell instead of drawing it :

from qrcode.image.styles.moduledrawers.base import QRModuleDrawer
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 StarModuleDrawer(StyledPilQRModuleDrawer):
    """
    Draws the modules as circles
    """

    circle = None

    def __init__(self, keyTest='None'):
        self.keyTest = keyTest

    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:
            box_size = self.img.box_size
            custom_image = Image.open(f"{base_path}/pattern4.png")
            custom_image.resize((box_size, box_size), Image.Resampling.LANCZOS)
            self.img._img.paste(custom_image, (box[0][0], box[0][1]),custom_image)

you can see the image path is static here

custom_image = Image.open(f"{base_path}/pattern4.png")

you can just pass it from the constructor in order to make it dynamic!

usage

here assign the custom class StarModuleDrawer in the make_image() method

import qrcode
from qrcode.image.styledpil import StyledPilImage
qr = qrcode.QRCode(error_correction=qrcode.constants.ERROR_CORRECT_H)

qr.add_data('http://www.medium.com')

qr_inner_eyes_img = qr.make_image(image_factory=StyledPilImage,

                            eye_drawer=StarModuleDrawer(),
                            color_mask=SolidFillColorMask(back_color=(255, 255, 255), front_color=(63, 42, 86)))
qr_inner_eyes_img.save(f"{base_path}/final_image.png")

The pattern4.png image is 10 x 10 :

The result in my case :

final_image

igarganuj commented 5 months ago

Can anyone help me how we can create QR code like below.. image (4)

subhransujena commented 4 months ago

Hi,

We need to create the below QR code using python or C#. The body shape will be in diamond shape and eye frame and eye ball will be default square shape. Also logo in center. Could anyone help me? 313493637-35d03abf-711e-401e-a32c-f75c306aa7e8

TechnoRahmon commented 3 months ago

Hi @subhransujena,

I can assist you with that if you still need help.

subhransujena commented 3 months ago

@TechnoRahmon Hi..I am still looking for help.

TechnoRahmon commented 3 months ago

@subhransujena can send me here techno.r@outlook.com

amirhoseinbidar commented 1 week ago

For Anyone who wants to customize their QRCode's eyes, use following code

import abc
from typing import TYPE_CHECKING, Any, Union

import qrcode
from PIL import ImageDraw
from qrcode.image.styledpil import StyledPilImage
from qrcode.image.styles.colormasks import SolidFillColorMask
from qrcode.image.styles.moduledrawers.pil import HorizontalBarsDrawer
from qrcode.main import QRCode

if TYPE_CHECKING:
    from qrcode.image.base import BaseImage
    from qrcode.main import ActiveWithNeighbors, QRCode

class BaseEyeDrawer(abc.ABC):
    needs_processing = True
    needs_neighbors = False
    factory: "StyledPilImage2"

    def initialize(self, img: "BaseImage") -> None:
        self.img = img

    def draw(self):
        (nw_eye_top, _), (_, nw_eye_bottom) = (
            self.factory.pixel_box(0, 0),
            self.factory.pixel_box(6, 6),
        )
        (nw_eyeball_top, _), (_, nw_eyeball_bottom) = (
            self.factory.pixel_box(2, 2),
            self.factory.pixel_box(4, 4),
        )
        self.draw_nw_eye((nw_eye_top, nw_eye_bottom))
        self.draw_nw_eyeball((nw_eyeball_top, nw_eyeball_bottom))

        (ne_eye_top, _), (_, ne_eye_bottom) = (
            self.factory.pixel_box(0, self.factory.width - 7),
            self.factory.pixel_box(6, self.factory.width - 1),
        )
        (ne_eyeball_top, _), (_, ne_eyeball_bottom) = (
            self.factory.pixel_box(2, self.factory.width - 5),
            self.factory.pixel_box(4, self.factory.width - 3),
        )
        self.draw_ne_eye((ne_eye_top, ne_eye_bottom))
        self.draw_ne_eyeball((ne_eyeball_top, ne_eyeball_bottom))

        (sw_eye_top, _), (_, sw_eye_bottom) = (
            self.factory.pixel_box(self.factory.width - 7, 0),
            self.factory.pixel_box(self.factory.width - 1, 6),
        )
        (sw_eyeball_top, _), (_, sw_eyeball_bottom) = (
            self.factory.pixel_box(self.factory.width - 5, 2),
            self.factory.pixel_box(self.factory.width - 3, 4),
        )
        self.draw_sw_eye((sw_eye_top, sw_eye_bottom))
        self.draw_sw_eyeball((sw_eyeball_top, sw_eyeball_bottom))

    @abc.abstractmethod
    def draw_nw_eye(self, position): ...

    @abc.abstractmethod
    def draw_nw_eyeball(self, position): ...

    @abc.abstractmethod
    def draw_ne_eye(self, position): ...

    @abc.abstractmethod
    def draw_ne_eyeball(self, position): ...

    @abc.abstractmethod
    def draw_sw_eye(self, position): ...

    @abc.abstractmethod
    def draw_sw_eyeball(self, position): ...

class CustomEyeDrawer(BaseEyeDrawer):
    def draw_nw_eye(self, position):
        draw = ImageDraw.Draw(self.img)
        draw.rounded_rectangle(
            position,
            fill=None,
            width=self.factory.box_size,
            outline="black",
            radius=self.factory.box_size * 2,
            corners=[True, True, False, True],
        )

    def draw_nw_eyeball(self, position):
        draw = ImageDraw.Draw(self.img)
        draw.rounded_rectangle(
            position,
            fill=True,
            outline="black",
            radius=self.factory.box_size,
            corners=[True, True, False, True],
        )

    def draw_ne_eye(self, position):
        draw = ImageDraw.Draw(self.img)
        draw.rounded_rectangle(
            position,
            fill=None,
            width=self.factory.box_size,
            outline="black",
            radius=self.factory.box_size * 2,
            corners=[True, True, True, False],
        )

    def draw_ne_eyeball(self, position):
        draw = ImageDraw.Draw(self.img)
        draw.rounded_rectangle(
            position,
            fill=True,
            outline="black",
            radius=self.factory.box_size,
            corners=[True, True, True, False],
        )

    def draw_sw_eye(self, position):
        draw = ImageDraw.Draw(self.img)
        draw.rounded_rectangle(
            position,
            fill=None,
            width=self.factory.box_size,
            outline="black",
            radius=self.factory.box_size * 2,
            corners=[True, False, True, True],
        )

    def draw_sw_eyeball(self, position):
        draw = ImageDraw.Draw(self.img)
        draw.rounded_rectangle(
            position,
            fill=True,
            outline="black",
            radius=self.factory.box_size,
            corners=[True, False, True, True],
        )

class StyledPilImage2(StyledPilImage):
    def drawrect_context(self, row: int, col: int, qr: QRCode[Any]):
        box = self.pixel_box(row, col)
        if self.is_eye(row, col):
            drawer = self.eye_drawer
            if getattr(self.eye_drawer, "needs_processing", False):
                return
        else:
            drawer = self.module_drawer

        is_active: Union[bool, ActiveWithNeighbors] = (
            qr.active_with_neighbors(row, col)
            if drawer.needs_neighbors
            else bool(qr.modules[row][col])
        )

        drawer.drawrect(box, is_active)

    def process(self) -> None:
        if getattr(self.eye_drawer, "needs_processing", False):
            self.eye_drawer.factory = self
            self.eye_drawer.draw()
        super().process()

qr = QRCode(error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=40)
qr.add_data("Some data")

img_1 = qr.make_image(
    image_factory=StyledPilImage2,
    module_drawer=HorizontalBarsDrawer(),
    eye_drawer=CustomEyeDrawer(),
    color_mask=SolidFillColorMask(front_color=(0, 157, 224)),
)
img_1.save("image.png")

Previous code will generate following QRCode.

Basically, you have to inherit the BaseEyeDrawer and populate its abstract methods in order to write your custom eyes. Note that here, I use the eye term for the outer part of an eye and eyeball for the inner part. Each one of BaseEyeDrawer's abstract methods takes a position argument indicating the position of an eye or eyeball in the image as a rectangle. With this information, it is easy to draw the eyes however you like. If you want to set the thickness of the eye lines identical to that of other parts of QRCode, use self.factory.box_size. I tried to follow source code design, therefore, this code probably does not break other functionalities. However, use at your own risk.