Skip to content

Commit

Permalink
Merge branch 'lincolnloop:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
alfreedom committed Sep 23, 2022
2 parents 7b194c6 + 7eb16ae commit 989b75e
Show file tree
Hide file tree
Showing 11 changed files with 100 additions and 96 deletions.
7 changes: 7 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ Change log
- Optimize the output for the ``SVGPathImage`` factory (more than 30% reduction
in file sizes).

- Add a ``pypng`` image factory as a pure Python PNG solution. If ``pillow`` is
*not* installed, then this becomes the default factory.

- The ``pymaging`` image factory has been removed, but its factory shortcut and
the actual PymagingImage factory class now just link to the PyPNGImage
factory.


7.3.1 (1 October 2021)
======================
Expand Down
2 changes: 1 addition & 1 deletion PACKAGING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ Make sure maintainer dependencies are installed::

pip install -e .[maintainer,dev]

Run release commad and follow prompt instuctions::
Run release command and follow prompt instructions::

fullrelease
38 changes: 19 additions & 19 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@ Pure python QR Code generator

Generate QR codes.

For a standard install (which will include pillow_ for generating images),
run::
A standard install uses pypng_ to generate PNG files and can also render QR
codes directly to the console. A standard install is just::

pip install qrcode[pil]
pip install pil

.. _pillow: https://pypi.python.org/pypi/Pillow
For more image functionality, install qrcode with the ``pil`` dependency so
that pillow_ is installed and can be used for generating images::

For macOS run::
pip install "qrcode[pil]"

pip install qrcode"[pil]"
.. _pypng: https://pypi.python.org/pypi/pypng
.. _pillow: https://pypi.python.org/pypi/Pillow


What is a QR Code?
Expand Down Expand Up @@ -141,9 +143,9 @@ background of the SVG with white::
qrcode.image.svg.SvgFillImage
qrcode.image.svg.SvgPathFillImage

The ``QRCode.make_image()`` method forwards additional keyword arguments to
the underlying ElementTree XML library. This helps to finetune the root element
of the resulting SVG:
The ``QRCode.make_image()`` method forwards additional keyword arguments to the
underlying ElementTree XML library. This helps to fine tune the root element of
the resulting SVG:

.. code:: python
Expand All @@ -165,22 +167,20 @@ Additional keyword arguments are forwarded to ElementTrees ``tostring()``:
Pure Python PNG
---------------

Install the following two packages::
If Pillow is not installed, the default image factory will be a pure Python PNG
encoder that uses `pypng`.

pip install -e git+git://github.com/ojii/pymaging.git#egg=pymaging
pip install -e git+git://github.com/ojii/pymaging-png.git#egg=pymaging-png

From your command line::
You can use the factory explicitly from your command line::

qr --factory=pymaging "Some text" > test.png
qr --factory=png "Some text" > test.png

Or in Python:

.. code:: python
import qrcode
from qrcode.image.pure import PymagingImage
img = qrcode.make('Some data here', image_factory=PymagingImage)
from qrcode.image.pure import PyPNGImage
img = qrcode.make('Some data here', image_factory=PyPNGImage)
Styled Image
Expand Down Expand Up @@ -217,7 +217,7 @@ Other color masks:

.. image:: doc/color_masks.png

Here is a code example to draw a QR code with rounded corners, radial gradiant
Here is a code example to draw a QR code with rounded corners, radial gradient
and an embedded image:

.. code:: python
Expand Down Expand Up @@ -267,7 +267,7 @@ Pipe ascii output to text file in command line::
qr --ascii "Some data" > "test.txt"
cat test.txt

Alternative to piping output to file to avoid PoweShell issues::
Alternative to piping output to file to avoid PowerShell issues::

# qr "Some data" > test.png
qr --output=test.png "Some data"
5 changes: 3 additions & 2 deletions doc/qr.1
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ Show a help message.
.RS 4
Full python path to the image factory class to create the
image with. You can use the following shortcuts to the
built-in image factory classes: pil (default), pymaging,
svg, svg-fragment, svg-path.
built-in image factory classes: pil (default), png (
default if pillow is not installed), svg, svg-fragment,
svg-path.
.RE

.PP
Expand Down
4 changes: 3 additions & 1 deletion qrcode/console_scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@

default_factories = {
"pil": "qrcode.image.pil.PilImage",
"pymaging": "qrcode.image.pure.PymagingImage",
"png": "qrcode.image.pure.PyPNGImage",
"svg": "qrcode.image.svg.SvgImage",
"svg-fragment": "qrcode.image.svg.SvgFragmentImage",
"svg-path": "qrcode.image.svg.SvgPathImage",
# Keeping for backwards compatibility:
"pymaging": "qrcode.image.pure.PymagingImage",
}

error_correction = {
Expand Down
2 changes: 2 additions & 0 deletions qrcode/image/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ class BaseImage:
allowed_kinds: Optional[Tuple[str]] = None
needs_context = False
needs_processing = False
needs_drawrect = True

def __init__(self, border, width, box_size, *args, **kwargs):
self.border = border
self.width = width
self.box_size = box_size
self.pixel_size = (self.width + self.border * 2) * self.box_size
self._img = self.new_image(**kwargs)
self.modules = kwargs["qrcode_modules"]
self.init_new_image()

@abc.abstractmethod
Expand Down
72 changes: 37 additions & 35 deletions qrcode/image/pure.py
Original file line number Diff line number Diff line change
@@ -1,50 +1,52 @@
from itertools import chain

import png

import qrcode.image.base
from pymaging import Image # type: ignore
from pymaging.colors import RGB # type: ignore
from pymaging.formats import registry # type: ignore
from pymaging.shapes import Line # type: ignore
from pymaging.webcolors import Black, White # type: ignore
from pymaging_png.png import PNG # type: ignore


class PymagingImage(qrcode.image.base.BaseImage):
class PyPNGImage(qrcode.image.base.BaseImage):
"""
pymaging image builder, default format is PNG.
pyPNG image builder.
"""

kind = "PNG"
allowed_kinds = ("PNG",)

def __init__(self, *args, **kwargs):
"""
Register PNG with pymaging.
"""
registry.formats = []
registry.names = {}
registry._populate()
registry.register(PNG)

super().__init__(*args, **kwargs)
needs_drawrect = False

def new_image(self, **kwargs):
return Image.new(RGB, self.pixel_size, self.pixel_size, White)
return png.Writer(self.pixel_size, self.pixel_size, greyscale=True, bitdepth=1)

def drawrect(self, row, col):
(x, y), (x2, y2) = self.pixel_box(row, col)
for r in range(self.box_size):
line_y = y + r
line = Line(x, line_y, x2, line_y)
self._img.draw(line, Black)

def save(self, stream, kind=None):
self._img.save(stream, self.check_kind(kind))

def check_kind(self, kind, transform=None, **kwargs):
"""
pymaging (pymaging_png at least) uses lower case for the type.
Not used.
"""

def lower_case(x):
return x.lower()

return super().check_kind(kind, transform=transform or lower_case, **kwargs)
def save(self, stream, kind=None):
self._img.write(stream, self.rows_iter())

def rows_iter(self):
yield from self.border_rows_iter()
border_col = [1] * (self.box_size * self.border)
for module_row in self.modules:
row = (
border_col
+ list(
chain.from_iterable(
([not point] * self.box_size) for point in module_row
)
)
+ border_col
)
for _ in range(self.box_size):
yield row
yield from self.border_rows_iter()

def border_rows_iter(self):
border_row = [1] * (self.box_size * (self.width + self.border * 2))
for _ in range(self.border * self.box_size):
yield border_row


# Keeping this for backwards compatibility.
PymagingImage = PyPNGImage
28 changes: 18 additions & 10 deletions qrcode/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

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 @@ -356,19 +357,26 @@ def make_image(self, image_factory=None, **kwargs):
else:
image_factory = self.image_factory
if image_factory is None:
# Use PIL by default
from qrcode.image.pil import PilImage
from qrcode.image.pil import Image, PilImage

image_factory = PilImage
# Use PIL by default if available, otherwise use PyPNG.
image_factory = PilImage if Image else PyPNGImage

im = image_factory(self.border, self.modules_count, self.box_size, **kwargs)
im = image_factory(
self.border,
self.modules_count,
self.box_size,
qrcode_modules=self.modules,
**kwargs,
)

for r in range(self.modules_count):
for c in range(self.modules_count):
if im.needs_context:
im.drawrect_context(r, c, qr=self)
elif self.modules[r][c]:
im.drawrect(r, c)
if im.needs_drawrect:
for r in range(self.modules_count):
for c in range(self.modules_count):
if im.needs_context:
im.drawrect_context(r, c, qr=self)
elif self.modules[r][c]:
im.drawrect(r, c)
if im.needs_processing:
im.process()

Expand Down
32 changes: 8 additions & 24 deletions qrcode/tests/test_qrcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,18 @@
from tempfile import mkdtemp
from unittest import mock

import png

import qrcode
import qrcode.util
from qrcode.compat.pil import Image as pil_Image
from qrcode.exceptions import DataOverflowError
from qrcode.image.base import BaseImage
from qrcode.image.pure import PyPNGImage
from qrcode.image.styledpil import StyledPilImage
from qrcode.image.styles import colormasks, moduledrawers
from qrcode.util import MODE_8BIT_BYTE, MODE_ALPHA_NUM, MODE_NUMBER, QRData

try:
import pymaging_png # type: ignore

from qrcode.image.pure import PymagingImage
except ImportError: # pragma: no cover
pymaging_png = None


UNICODE_TEXT = "\u03b1\u03b2\u03b3"
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
Expand Down Expand Up @@ -180,25 +175,14 @@ class MockFactory(BaseImage):
self.assertTrue(MockFactory.new_image.called)
self.assertTrue(MockFactory.drawrect.called)

@unittest.skipIf(not pymaging_png, "Requires pymaging with PNG support")
def test_render_pymaging_png(self):
def test_render_pypng(self):
qr = qrcode.QRCode()
qr.add_data(UNICODE_TEXT)
img = qr.make_image(image_factory=PymagingImage)
from pymaging import Image as pymaging_Image # type: ignore

self.assertIsInstance(img.get_image(), pymaging_Image)
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
img.save(io.BytesIO())
img = qr.make_image(image_factory=PyPNGImage)
self.assertIsInstance(img.get_image(), png.Writer)

@unittest.skipIf(not pymaging_png, "Requires pymaging")
def test_render_pymaging_png_bad_kind(self):
qr = qrcode.QRCode()
qr.add_data(UNICODE_TEXT)
img = qr.make_image(image_factory=PymagingImage)
with self.assertRaises(ValueError):
img.save(io.BytesIO(), kind="FISH")
print(img.width, img.box_size, img.border)
img.save(io.BytesIO())

@unittest.skipIf(not pil_Image, "Requires PIL")
def test_render_styled_Image(self):
Expand Down
3 changes: 2 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = qrcode
version = 7.4.dev0
version = 8.0.dev0
description = QR Code image generator
long_description = file: README.rst, CHANGES.rst
author = Lincoln Loop
Expand Down Expand Up @@ -31,6 +31,7 @@ packages = find:
install_requires =
colorama;platform_system=="Windows"
typing_extensions
pypng
python_requires = >= 3.6

[options.extras_require]
Expand Down
3 changes: 0 additions & 3 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@ usedevelop = True
extras =
test
pil
deps =
git+git://github.com/ojii/pymaging.git#egg=pymaging
git+git://github.com/ojii/pymaging-png.git#egg=pymaging-png
commands = coverage run -m pytest

[testenv:nopil]
Expand Down

0 comments on commit 989b75e

Please sign in to comment.