First attempt at centering images and QRs (#250)

This was tested on ZJ-5890 with success. By default centering is
deactivated for backward compatibility. Trying to center a QR code in
native mode will raise an exception as we do not know ATM if the native
rendering is centered by default or not.

* Added basic tests for center feature

* Check image size before centering
This commit is contained in:
Romain Porte 2017-08-31 09:25:35 +02:00 committed by Patrick Kanzler
parent 50c627fbb0
commit b648cfd67f
6 changed files with 74 additions and 10 deletions

View File

@ -16,4 +16,4 @@ if __name__ == '__main__':
# Adapt to your needs # Adapt to your needs
p = Usb(0x0416, 0x5011, profile="POS-5890") p = Usb(0x0416, 0x5011, profile="POS-5890")
p.qr(content) p.qr(content, center=True)

View File

@ -87,7 +87,7 @@ class Escpos(object):
raise NotImplementedError() raise NotImplementedError()
def image(self, img_source, high_density_vertical=True, high_density_horizontal=True, impl="bitImageRaster", def image(self, img_source, high_density_vertical=True, high_density_horizontal=True, impl="bitImageRaster",
fragment_height=960): fragment_height=960, center=False):
""" Print an image """ Print an image
You can select whether the printer should print in high density or not. The default value is high density. You can select whether the printer should print in high density or not. The default value is high density.
@ -108,14 +108,19 @@ class Escpos(object):
:param high_density_horizontal: print in high density in horizontal direction *default:* True :param high_density_horizontal: print in high density in horizontal direction *default:* True
:param impl: choose image printing mode between `bitImageRaster`, `graphics` or `bitImageColumn` :param impl: choose image printing mode between `bitImageRaster`, `graphics` or `bitImageColumn`
:param fragment_height: Images larger than this will be split into multiple fragments *default:* 960 :param fragment_height: Images larger than this will be split into multiple fragments *default:* 960
:param center: Center image horizontally *default:* False
""" """
im = EscposImage(img_source) im = EscposImage(img_source)
try: try:
max_width = int(self.profile.profile_data['media']['width']['pixels']) max_width = int(self.profile.profile_data['media']['width']['pixels'])
if im.width > max_width: if im.width > max_width:
raise ImageWidthError('{} > {}'.format(im.width, max_width)) raise ImageWidthError('{} > {}'.format(im.width, max_width))
if center:
im.center(max_width)
except KeyError: except KeyError:
# If the printer's pixel width is not known, print anyways... # If the printer's pixel width is not known, print anyways...
pass pass
@ -173,7 +178,8 @@ class Escpos(object):
header = self._int_low_high(len(data) + 2, 2) header = self._int_low_high(len(data) + 2, 2)
self._raw(GS + b'(L' + header + m + fn + data) self._raw(GS + b'(L' + header + m + fn + data)
def qr(self, content, ec=QR_ECLEVEL_L, size=3, model=QR_MODEL_2, native=False): def qr(self, content, ec=QR_ECLEVEL_L, size=3, model=QR_MODEL_2,
native=False, center=False):
""" Print QR Code for the provided string """ Print QR Code for the provided string
:param content: The content of the code. Numeric data will be more efficiently compacted. :param content: The content of the code. Numeric data will be more efficiently compacted.
@ -185,6 +191,7 @@ class Escpos(object):
by all printers). by all printers).
:param native: True to render the code on the printer, False to render the code as an image and send it to the :param native: True to render the code on the printer, False to render the code as an image and send it to the
printer (Default) printer (Default)
:param center: Centers the code *default:* False
""" """
# Basic validation # Basic validation
if ec not in [QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q]: if ec not in [QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q]:
@ -211,12 +218,17 @@ class Escpos(object):
qr_code.make(fit=True) qr_code.make(fit=True)
qr_img = qr_code.make_image() qr_img = qr_code.make_image()
im = qr_img._img.convert("RGB") im = qr_img._img.convert("RGB")
# Convert the RGB image in printable image # Convert the RGB image in printable image
self.text('\n') self.text('\n')
self.image(im) self.image(im, center=center)
self.text('\n') self.text('\n')
self.text('\n') self.text('\n')
return return
if center:
raise NotImplementedError("Centering not implemented for native QR rendering")
# Native 2D code printing # Native 2D code printing
cn = b'1' # Code type for QR code cn = b'1' # Code type for QR code
# Select model: 1, 2 or micro. # Select model: 1, 2 or micro.

