QL-800 series: printing black/red/white labels (CLI: --red)
This commit is contained in:
@@ -15,7 +15,7 @@ The following printers are claimed to be supported (✓ means verified by the au
|
||||
|
||||
* QL-500 (✓), QL-550 (✓), QL-560, QL-570 (✓), QL-580N, QL-650TD, QL-700 (✓), QL-710W (✓), QL-720NW (✓), QL-800, QL-810W, QL-820NWB (✓), QL-1050, and QL-1060N.
|
||||
|
||||
The new QL-800 series can print labels with two colors (black and red) on DK-22251 labels. This is not yet supported by this package.
|
||||
The new QL-800 series can print labels with two colors (black and red) on DK-22251 labels.
|
||||
|
||||
## Why
|
||||
|
||||
@@ -63,7 +63,7 @@ giving:
|
||||
|
||||
usage: brother_ql_create [-h] [--model MODEL] [--label-size LABEL_SIZE]
|
||||
[--rotate {0,90,180,270}] [--threshold THRESHOLD]
|
||||
[--dither] [--compress] [--no-cut]
|
||||
[--dither] [--compress] [--red] [--no-cut]
|
||||
[--loglevel LOGLEVEL]
|
||||
image [outfile]
|
||||
|
||||
@@ -90,6 +90,8 @@ giving:
|
||||
set, --threshold is meaningless.
|
||||
--compress, -c Enable compression (if available with the model).
|
||||
Takes more time but results in smaller file size.
|
||||
--red Create a label to be printed in black/red/white (only
|
||||
with QL-800, QL-810W, QL-820NWB on DK-22251 labels).
|
||||
--no-cut Don't cut the tape after printing the label.
|
||||
--loglevel LOGLEVEL Set to DEBUG for verbose debugging output to stderr.
|
||||
|
||||
|
||||
@@ -5,11 +5,12 @@ from __future__ import division
|
||||
import sys, argparse, logging
|
||||
|
||||
from PIL import Image
|
||||
import PIL.ImageOps
|
||||
import PIL.ImageOps, PIL.ImageChops
|
||||
|
||||
from brother_ql.raster import BrotherQLRaster
|
||||
from brother_ql.devicedependent import models, label_type_specs, ENDLESS_LABEL, DIE_CUT_LABEL, ROUND_DIE_CUT_LABEL
|
||||
from brother_ql import BrotherQLError, BrotherQLUnsupportedCmd, BrotherQLUnknownModel
|
||||
from brother_ql.image_trafos import filtered_hls
|
||||
|
||||
try:
|
||||
stdout = sys.stdout.buffer
|
||||
@@ -33,6 +34,7 @@ def main():
|
||||
parser.add_argument('--threshold', '-t', type=float, default=70.0, help='The threshold value (in percent) to discriminate between black and white pixels.')
|
||||
parser.add_argument('--dither', '-d', action='store_true', help='Enable dithering when converting the image to b/w. If set, --threshold is meaningless.')
|
||||
parser.add_argument('--compress', '-c', action='store_true', help='Enable compression (if available with the model). Takes more time but results in smaller file size.')
|
||||
parser.add_argument('--red', action='store_true', help='Create a label to be printed in black/red/white (only with QL-800, QL-810W, QL-820NWB on DK-22251 labels).')
|
||||
parser.add_argument('--no-cut', dest='cut', action='store_false', help="Don't cut the tape after printing the label.")
|
||||
parser.add_argument('--loglevel', type=lambda x: getattr(logging, x), default=logging.WARNING, help='Set to DEBUG for verbose debugging output to stderr.')
|
||||
args = parser.parse_args()
|
||||
@@ -54,11 +56,11 @@ def main():
|
||||
|
||||
qlr.exception_on_warning = True
|
||||
|
||||
create_label(qlr, args.image, args.label_size, threshold=args.threshold, cut=args.cut, rotate=args.rotate, dither=args.dither, compress=args.compress)
|
||||
create_label(qlr, args.image, args.label_size, threshold=args.threshold, cut=args.cut, rotate=args.rotate, dither=args.dither, compress=args.compress, red=args.red)
|
||||
|
||||
args.outfile.write(qlr.data)
|
||||
|
||||
def create_label(qlr, image, label_size, threshold=70, cut=True, dither=False, compress=False, **kwargs):
|
||||
def create_label(qlr, image, label_size, threshold=70, cut=True, dither=False, compress=False, red=False, **kwargs):
|
||||
|
||||
label_specs = label_type_specs[label_size]
|
||||
dots_printable = label_specs['dots_printable']
|
||||
@@ -101,15 +103,33 @@ def create_label(qlr, image, label_size, threshold=70, cut=True, dither=False, c
|
||||
new_im.paste(im, (device_pixel_width-im.size[0]-right_margin_dots, 0))
|
||||
im = new_im
|
||||
|
||||
im = im.convert("L")
|
||||
im = PIL.ImageOps.invert(im)
|
||||
if red:
|
||||
filter_h = lambda h: 255 if h < 60 or h > 240 else 0
|
||||
filter_l = lambda l: 255 if l < 220 else 0
|
||||
filter_s = lambda s: 255 if s > 100 else 0
|
||||
red_im = filtered_hls(im, filter_h, filter_l, filter_s)
|
||||
red_im = red_im.convert("L")
|
||||
red_im = PIL.ImageOps.invert(red_im)
|
||||
red_im = red_im.convert("1", dither=Image.NONE)
|
||||
|
||||
if dither:
|
||||
im = im.convert("1", dither=Image.FLOYDSTEINBERG)
|
||||
filter_h = lambda h: 255
|
||||
filter_l = lambda l: 255 if l < 120 else 0
|
||||
filter_s = lambda s: 255
|
||||
black_im = filtered_hls(im, filter_h, filter_l, filter_s)
|
||||
black_im = black_im.convert("L")
|
||||
black_im = PIL.ImageOps.invert(black_im)
|
||||
black_im = black_im.convert("1", dither=Image.NONE)
|
||||
black_im = PIL.ImageChops.subtract(black_im, red_im)
|
||||
else:
|
||||
threshold = 100.0 - threshold
|
||||
threshold = min(255, max(0, int(threshold/100.0 * 255))) # from percent to pixel val
|
||||
im = im.point(lambda x: 0 if x < threshold else 255, mode="1")
|
||||
im = im.convert("L")
|
||||
im = PIL.ImageOps.invert(im)
|
||||
|
||||
if dither:
|
||||
im = im.convert("1", dither=Image.FLOYDSTEINBERG)
|
||||
else:
|
||||
threshold = 100.0 - threshold
|
||||
threshold = min(255, max(0, int(threshold/100.0 * 255))) # from percent to pixel val
|
||||
im = im.point(lambda x: 0 if x < threshold else 255, mode="1")
|
||||
|
||||
try:
|
||||
qlr.add_switch_mode()
|
||||
@@ -142,6 +162,7 @@ def create_label(qlr, image, label_size, threshold=70, cut=True, dither=False, c
|
||||
try:
|
||||
qlr.dpi_600 = False
|
||||
qlr.cut_at_end = cut
|
||||
qlr.two_color_printing = True if red else False
|
||||
qlr.add_expanded_mode()
|
||||
except BrotherQLUnsupportedCmd:
|
||||
pass
|
||||
@@ -150,7 +171,10 @@ def create_label(qlr, image, label_size, threshold=70, cut=True, dither=False, c
|
||||
if compress: qlr.add_compression(True)
|
||||
except BrotherQLUnsupportedCmd:
|
||||
pass
|
||||
qlr.add_raster_data(im)
|
||||
if red:
|
||||
qlr.add_raster_data(black_im, red_im)
|
||||
else:
|
||||
qlr.add_raster_data(im)
|
||||
qlr.add_print()
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
from PIL import Image
|
||||
import colorsys
|
||||
|
||||
def HLSColor(img):
|
||||
""" https://stackoverflow.com/a/22237709/183995 """
|
||||
if isinstance(img,Image.Image):
|
||||
img = img.convert('RGB')
|
||||
r,g,b = img.split()
|
||||
Hdat = []
|
||||
Ldat = []
|
||||
Sdat = []
|
||||
for rd,gn,bl in zip(r.getdata(),g.getdata(),b.getdata()) :
|
||||
h,l,s = colorsys.rgb_to_hls(rd/255.,gn/255.,bl/255.)
|
||||
Hdat.append(int(h*255.))
|
||||
Ldat.append(int(l*255.))
|
||||
Sdat.append(int(s*255.))
|
||||
r.putdata(Hdat)
|
||||
g.putdata(Ldat)
|
||||
b.putdata(Sdat)
|
||||
return Image.merge('RGB',(r,g,b))
|
||||
else:
|
||||
return None
|
||||
|
||||
def filtered_hls(im, filter_h, filter_l, filter_s, default_col=(255,255,255)):
|
||||
hls_im = HLSColor(im)
|
||||
|
||||
H, L, S = 0, 1, 2
|
||||
hls = hls_im.split()
|
||||
mask_h = hls[H].point(filter_h)
|
||||
mask_l = hls[L].point(filter_l)
|
||||
mask_s = hls[S].point(filter_s)
|
||||
|
||||
Mdat = []
|
||||
# for debugging:
|
||||
#mask_h, mask_l, mask_s = hls_im.split()
|
||||
seen = []
|
||||
for h, l, s in zip(mask_h.getdata(), mask_l.getdata(), mask_s.getdata()):
|
||||
if (h, l, s) not in seen:
|
||||
seen.append((h, l, s))
|
||||
#print((h, l, s))
|
||||
Mdat.append(255 if (h and l and s) else 0)
|
||||
|
||||
mask = mask_h
|
||||
mask.putdata(Mdat)
|
||||
|
||||
filtered_im = Image.new("RGB", hls_im.size, color=default_col)
|
||||
filtered_im.paste(im, None, mask)
|
||||
return filtered_im
|
||||
|
||||
+26
-12
@@ -38,6 +38,7 @@ class BrotherQLRaster(object):
|
||||
self.page_number = 0
|
||||
self.cut_at_end = True
|
||||
self.dpi_600 = False
|
||||
self.two_color_printing = False
|
||||
self._compression = False
|
||||
self.exception_on_warning = False
|
||||
|
||||
@@ -153,6 +154,7 @@ class BrotherQLRaster(object):
|
||||
flags = 0x00
|
||||
flags |= self.cut_at_end << 3
|
||||
flags |= self.dpi_600 << 6
|
||||
flags |= self.two_color_printing << 0
|
||||
self.data += bytes([flags])
|
||||
|
||||
def add_margins(self, dots=0x23):
|
||||
@@ -174,27 +176,39 @@ class BrotherQLRaster(object):
|
||||
nbpr = number_bytes_per_row['default']
|
||||
return nbpr*8
|
||||
|
||||
def add_raster_data(self, image):
|
||||
def add_raster_data(self, image, second_image=None):
|
||||
""" image: Pillow Image() """
|
||||
logger.info("raster_image_size: {0}x{1}".format(*image.size))
|
||||
image = image.transpose(Image.FLIP_LEFT_RIGHT)
|
||||
image = image.convert("1")
|
||||
if image.size[0] != self.get_pixel_width():
|
||||
fmt = 'Wrong pixel width: {}, expected {}'
|
||||
raise BrotherQLRasterError(fmt.format(image.size[0], self.get_pixel_width()))
|
||||
frame = bytes(image.tobytes(encoder_name='raw'))
|
||||
frame_len = len(frame)
|
||||
row_len = image.size[0]//8
|
||||
images = [image]
|
||||
if second_image:
|
||||
if image.size != second_image.size:
|
||||
fmt = "First and second image don't have the same dimesions: {} vs {}."
|
||||
raise BrotherQLRasterError(fmt.format(image.size, second_image.size))
|
||||
images.append(second_image)
|
||||
frames = []
|
||||
for image in images:
|
||||
image = image.transpose(Image.FLIP_LEFT_RIGHT)
|
||||
image = image.convert("1")
|
||||
frames.append(bytes(image.tobytes(encoder_name='raw')))
|
||||
frame_len = len(frames[0])
|
||||
row_len = images[0].size[0]//8
|
||||
start = 0
|
||||
file_str = BytesIO()
|
||||
while start + row_len <= frame_len:
|
||||
row = frame[start:start+row_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)]))
|
||||
file_str.write(row)
|
||||
start += row_len
|
||||
file_str.write(b'\x67\x00')
|
||||
if self._compression:
|
||||
row = packbits.encode(row)
|
||||
file_str.write(bytes([len(row)]))
|
||||
file_str.write(row)
|
||||
self.data += file_str.getvalue()
|
||||
|
||||
def add_print(self, last_page=True):
|
||||
|
||||
Reference in New Issue
Block a user