Skip to content

Commit

Permalink
decoders: use sane data conversion defaults.
Browse files Browse the repository at this point in the history
In the general case, returning data as bytes isn't particularly
helpful. While it's true that in some advanced cases, converting data
requires additional information that the library doesn't have access
to, I think it's better for the basic case to work out of the box
while providing enough customization capability for more advanced use
cases to still be practical.

This change is straining against the load-bearing copy-paste technique,
which I don't like, but I'll save an attempt to extract the common
functionality until later.
  • Loading branch information
torque committed Jun 28, 2021
1 parent a4cacb3 commit c430a2a
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 20 deletions.
4 changes: 2 additions & 2 deletions deqr/datatypes.py
Expand Up @@ -67,9 +67,9 @@ def __init__(self, type, data):
self.data = data

def __str__(self):
return "QrCodeData(type=%s, data=<%s>)" % (
return "QrCodeData(type=%s, data=%s)" % (
self.type,
self.data.hex(" ").upper(),
self.data,
)

def __repr__(self):
Expand Down
65 changes: 55 additions & 10 deletions deqr/qrdec.pyx
Expand Up @@ -13,6 +13,7 @@
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

from __future__ import annotations
from typing import Optional

cimport cython

Expand Down Expand Up @@ -59,7 +60,12 @@ cdef class QRdecDecoder:
qrdecdecl.qr_reader_free(self._chndl)

def decode(
self, image_data, binarize: bool = True, binarize_invert: bool = True
self,
image_data,
binarize: bool = True,
binarize_invert: bool = True,
convert_data: bool = True,
byte_charset: Optional[str] = "utf-8"
) -> list[datatypes.QRCode]:
"""
Decode all detectable QR codes in an image.
Expand All @@ -81,18 +87,41 @@ cdef class QRdecDecoder:
by :class:`image.ImageLoader`.

:param binarize:
If True, binarize the input image (i.e. convert all pixels to either
fully black or fully white). The decoder is unlikely to work
properly on images that have not been binarized. Defaults to True.
If ``True``, binarize the input image (i.e. convert all
pixels to either fully black or fully white). The decoder is
unlikely to work properly on images that have not been binarized.

:param binarize_invert:
If True, binarization inverts the reflectance (i.e dark pixels
If ``True``, binarization inverts the reflectance (i.e dark pixels
become white and light pixels become black).

:return: A list of decoded qr codes.

:raises TypeError: if `image_data` is of an unsupported type.
:raises ValueError: if `image_data` is malformed somehow.
:param convert_data:
If ``True``, all data entry payloads are decoded into
"native" python types:

- :py:obj:`datatypes.QRDataType.NUMERIC` => :py:class:`int`,
- :py:obj:`datatypes.QRDataType.ALPHANUMERIC` =>
:py:class:`str` as ``ascii``
- :py:obj:`datatypes.QRDataType.KANJI` =>
:py:class:`str` as ``shift-jis``
- :py:obj:`datatypes.QRDataType.BYTE` =>
:py:class:`str` using the ``byte_charset`` parameter

If ``False``, no conversion will be done and all data entry data
will be returned as :py:class:`bytes`.

:param byte_charset:
The charset to use for converting all decoded data entries of type
:py:obj:`datatypes.QRDataType.BYTE` found in the image. If
``None``, then data entries of type
:py:obj:`datatypes.QRDataType.BYTE` will not be converted,
even if ``convert_data`` is ``True``.

:return: A list of objects containing information from the decoded QR
codes.

:raises TypeError: if ``image_data`` is of an unsupported type.
:raises ValueError: if ``image_data`` is malformed somehow.
"""
cdef qrdecdecl.qr_code_data_list qrlist

Expand All @@ -114,6 +143,19 @@ cdef class QRdecDecoder:
binarize_invert,
)

if convert_data:
if byte_charset is not None:
byte_converter = lambda d: d.decode(byte_charset)
else:
byte_converter = lambda d: d

converters = {
datatypes.QRDataType.NUMERIC: int,
datatypes.QRDataType.ALPHANUMERIC: lambda d: d.decode("ascii"),
datatypes.QRDataType.KANJI: lambda d: d.decode("shift-jis"),
datatypes.QRDataType.BYTE: byte_converter,
}