View File

@ -115,3 +115,19 @@ class EscposImage(object):
box = (left, upper, right, lower) box = (left, upper, right, lower)
fragments.append(self.img_original.crop(box)) fragments.append(self.img_original.crop(box))
return fragments return fragments
def center(self, max_width):
"""In-place image centering
:param: Maximum width in order to deduce x offset for centering
:return: None
"""
old_width, height = self._im.size
new_size = (max_width, height)
new_im = Image.new("1", new_size)
paste_x = int((max_width - old_width) / 2)
new_im.paste(self._im, (paste_x, 0))
self._im = new_im

View File

@ -145,10 +145,8 @@ def test_large_graphics():
assert(instance.output == b'\x1dv0\x00\x01\x00\x01\x00\xc0\x1dv0\x00\x01\x00\x01\x00\x00') assert(instance.output == b'\x1dv0\x00\x01\x00\x01\x00\xc0\x1dv0\x00\x01\x00\x01\x00\x00')
def test_width_too_large(): @pytest.fixture
""" def dummy_with_width():
Test printing an image that is too large in width.
"""
instance = printer.Dummy() instance = printer.Dummy()
instance.profile.profile_data = { instance.profile.profile_data = {
'media': { 'media': {
@ -157,8 +155,25 @@ def test_width_too_large():
} }
} }
} }
return instance
def test_width_too_large(dummy_with_width):
"""
Test printing an image that is too large in width.
"""
instance = dummy_with_width
with pytest.raises(ImageWidthError): with pytest.raises(ImageWidthError):
instance.image(Image.new("RGB", (385, 200))) instance.image(Image.new("RGB", (385, 200)))
instance.image(Image.new("RGB", (384, 200))) instance.image(Image.new("RGB", (384, 200)))
def test_center_image(dummy_with_width):
instance = dummy_with_width
with pytest.raises(ImageWidthError):
instance.image(Image.new("RGB", (385, 200)), center=True)
instance.image(Image.new("RGB", (384, 200)), center=True)

View File

@ -13,6 +13,8 @@ from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
from nose.tools import raises from nose.tools import raises
import pytest
import escpos.printer as printer import escpos.printer as printer
from escpos.constants import QR_ECLEVEL_H, QR_MODEL_1 from escpos.constants import QR_ECLEVEL_H, QR_MODEL_1
@ -25,7 +27,6 @@ def test_defaults():
b'(k\x07\x001P01234\x1d(k\x03\x001Q0' b'(k\x07\x001P01234\x1d(k\x03\x001Q0'
assert(instance.output == expected) assert(instance.output == expected)
def test_empty(): def test_empty():
"""Test QR printing blank code""" """Test QR printing blank code"""
instance = printer.Dummy() instance = printer.Dummy()
@ -99,3 +100,13 @@ def test_image_invalid_model():
"""Test unsupported QR model as image""" """Test unsupported QR model as image"""
instance = printer.Dummy() instance = printer.Dummy()
instance.qr("1234", native=False, model=QR_MODEL_1) instance.qr("1234", native=False, model=QR_MODEL_1)
@pytest.fixture
def instance():
return printer.Dummy()
def test_center_not_implementer(instance):
with pytest.raises(NotImplementedError):
instance.qr("test", center=True, native=True)

View File

@ -13,6 +13,7 @@ from __future__ import division
from __future__ import print_function from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
import pytest
import mock import mock
from escpos.printer import Dummy from escpos.printer import Dummy
@ -30,3 +31,12 @@ def test_type_of_object_passed_to_image_function(img_function):
d.qr("LoremIpsum") d.qr("LoremIpsum")
args, kwargs = img_function.call_args args, kwargs = img_function.call_args
assert isinstance(args[0], Image.Image) assert isinstance(args[0], Image.Image)
@pytest.fixture
def instance():
return Dummy()
def test_center(instance):
instance.qr("LoremIpsum", center=True)