Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/8.0 #381

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
6 changes: 6 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
Change log
==========

8.1 (unreleased)
==================

- Nothing changed yet.


8.0 ( 27 September 2024)
========================

Expand Down
13 changes: 10 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ Pure python QR Code generator

Generate QR codes.

A standard install uses pypng_ to generate PNG files and can also render QR
A standard install generates SVG files and can also render QR
codes directly to the console. A standard install is just::

pip install qrcode

For more image functionality, install qrcode with the ``pil`` dependency so
that pillow_ is installed and can be used for generating images::
For more image functionality, install qrcode with the ``pil`` or ``png``
dependencies so that pillow_ or pypng_ is installed and can be used for
generating images::

pip install "qrcode[pil]"
pip install "qrcode[png]"

.. _pypng: https://pypi.python.org/pypi/pypng
.. _pillow: https://pypi.python.org/pypi/Pillow
Expand All @@ -32,6 +34,11 @@ Usage

From the command line, use the installed ``qr`` script::

qr "Some text" > test.svg

If you installed the ``pil`` or ``png`` dependencies, those will be used by
default by the script and the output will be a PNG image::

qr "Some text" > test.png

Or in Python, use the ``make`` shortcut function:
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "qrcode"
version = "8.0"
version = "8.1.dev0"
packages = [{include = "qrcode"}]
include = [{ path = "doc", format = "sdist" }]
description = "QR Code image generator"
authors = ["Lincoln Loop <[email protected]>"]
license = "BSD"
Expand Down
8 changes: 7 additions & 1 deletion qrcode/console_scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
qr - Convert stdin (or the first argument) to a QR Code.

When stdout is a tty the QR Code is printed to the terminal and when stdout is
a pipe to a file an image is written. The default image format is PNG.
a pipe to a file an image is written. The default image format is PNG if pil or
png optional dependencies are installed, otherwise SVG.
"""

import optparse
Expand Down Expand Up @@ -111,6 +112,11 @@ def raise_error(msg: str) -> NoReturn:
else:
qr.add_data(data, optimize=opts.optimize)

try:
qr.make()
except qrcode.exceptions.DataOverflowError:
raise_error("too much data to fit in QR code")

if opts.output:
img = qr.make_image()
with open(opts.output, "wb") as out:
Expand Down
6 changes: 5 additions & 1 deletion qrcode/image/pil.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import qrcode.image.base
from PIL import Image, ImageDraw

try:
from PIL import Image, ImageDraw
except ImportError:
Image, ImageDraw = None, None


class PilImage(qrcode.image.base.BaseImage):
Expand Down
25 changes: 16 additions & 9 deletions qrcode/image/styles/colormasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,29 @@ def initialize(self, styledPilImage, image):

def apply_mask(self, image):
width, height = image.size
pixels = image.load()
fg_color_cache = {}
for x in range(width):
for y in range(height):
current_color = pixels[x, y]
if current_color == self.back_color:
continue
if current_color in fg_color_cache:
pixels[x, y] = fg_color_cache[current_color]
continue
norm = self.extrap_color(
self.back_color, self.paint_color, image.getpixel((x, y))
self.back_color, self.paint_color, current_color
)
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,
),
new_color = self.interp_color(
self.get_bg_pixel(image, x, y),
self.get_fg_pixel(image, x, y),
norm,
)
pixels[x, y] = new_color
fg_color_cache[current_color] = new_color
else:
image.putpixel((x, y), self.get_bg_pixel(image, x, y))
pixels[x, y] = self.get_bg_pixel(image, x, y)

def get_fg_pixel(self, image, x, y):
raise NotImplementedError("QRModuleDrawer.paint_fg_pixel")
Expand Down
20 changes: 12 additions & 8 deletions qrcode/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,17 @@
Dict,
Generic,
List,
Literal,
NamedTuple,
Optional,
Type,
TypeVar,
cast,
overload,
Literal,
)

from qrcode import constants, exceptions, util
from qrcode.image.base import BaseImage
from qrcode.image.pure import PyPNGImage

ModulesType = List[List[Optional[bool]]]
# Cache modules generated just based on the QR Code version
Expand Down Expand Up @@ -155,8 +154,8 @@ def make(self, fit=True):
:param fit: If ``True`` (or if a size has not been provided), find the
best fit for the data to avoid data overflow errors.
"""
if fit or (self.version is None):
self.best_fit(start=self.version)
if fit or (self._version is None):
self.best_fit(start=self._version)
if self.mask_pattern is None:
self.makeImpl(False, self.best_mask_pattern())
else:
Expand Down Expand Up @@ -226,11 +225,12 @@ def best_fit(self, start=None):
data.write(buffer)

needed_bits = len(buffer)
self.version = bisect_left(
new_version = bisect_left(
util.BIT_LIMIT_TABLE[self.error_correction], needed_bits, start
)
if self.version == 41:
if new_version == 41:
raise exceptions.DataOverflowError()
self.version = new_version

# Now check whether we need more bits for the mode sizes, recursing if
# our guess was too low
Expand Down Expand Up @@ -361,9 +361,13 @@ def make_image(self, image_factory=None, **kwargs):
image_factory = self.image_factory
if image_factory is None:
from qrcode.image.pil import Image, PilImage
from qrcode.image.pure import PngWriter, PyPNGImage
from qrcode.image.svg import SvgImage

# Use PIL by default if available, otherwise use PyPNG.
image_factory = PilImage if Image else PyPNGImage
# Use PIL by default if available, otherwise use PyPNG or SVG
image_factory = (
PilImage if Image else PyPNGImage if PngWriter else SvgImage
)

im = image_factory(
self.border,
Expand Down
8 changes: 8 additions & 0 deletions qrcode/tests/test_qrcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ def test_mode_number():
assert qr.data_list[0].mode == MODE_NUMBER


def test_fit_overflow():
# Alphanumeric. Version 40 with ERROR_CORRECT_L has max 4296 characters.
qr = qrcode.QRCode(error_correction=qrcode.constants.ERROR_CORRECT_L)
qr.add_data("A" * 4297)
with pytest.raises(DataOverflowError):
qr.make()


def test_mode_alpha():
qr = qrcode.QRCode()
qr.add_data("ABCDEFGHIJ1234567890", optimize=0)
Expand Down
6 changes: 1 addition & 5 deletions qrcode/tests/test_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ def test_isatty(mock_print_ascii):

@mock.patch("os.isatty", lambda *args: False)
def test_piped():
pytest.importorskip("PIL", reason="Requires PIL")
main(["testtext"])


Expand Down Expand Up @@ -48,7 +47,6 @@ def test_stdin_py3_unicodedecodeerror():


def test_optimize():
pytest.importorskip("PIL", reason="Requires PIL")
main("testtext --optimize 0".split())


Expand All @@ -63,13 +61,11 @@ def test_bad_factory():

@mock.patch.object(sys, "argv", "qr testtext output".split())
def test_sys_argv():
pytest.importorskip("PIL", reason="Requires PIL")
main()


def test_output(tmp_path):
pytest.importorskip("PIL", reason="Requires PIL")
main(["testtext", "--output", str(tmp_path / "test.png")])
main(["testtext", "--output", str(tmp_path / "test.svg")])


def test_factory_drawer_none(capsys):
Expand Down