try:
for idx in range(
qrdecdecl.qr_reader_locate(
Expand All @@ -133,12 +175,15 @@ cdef class QRdecDecoder:
data_entries = tuple(
datatypes.QRCodeData(
entry.mode,
entry.payload.data.buf[:entry.payload.data.len]
entry.payload.data.buf[:entry.payload.data.len],
)
for entry in code.entries[:code.nentries]
if qrdecdecl.QR_MODE_HAS_DATA(entry.mode)
)

if convert_data:
for entry in data_entries:
entry.data = converters[entry.type](entry.data)

decoded.append(
datatypes.QRCode(
Expand Down
61 changes: 53 additions & 8 deletions deqr/quirc.pyx
Expand Up @@ -13,6 +13,7 @@
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

from __future__ import annotations
from typing import Optional

cimport cython

Expand Down Expand Up @@ -45,7 +46,12 @@ cdef class QuircDecoder:


def decode(
self, image_data, binarize: bool = True, binarize_invert: bool = False
self,
image_data,
binarize: bool = True,
binarize_invert: bool = False,
convert_data: bool = True,
byte_charset: Optional[str] = "utf-8"
):
"""
Decode all detectable QR codes in an image.
Expand All @@ -67,19 +73,41 @@ cdef class QuircDecoder:
by :class:`image.ImageLoader`.
:param binarize:
If True, binarize the input image (i.e. convert all pixels to either
fully black or fully white). The decoder is unlikely to work
properly on images that have not been binarized. Defaults to True.
If ``True``, binarize the input image (i.e. convert all
pixels to either fully black or fully white). The decoder is
unlikely to work properly on images that have not been binarized.
:param binarize_invert:
If True, binarization inverts the reflectance (i.e dark pixels
If ``True``, binarization inverts the reflectance (i.e dark pixels
become white and light pixels become black).
:return: A list of decoded qr codes.
:param convert_data:
If ``True``, all data entry payloads are decoded into
"native" python types:
- :py:obj:`deqr.datatypes.QRDataType.NUMERIC` => :py:class:`int`,
- :py:obj:`deqr.datatypes.QRDataType.ALPHANUMERIC` =>
:py:class:`str` as ``ascii``
- :py:obj:`deqr.datatypes.QRDataType.KANJI` =>
:py:class:`str` as ``shift-jis``
- :py:obj:`deqr.datatypes.QRDataType.BYTE` =>
:py:class:`str` using the ``byte_charset`` parameter
If ``False``, no conversion will be done and all data entry data
will be returned as :py:class:`bytes`.
:param byte_charset:
The charset to use for converting all decoded data entries of type
:py:obj:`deqr.datatypes.QRDataType.BYTE` found in the image. If ``None``,
then data entries of type :py:obj:`deqr.datatypes.QRDataType.BYTE` will not be
converted, even if ``convert_data`` is ``True``.
:return: A list of objects containing information from the decoded QR
codes.
:raises MemoryError: if the decoder image buffer allocation fails.
:raises TypeError: if `image_data` is of an unsupported type.
:raises ValueError: if `image_data` is malformed somehow.
:raises TypeError: if ``image_data`` is of an unsupported type.
:raises ValueError: if ``image_data`` is malformed somehow.
"""

cdef int idx = 0
Expand All @@ -99,6 +127,19 @@ cdef class QuircDecoder:
binarize_invert
)

if convert_data:
if byte_charset is not None:
byte_converter = lambda d: d.decode(byte_charset)
else:
byte_converter = lambda d: d

converters = {
datatypes.QRDataType.NUMERIC: int,
datatypes.QRDataType.ALPHANUMERIC: lambda d: d.decode("ascii"),
datatypes.QRDataType.KANJI: lambda d: d.decode("shift-jis"),
datatypes.QRDataType.BYTE: byte_converter,
}

for idx in range(
self._set_image(
image_data.data, image_data.width, image_data.height
Expand All @@ -112,6 +153,10 @@ cdef class QuircDecoder:
datatypes.QRCodeData(data.data_type, data.payload[:data.payload_len]),
)

if convert_data:
for entry in data_entries:
entry.data = converters[entry.type](entry.data)

center = self.compute_center_from_bounds(code.corners)

decoded.append(
Expand Down

0 comments on commit c430a2a

Please sign in to comment.