Compare commits
12 Commits
0a3af68eaa
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
61e16625fe
|
|||
|
7aa3bbb5be
|
|||
| 56cf4394ad | |||
| 638b365d45 | |||
| 05516a7692 | |||
| b551b1fc94 | |||
| 5c2b72b18b | |||
| 2eeac7a4b6 | |||
| 99c35993c8 | |||
| ca4ac0544c | |||
| 1cfc7e7302 | |||
| df31020d4d |
@@ -48,7 +48,7 @@ of its dependencies.
|
||||
|
||||
Alternatively, you can install the latest version from Github using:
|
||||
|
||||
pip install --upgrade https://github.com/pklaus/brother_ql/archive/master.zip
|
||||
pip install --upgrade https://git.labolyon.fr/epickiwi/brother_ql/archive/main.zip
|
||||
|
||||
This package was mainly created for use with Python 3.
|
||||
The essential functionality, however, will also work with Python 2: the creation of label files.
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
|
||||
from builtins import bytes
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -9,7 +9,7 @@ Install via `pip install pyusb`
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from builtins import str
|
||||
from builtins import str, bytes
|
||||
|
||||
import time
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ from PIL import Image
|
||||
import PIL.ImageOps, PIL.ImageChops
|
||||
|
||||
from brother_ql.raster import BrotherQLRaster
|
||||
from brother_ql.devicedependent import label_type_specs, ENDLESS_LABEL, DIE_CUT_LABEL, ROUND_DIE_CUT_LABEL, right_margin_addition
|
||||
from brother_ql.devicedependent import ENDLESS_LABEL, DIE_CUT_LABEL, ROUND_DIE_CUT_LABEL, PTOUCH_ENDLESS_LABEL
|
||||
from brother_ql.devicedependent import label_type_specs, right_margin_addition
|
||||
from brother_ql import BrotherQLUnsupportedCmd
|
||||
from brother_ql.image_trafos import filtered_hsv
|
||||
|
||||
@@ -102,14 +103,14 @@ def convert(qlr, images, label, **kwargs):
|
||||
else:
|
||||
dots_expected = dots_printable
|
||||
|
||||
if label_specs['kind'] == ENDLESS_LABEL:
|
||||
if label_specs['kind'] in (ENDLESS_LABEL, PTOUCH_ENDLESS_LABEL):
|
||||
if rotate not in ('auto', 0):
|
||||
im = im.rotate(rotate, expand=True)
|
||||
if dpi_600:
|
||||
im = im.resize((im.size[0]//2, im.size[1]))
|
||||
if im.size[0] != dots_printable[0]:
|
||||
hsize = int((dots_printable[0] / im.size[0]) * im.size[1])
|
||||
im = im.resize((dots_printable[0], hsize), Image.ANTIALIAS)
|
||||
im = im.resize((dots_printable[0], hsize), Image.Resampling.LANCZOS)
|
||||
logger.warning('Need to resize the image...')
|
||||
if im.size[0] < device_pixel_width:
|
||||
new_im = Image.new(im.mode, (device_pixel_width, im.size[1]), (255,)*len(im.mode))
|
||||
@@ -161,10 +162,14 @@ def convert(qlr, images, label, **kwargs):
|
||||
qlr.mtype = 0x0B
|
||||
qlr.mwidth = tape_size[0]
|
||||
qlr.mlength = tape_size[1]
|
||||
else:
|
||||
elif label_specs['kind'] in (ENDLESS_LABEL, ):
|
||||
qlr.mtype = 0x0A
|
||||
qlr.mwidth = tape_size[0]
|
||||
qlr.mlength = 0
|
||||
elif label_specs['kind'] in (PTOUCH_ENDLESS_LABEL, ):
|
||||
qlr.mtype = 0x00
|
||||
qlr.mwidth = tape_size[0]
|
||||
qlr.mlength = 0
|
||||
qlr.pquality = int(hq)
|
||||
qlr.add_media_and_quality(im.size[1])
|
||||
try:
|
||||
|
||||
@@ -16,13 +16,12 @@ import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
logger.warn("deprecation warning: brother_ql.devicedependent is deprecated and will be removed in a future release")
|
||||
|
||||
## These module level variables were available here before.
|
||||
# Concerning labels
|
||||
DIE_CUT_LABEL = None
|
||||
ENDLESS_LABEL = None
|
||||
ROUND_DIE_CUT_LABEL = None
|
||||
PTOUCH_ENDLESS_LABEL = None
|
||||
label_type_specs = {}
|
||||
label_sizes = []
|
||||
# And concerning printer models
|
||||
@@ -63,13 +62,14 @@ def _populate_label_legacy_structures():
|
||||
We contain this code inside a function so that the imports
|
||||
we do in here are not visible at the module level.
|
||||
"""
|
||||
global DIE_CUT_LABEL, ENDLESS_LABEL, ROUND_DIE_CUT_LABEL
|
||||
global DIE_CUT_LABEL, ENDLESS_LABEL, ROUND_DIE_CUT_LABEL, PTOUCH_ENDLESS_LABEL
|
||||
global label_sizes, label_type_specs
|
||||
|
||||
from brother_ql.labels import FormFactor
|
||||
DIE_CUT_LABEL = FormFactor.DIE_CUT
|
||||
ENDLESS_LABEL = FormFactor.ENDLESS
|
||||
ROUND_DIE_CUT_LABEL = FormFactor.ROUND_DIE_CUT
|
||||
PTOUCH_ENDLESS_LABEL =FormFactor.PTOUCH_ENDLESS
|
||||
|
||||
from brother_ql.labels import LabelsManager
|
||||
lm = LabelsManager()
|
||||
|
||||
+20
-17
@@ -1,13 +1,13 @@
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from attr import attrs, attrib
|
||||
from typing import List, Tuple
|
||||
from enum import Enum
|
||||
from enum import IntEnum
|
||||
|
||||
import copy
|
||||
|
||||
from brother_ql.helpers import ElementsManager
|
||||
|
||||
class FormFactor(Enum):
|
||||
class FormFactor(IntEnum):
|
||||
"""
|
||||
Enumeration representing the form factor of a label.
|
||||
The labels for the Brother QL series are supplied either as die-cut (pre-sized), or for more flexibility the
|
||||
@@ -19,8 +19,10 @@ class FormFactor(Enum):
|
||||
ENDLESS = 2
|
||||
#: round die-cut labels
|
||||
ROUND_DIE_CUT = 3
|
||||
#: endless P-touch labels
|
||||
PTOUCH_ENDLESS = 4
|
||||
|
||||
class Color(Enum):
|
||||
class Color(IntEnum):
|
||||
"""
|
||||
Enumeration representing the colors to be printed on a label. Most labels only support printing black on white.
|
||||
Some newer ones can also print in black and red on white.
|
||||
@@ -30,35 +32,35 @@ class Color(Enum):
|
||||
#: The label can be printed in black, white & red.
|
||||
BLACK_RED_WHITE = 1
|
||||
|
||||
@dataclass
|
||||
class Label:
|
||||
@attrs
|
||||
class Label(object):
|
||||
"""
|
||||
This class represents a label. All specifics of a certain label
|
||||
and what the rasterizer needs to take care of depending on the
|
||||
label choosen, should be contained in this class.
|
||||
"""
|
||||
#: A string identifier given to each label that can be selected. Eg. '29'.
|
||||
identifier: str
|
||||
identifier = attrib(type=str)
|
||||
#: The tape size of a single label (width, lenght) in mm. For endless labels, the length is 0 by definition.
|
||||
tape_size: Tuple[int, int]
|
||||
tape_size = attrib(type=Tuple[int, int])
|
||||
#: The type of label
|
||||
form_factor: FormFactor
|
||||
form_factor = attrib(type=FormFactor)
|
||||
#: The total area (width, length) of the label in dots (@300dpi).
|
||||
dots_total: Tuple[int, int]
|
||||
dots_total = attrib(type=Tuple[int, int])
|
||||
#: The printable area (width, length) of the label in dots (@300dpi).
|
||||
dots_printable: Tuple[int, int]
|
||||
dots_printable = attrib(type=Tuple[int, int])
|
||||
#: The required offset from the right side of the label in dots to obtain a centered printout.
|
||||
offset_r: int
|
||||
offset_r = attrib(type=int)
|
||||
#: An additional amount of feeding when printing the label.
|
||||
#: This is non-zero for some smaller label sizes and for endless labels.
|
||||
feed_margin: int = 0
|
||||
feed_margin = attrib(type=int, default=0)
|
||||
#: If a label can only be printed with certain label printers, this member variable lists the allowed ones.
|
||||
#: Otherwise it's an empty list.
|
||||
restricted_to_models: List[str] = field(default_factory=list)
|
||||
restricted_to_models = attrib(type=List[str], factory=list)
|
||||
#: Some labels allow printing in red, most don't.
|
||||
color: Color = Color.BLACK_WHITE
|
||||
color = attrib(type=Color, default=Color.BLACK_WHITE)
|
||||
|
||||
def works_with_model(self, model) -> bool:
|
||||
def works_with_model(self, model): # type: bool
|
||||
"""
|
||||
Method to determine if certain label can be printed by the specified printer model.
|
||||
"""
|
||||
@@ -66,7 +68,7 @@ class Label:
|
||||
else: return True
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
def name(self): # type: str
|
||||
out = ""
|
||||
if 'x' in self.identifier:
|
||||
out = '{0}mm x {1}mm die-cut'.format(*self.tape_size)
|
||||
@@ -102,6 +104,7 @@ ALL_LABELS = (
|
||||
Label("d12", ( 12, 12), FormFactor.ROUND_DIE_CUT, ( 142, 142), ( 94, 94), 113 , feed_margin=35),
|
||||
Label("d24", ( 24, 24), FormFactor.ROUND_DIE_CUT, ( 284, 284), ( 236, 236), 42 ),
|
||||
Label("d58", ( 58, 58), FormFactor.ROUND_DIE_CUT, ( 688, 688), ( 618, 618), 51 ),
|
||||
Label("pt24", ( 24, 0), FormFactor.PTOUCH_ENDLESS,( 128, 0), ( 128, 0), 0, feed_margin=14),
|
||||
)
|
||||
|
||||
class LabelsManager(ElementsManager):
|
||||
|
||||
+16
-14
@@ -1,43 +1,43 @@
|
||||
from dataclasses import dataclass
|
||||
from attr import attrs, attrib
|
||||
from typing import Tuple
|
||||
|
||||
import copy
|
||||
|
||||
from brother_ql.helpers import ElementsManager
|
||||
|
||||
@dataclass
|
||||
class Model:
|
||||
@attrs
|
||||
class Model(object):
|
||||
"""
|
||||
This class represents a printer model. All specifics of a certain model
|
||||
and the opcodes it supports should be contained in this class.
|
||||
"""
|
||||
#: A string identifier given to each model implemented. Eg. 'QL-500'.
|
||||
identifier: str
|
||||
identifier = attrib(type=str)
|
||||
#: Minimum and maximum number of rows or 'dots' that can be printed.
|
||||
#: Together with the dpi this gives the minimum and maximum length
|
||||
#: for continuous tape printing.
|
||||
min_max_length_dots: Tuple[int, int]
|
||||
min_max_length_dots = attrib(type=Tuple[int, int])
|
||||
#: The minimum and maximum amount of feeding a label
|
||||
min_max_feed: Tuple[int, int] = (35, 1500)
|
||||
number_bytes_per_row: int = 90
|
||||
min_max_feed = attrib(type=Tuple[int, int], default=(35, 1500))
|
||||
number_bytes_per_row = attrib(type=int, default=90)
|
||||
#: The required additional offset from the right side
|
||||
additional_offset_r: int = 0
|
||||
additional_offset_r = attrib(type=int, default=0)
|
||||
#: Support for the 'mode setting' opcode
|
||||
mode_setting: bool = True
|
||||
mode_setting = attrib(type=bool, default=True)
|
||||
#: Model has a cutting blade to automatically cut labels
|
||||
cutting: bool = True
|
||||
cutting = attrib(type=bool, default=True)
|
||||
#: Model has support for the 'expanded mode' opcode.
|
||||
#: (So far, all models that have cutting support do).
|
||||
expanded_mode: bool = True
|
||||
expanded_mode = attrib(type=bool, default=True)
|
||||
#: Model has support for compressing the transmitted raster data.
|
||||
#: Some models with only USB connectivity don't support compression.
|
||||
compression: bool = True
|
||||
compression = attrib(type=bool, default=True)
|
||||
#: Support for two color printing (black/red/white)
|
||||
#: available only on some newer models.
|
||||
two_color: bool = False
|
||||
two_color = attrib(type=bool, default=False)
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
def name(self):
|
||||
return self.identifier
|
||||
|
||||
ALL_MODELS = [
|
||||
@@ -55,6 +55,8 @@ ALL_MODELS = [
|
||||
Model('QL-820NWB',(150, 11811), two_color=True),
|
||||
Model('QL-1050', (295, 35433), number_bytes_per_row=162, additional_offset_r=44),
|
||||
Model('QL-1060N', (295, 35433), number_bytes_per_row=162, additional_offset_r=44),
|
||||
Model('PT-P750W', (31, 14172), number_bytes_per_row=16),
|
||||
Model('PT-P900W', (57, 28346), number_bytes_per_row=70),
|
||||
]
|
||||
|
||||
class ModelsManager(ElementsManager):
|
||||
|
||||
+56
-14
@@ -1,3 +1,11 @@
|
||||
"""
|
||||
This module contains the implementation of the raster language
|
||||
of the Brother QL-series label printers according to their
|
||||
documentation and to reverse engineering efforts.
|
||||
|
||||
The central piece of code in this module is the class
|
||||
:py:class:`BrotherQLRaster`.
|
||||
"""
|
||||
|
||||
from builtins import bytes
|
||||
|
||||
@@ -12,7 +20,6 @@ from .devicedependent import models, \
|
||||
min_max_feed, \
|
||||
min_max_length_dots, \
|
||||
number_bytes_per_row, \
|
||||
right_margin_addition, \
|
||||
compressionsupport, \
|
||||
cuttingsupport, \
|
||||
expandedmode, \
|
||||
@@ -30,6 +37,21 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class BrotherQLRaster(object):
|
||||
|
||||
"""
|
||||
This class facilitates the creation of a complete set
|
||||
of raster instructions by adding them one after the other
|
||||
using the methods of the class. Each method call is adding
|
||||
instructions to the member variable :py:attr:`data`.
|
||||
|
||||
Instatiate the class by providing the printer
|
||||
model as argument.
|
||||
|
||||
:param str model: Choose from the list of available models.
|
||||
|
||||
:ivar bytes data: The resulting bytecode with all instructions.
|
||||
:ivar bool exception_on_warning: If set to True, an exception is raised if trying to add instruction which are not supported on the selected model. If set to False, the instruction is simply ignored and a warning sent to logging/stderr.
|
||||
"""
|
||||
|
||||
def __init__(self, model='QL-500'):
|
||||
if model not in models:
|
||||
raise BrotherQLUnknownModel()
|
||||
@@ -57,7 +79,7 @@ class BrotherQLRaster(object):
|
||||
else:
|
||||
logger.warning(problem)
|
||||
|
||||
def unsupported(self, problem):
|
||||
def _unsupported(self, problem):
|
||||
"""
|
||||
Raises BrotherQLUnsupportedCmd if
|
||||
exception_on_warning is set to True.
|
||||
@@ -86,7 +108,7 @@ class BrotherQLRaster(object):
|
||||
the mode change (others are in raster mode already).
|
||||
"""
|
||||
if self.model not in modesetting:
|
||||
self.unsupported("Trying to switch the operating mode on a printer that doesn't support the command.")
|
||||
self._unsupported("Trying to switch the operating mode on a printer that doesn't support the command.")
|
||||
return
|
||||
self.data += b'\x1B\x69\x61\x01' # ESC i a
|
||||
|
||||
@@ -139,24 +161,24 @@ class BrotherQLRaster(object):
|
||||
|
||||
def add_autocut(self, autocut = False):
|
||||
if self.model not in cuttingsupport:
|
||||
self.unsupported("Trying to call add_autocut with a printer that doesn't support it")
|
||||
self._unsupported("Trying to call add_autocut with a printer that doesn't support it")
|
||||
return
|
||||
self.data += b'\x1B\x69\x4D' # ESC i M
|
||||
self.data += bytes([autocut << 6])
|
||||
|
||||
def add_cut_every(self, n=1):
|
||||
if self.model not in cuttingsupport:
|
||||
self.unsupported("Trying to call add_cut_every with a printer that doesn't support it")
|
||||
self._unsupported("Trying to call add_cut_every with a printer that doesn't support it")
|
||||
return
|
||||
self.data += b'\x1B\x69\x41' # ESC i A
|
||||
self.data += bytes([n & 0xFF])
|
||||
|
||||
def add_expanded_mode(self):
|
||||
if self.model not in expandedmode:
|
||||
self.unsupported("Trying to set expanded mode (dpi/cutting at end) on a printer that doesn't support it")
|
||||
self._unsupported("Trying to set expanded mode (dpi/cutting at end) on a printer that doesn't support it")
|
||||
return
|
||||
if self.two_color_printing and not self.two_color_support:
|
||||
self.unsupported("Trying to set two_color_printing in expanded mode on a printer that doesn't support it.")
|
||||
self._unsupported("Trying to set two_color_printing in expanded mode on a printer that doesn't support it.")
|
||||
return
|
||||
self.data += b'\x1B\x69\x4B' # ESC i K
|
||||
flags = 0x00
|
||||
@@ -170,8 +192,16 @@ class BrotherQLRaster(object):
|
||||
self.data += struct.pack('<H', dots)
|
||||
|
||||
def add_compression(self, compression=True):
|
||||
"""
|
||||
Add an instruction enabling or disabling compression for the transmitted raster image lines.
|
||||
Not all models support compression. If the specific model doesn't support it but this method
|
||||
is called trying to enable it, either a warning is set or an exception is raised depending on
|
||||
the value of :py:attr:`exception_on_warning`
|
||||
|
||||
:param bool compression: Whether compression should be on or off
|
||||
"""
|
||||
if self.model not in compressionsupport:
|
||||
self.unsupported("Trying to set compression on a printer that doesn't support it")
|
||||
self._unsupported("Trying to set compression on a printer that doesn't support it")
|
||||
return
|
||||
self._compression = compression
|
||||
self.data += b'\x4D' # M
|
||||
@@ -185,7 +215,14 @@ class BrotherQLRaster(object):
|
||||
return nbpr*8
|
||||
|
||||
def add_raster_data(self, image, second_image=None):
|
||||
""" image: Pillow Image() """
|
||||
"""
|
||||
Add the image data to the instructions.
|
||||
The provided image has to be binary (every pixel
|
||||
is either black or white).
|
||||
|
||||
:param PIL.Image.Image image: The image to be converted and added to the raster instructions
|
||||
:param PIL.Image.Image second_image: A second image with a separate color layer (red layer for the QL-800 series)
|
||||
"""
|
||||
logger.debug("raster_image_size: {0}x{1}".format(*image.size))
|
||||
if image.size[0] != self.get_pixel_width():
|
||||
fmt = 'Wrong pixel width: {}, expected {}'
|
||||
@@ -208,13 +245,18 @@ class BrotherQLRaster(object):
|
||||
while start + row_len <= frame_len:
|
||||
for i, frame in enumerate(frames):
|
||||
row = frame[start:start+row_len]
|
||||
if second_image:
|
||||
file_str.write(b'\x77\x01' if i == 0 else b'\x77\x02')
|
||||
else:
|
||||
file_str.write(b'\x67\x00')
|
||||
if self._compression:
|
||||
row = packbits.encode(row)
|
||||
file_str.write(bytes([len(row)]))
|
||||
translen = len(row) # number of bytes to be transmitted
|
||||
if self.model.startswith('PT'):
|
||||
file_str.write(b'\x47')
|
||||
file_str.write(bytes([translen%256, translen//256]))
|
||||
else:
|
||||
if second_image:
|
||||
file_str.write(b'\x77\x01' if i == 0 else b'\x77\x02')
|
||||
else:
|
||||
file_str.write(b'\x67\x00')
|
||||
file_str.write(bytes([translen]))
|
||||
file_str.write(row)
|
||||
start += row_len
|
||||
self.data += file_str.getvalue()
|
||||
|
||||
+27
-9
@@ -16,8 +16,10 @@ OPCODES = {
|
||||
# signature name following bytes description
|
||||
b'\x00': ("preamble", -1, "Preamble, 200-300x 0x00 to clear comamnd buffer"),
|
||||
b'\x4D': ("compression", 1, ""),
|
||||
b'\x67': ("raster", -1, ""),
|
||||
b'\x77': ("2-color raster", -1, ""),
|
||||
b'\x67': ("raster QL", -1, ""),
|
||||
b'\x47': ("raster P-touch", -1, ""),
|
||||
b'\x77': ("2-color raster QL", -1, ""),
|
||||
b'\x5a': ("zero raster", 0, "empty raster line"),
|
||||
b'\x0C': ("print", 0, "print intermediate page"),
|
||||
b'\x1A': ("print", 0, "print final page"),
|
||||
b'\x1b\x40': ("init", 0, "initialization"),
|
||||
@@ -139,12 +141,15 @@ def chunker(data, raise_exception=False):
|
||||
opcode_def = OPCODES[opcode]
|
||||
num_bytes = len(opcode)
|
||||
if opcode_def[1] > 0: num_bytes += opcode_def[1]
|
||||
if 'raster' in opcode_def[0]:
|
||||
elif opcode_def[0] in ('raster QL', '2-color raster QL'):
|
||||
num_bytes += data[2] + 2
|
||||
elif opcode_def[0] in ('raster P-touch',):
|
||||
num_bytes += data[1] + data[2]*256 + 2
|
||||
#payload = data[len(opcode):num_bytes]
|
||||
instructions.append(data[:num_bytes])
|
||||
yield instructions[-1]
|
||||
data = data[num_bytes:]
|
||||
return instructions
|
||||
#return instructions
|
||||
|
||||
def match_opcode(data):
|
||||
matching_opcodes = [opcode for opcode in OPCODES.keys() if data.startswith(opcode)]
|
||||
@@ -262,7 +267,11 @@ class BrotherQLReader(object):
|
||||
logger.info(" {} ({}) --> found! (payload: {})".format(opcode_def[0], hex_format(opcode), hex_format(payload)))
|
||||
if opcode_def[0] == 'compression':
|
||||
self.compression = payload[0] == 0x02
|
||||
if 'raster' in opcode_def[0]:
|
||||
if opcode_def[0] == 'zero raster':
|
||||
self.black_rows.append(bytes())
|
||||
if self.two_color_printing:
|
||||
self.red_rows.append(bytes())
|
||||
if opcode_def[0] in ('raster QL', '2-color raster QL', 'raster P-touch'):
|
||||
rpl = bytes(payload[2:]) # raster payload
|
||||
if self.compression:
|
||||
row = bytes()
|
||||
@@ -282,7 +291,7 @@ class BrotherQLReader(object):
|
||||
if index >= len(rpl): break
|
||||
else:
|
||||
row = rpl
|
||||
if opcode_def[0] == 'raster':
|
||||
if opcode_def[0] in ('raster QL', 'raster P-touch'):
|
||||
self.black_rows.append(row)
|
||||
else: # 2-color
|
||||
if payload[0] == 0x01:
|
||||
@@ -306,15 +315,24 @@ class BrotherQLReader(object):
|
||||
logger.info("Len of red rows: %d", len(self.red_rows))
|
||||
def get_im(rows):
|
||||
if not len(rows): return None
|
||||
size = (len(rows[0])*8, len(rows))
|
||||
data = bytes(b''.join(rows))
|
||||
width_dots = max(len(row) for row in rows)
|
||||
height_dots = len(rows)
|
||||
size = (width_dots*8, height_dots)
|
||||
expanded_rows = []
|
||||
for row in rows:
|
||||
if len(row) == 0:
|
||||
expanded_rows.append(b'\x00'*width_dots)
|
||||
else:
|
||||
expanded_rows.append(row)
|
||||
data = bytes(b''.join(expanded_rows))
|
||||
data = bytes([2**8 + ~byte for byte in data]) # invert b/w
|
||||
im = Image.frombytes("1", size, data, decoder_name='raw')
|
||||
return im
|
||||
im_black, im_red = (get_im(rows) for rows in (self.black_rows, self.red_rows))
|
||||
if not self.two_color_printing:
|
||||
im_black = get_im(self.black_rows)
|
||||
im = im_black
|
||||
else:
|
||||
im_black, im_red = (get_im(rows) for rows in (self.black_rows, self.red_rows))
|
||||
im_black = im_black.convert("RGBA")
|
||||
im_red = im_red.convert("L")
|
||||
im_red = colorize(im_red, (255, 0, 0), (255, 255, 255))
|
||||
|
||||
Reference in New Issue
Block a user