Skip to content

Instantly share code, notes, and snippets.

@schorschii
Last active November 27, 2023 14:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save schorschii/8df2a4cbc8bb8ec44e29d2d1b4b563c5 to your computer and use it in GitHub Desktop.
Save schorschii/8df2a4cbc8bb8ec44e29d2d1b4b563c5 to your computer and use it in GitHub Desktop.
SATO Label Printer SBPL Graphic Converter
<?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
);
#!/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