Last active
November 27, 2023 14:07
-
-
Save schorschii/8df2a4cbc8bb8ec44e29d2d1b4b563c5 to your computer and use it in GitHub Desktop.
SATO Label Printer SBPL Graphic Converter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
# SATO Graphic Converter, (c) Georg Sieber 2023, https://github.com/schorschii | |
# open source implementation of the SATO label printer graphic format, | |
# generates SBPL command to print arbitrary graphic given as parameter | |
# supports hex and binary (more performant) format | |
# usage: php sato-graphics-converter.php 192.168.1.50 image.jpg 250 100 | |
# important: the printer won't print if the image location is not on your label size, so please choose appropriate X/Y coordinates | |
function isBlack($pixel) { | |
$r = ($pixel >> 16) & 0xFF; | |
$g = ($pixel >> 8) & 0xFF; | |
$b = $pixel & 0xFF; | |
if($r < 100 && $g < 100 & $b < 100) | |
return 1; | |
else | |
return 0; | |
} | |
function renderSBPL($resource, $binary=false) { | |
$graphicBytesWidth = ceil(imagesx($resource) / 8); | |
$graphicBytesHeight = ceil(imagesy($resource) / 8); | |
$graphicBytes = ''; | |
for($y = 0; $y < imagesy($resource); $y++) { | |
$stride = []; | |
for($i = 0; $i < $graphicBytesWidth; $i++) { | |
$stride[] = 0; | |
} | |
for($x = 0; $x < imagesx($resource); $x++) { | |
$stride[floor($x/8)] |= (isBlack(imagecolorat($resource,$x,$y)) << (7 - ($x % 8))); | |
} | |
$graphicBytes .= implode(array_map('chr', $stride)); | |
} | |
$graphicBytes = str_pad($graphicBytes, $graphicBytesWidth*$graphicBytesHeight*8, "\x00", STR_PAD_RIGHT); | |
if($binary) { | |
return "\x1b" . "GB" . | |
str_pad($graphicBytesWidth, 3, '0', STR_PAD_LEFT) . | |
str_pad($graphicBytesHeight, 3, '0', STR_PAD_LEFT) . | |
$graphicBytes . "\n"; | |
} else { | |
return "\x1b" . "GH" . | |
str_pad($graphicBytesWidth, 3, '0', STR_PAD_LEFT) . | |
str_pad($graphicBytesHeight, 3, '0', STR_PAD_LEFT) . | |
strtoupper(bin2hex($graphicBytes)) . "\n"; | |
} | |
} | |
function sendToPrinter($printer, $body) { | |
$fp = fsockopen($printer, 9100, $errno, $errstr, 5); | |
if(!$fp) return false; | |
fwrite($fp, $body); | |
sleep(3); // wait for print to finish - sending multiple requests too quickly crashes the printer | |
fclose($fp); | |
return true; | |
} | |
if(!isset($argv[1]) || !isset($argv[2])) { | |
die('Usage: php sato-graphic-converter.php <PRINTER-ADDRESS> <IMAGE-FILE> [<X> [<Y>]]'."\n"); | |
} | |
sendToPrinter( | |
$argv[1], | |
"\x1b"."A". // start new document | |
"\x1b"."H".str_pad($argv[3] ?? 250, 4, '0', STR_PAD_LEFT). // specify X location | |
"\x1b"."V".str_pad($argv[4] ?? 100, 4, '0', STR_PAD_LEFT). // specify Y location | |
renderSBPL(imagecreatefromjpeg($argv[2]), false). // draw image | |
"\x1b"."Q1". // 1 copy | |
"\x1b"."Z\n" // end document | |
); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/python3 | |
# SATO Graphic Converter, (c) Georg Sieber 2023, https://github.com/schorschii | |
# open source implementation of the SATO label printer graphic format, | |
# generates SBPL command to print arbitrary graphic given as parameter | |
# supports hex and binary (more performant) format | |
# usage: python3 sato-graphics-converter.py 192.168.1.50 image.jpg 250 100 | |
# important: the printer won't print if the image location is not on your label size, so please choose appropriate X/Y coordinates | |
from PIL import Image | |
import math | |
import argparse | |
import socket | |
import time | |
def isBlack(pixel): | |
return 1 if (pixel[0] < 100 and pixel[1] < 100 and pixel[2] < 100) else 0 | |
def renderSBPL(img, binary): | |
pix = img.load() | |
graphicBytesWidth = math.ceil(img.size[0]/8) | |
graphicBytesHeight = math.ceil(img.size[1]/8) | |
graphicBytes = b'' | |
for y in range(0, img.size[1]): | |
stride = [] | |
for i in range(0, graphicBytesWidth): | |
stride.append(0) | |
for x in range(0, img.size[0]): | |
stride[math.floor(x/8)] |= (isBlack(pix[x,y]) << (7 - (x % 8))) | |
graphicBytes += bytes(stride) | |
graphicBytes = graphicBytes.ljust(graphicBytesWidth*graphicBytesHeight*8, b'\x00') | |
if(binary): | |
return (b'\x1b' + b'GB' + | |
bytes('{0:03d}'.format(graphicBytesWidth),'ascii') + | |
bytes('{0:03d}'.format(graphicBytesHeight),'ascii') + | |
graphicBytes + b'\n') | |
else: | |
return (b'\x1b' + b'GH' + | |
bytes('{0:03d}'.format(graphicBytesWidth),'ascii') + | |
bytes('{0:03d}'.format(graphicBytesHeight),'ascii') + | |
bytes(graphicBytes.hex().upper(),'ascii') + b'\n') | |
def sendToPrinter(printer, body): | |
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
sock.settimeout(5) | |
sock.connect((printer, 9100)) | |
sock.sendall(body) | |
time.sleep(3) # wait for print to finish - sending multiple requests too quickly crashes the printer | |
parser = argparse.ArgumentParser() | |
parser.add_argument('printer', help='The printer IP address or DNS name') | |
parser.add_argument('imagefile', help='The image file to convert') | |
parser.add_argument('x', help='The X position where to print the graphic', default='250', nargs='?') | |
parser.add_argument('y', help='The Y position where to print the graphic', default='100', nargs='?') | |
parser.add_argument('-b', '--binary', action='store_true', help='Binary graphic output instead of hex (default)') | |
args = parser.parse_args() | |
sendToPrinter( | |
args.printer, | |
( | |
b'\x1b' + b'A' + # start new document | |
b'\x1b' + b'H' + bytes('{0:04d}'.format(int(args.x)),'ascii') + # specify X location | |
b'\x1b' + b'V' + bytes('{0:04d}'.format(int(args.y)),'ascii') + # specify Y location | |
renderSBPL(Image.open(args.imagefile), args.binary) + # draw image | |
b'\x1b' + b'Q1' + # 1 copy | |
b'\x1b' + b'Z\n' # end document | |
) | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment