diff options
Diffstat (limited to 'src/cups/backend_kodak1400.c')
-rw-r--r-- | src/cups/backend_kodak1400.c | 780 |
1 files changed, 780 insertions, 0 deletions
diff --git a/src/cups/backend_kodak1400.c b/src/cups/backend_kodak1400.c new file mode 100644 index 0000000..3b97e3a --- /dev/null +++ b/src/cups/backend_kodak1400.c @@ -0,0 +1,780 @@ +/* + * Kodak Professional 1400/805 CUPS backend -- libusb-1.0 version + * + * (c) 2013-2016 Solomon Peachy <pizza@shaftnet.org> + * + * The latest version of this program can be found at: + * + * http://git.shaftnet.org/cgit/selphy_print.git + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * [http://www.gnu.org/licenses/gpl-2.0.html] + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <signal.h> + +#define BACKEND kodak1400_backend + +#include "backend_common.h" + +/* Program states */ +enum { + S_IDLE = 0, + S_PRINTER_READY_Y, + S_PRINTER_SENT_Y, + S_PRINTER_READY_M, + S_PRINTER_SENT_M, + S_PRINTER_READY_C, + S_PRINTER_SENT_C, + S_PRINTER_READY_L, + S_PRINTER_SENT_L, + S_PRINTER_DONE, + S_FINISHED, +}; + +#define CMDBUF_LEN 96 +#define READBACK_LEN 8 + +/* File header */ +struct kodak1400_hdr { + uint8_t hdr[4]; + uint16_t columns; + uint16_t null1; + uint16_t rows; + uint16_t null2; + uint32_t planesize; + uint32_t null3; + uint8_t matte; + uint8_t laminate; + uint8_t unk1; /* Always 0x01 */ + uint8_t lam_strength; + uint8_t null4[12]; +} __attribute__((packed)); + + +/* Private data stucture */ +struct kodak1400_ctx { + struct libusb_device_handle *dev; + uint8_t endp_up; + uint8_t endp_down; + int type; + + struct kodak1400_hdr hdr; + uint8_t *plane_r; + uint8_t *plane_g; + uint8_t *plane_b; +}; + +static int send_plane(struct kodak1400_ctx *ctx, + uint8_t planeno, uint8_t *planedata, + uint8_t *cmdbuf) +{ + uint16_t temp16; + int ret; + + if (planeno != 1) { + memset(cmdbuf, 0, CMDBUF_LEN); + cmdbuf[0] = 0x1b; + cmdbuf[1] = 0x74; + cmdbuf[2] = 0x00; + cmdbuf[3] = 0x50; + + if ((ret = send_data(ctx->dev, ctx->endp_down, + cmdbuf, CMDBUF_LEN))) + return ret; + } + + memset(cmdbuf, 0, CMDBUF_LEN); + cmdbuf[0] = 0x1b; + cmdbuf[1] = 0x5a; + cmdbuf[2] = 0x54; + cmdbuf[3] = planeno; + + if (planedata) { + temp16 = htons(ctx->hdr.columns); + memcpy(cmdbuf+7, &temp16, 2); + temp16 = htons(ctx->hdr.rows); + memcpy(cmdbuf+9, &temp16, 2); + } + + if ((ret = send_data(ctx->dev, ctx->endp_down, + cmdbuf, CMDBUF_LEN))) + return ret; + + if (planedata) { + int i; + for (i = 0 ; i < ctx->hdr.rows ; i++) { + if ((ret = send_data(ctx->dev, ctx->endp_down, + planedata + i * ctx->hdr.columns, + ctx->hdr.columns))) + return ret; + } + } + + memset(cmdbuf, 0, CMDBUF_LEN); + cmdbuf[0] = 0x1b; + cmdbuf[1] = 0x74; + cmdbuf[2] = 0x01; + cmdbuf[3] = 0x50; + + if ((ret = send_data(ctx->dev, ctx->endp_down, + cmdbuf, CMDBUF_LEN))) + return ret; + + return 0; +} + +#define UPDATE_SIZE 1552 +static int kodak1400_set_tonecurve(struct kodak1400_ctx *ctx, char *fname) +{ + libusb_device_handle *dev = ctx->dev; + uint8_t endp_down = ctx->endp_down; + uint8_t endp_up = ctx->endp_up; + + uint8_t cmdbuf[8]; + uint8_t respbuf[64]; + int ret = 0, num = 0; + + INFO("Set Tone Curve from '%s'\n", fname); + + uint16_t *data = malloc(UPDATE_SIZE); + + if (!data) { + ERROR("Memory Allocation Failure!\n"); + return -1; + } + + /* Read in file */ + int tc_fd = open(fname, O_RDONLY); + if (tc_fd < 0) { + ret = -1; + goto done; + } + if (read(tc_fd, data, UPDATE_SIZE) != UPDATE_SIZE) { + ret = -2; + goto done; + } + close(tc_fd); + + /* Byteswap data to printer's format */ + for (ret = 0; ret < (UPDATE_SIZE-16)/2 ; ret++) { + data[ret] = cpu_to_le16(be16_to_cpu(data[ret])); + } + /* Null-terminate */ + memset(data + (UPDATE_SIZE-16)/2, 0, 16); + + /* Clear tables */ + memset(cmdbuf, 0, sizeof(cmdbuf)); + cmdbuf[0] = 0x1b; + cmdbuf[1] = 0xa2; + if ((ret = send_data(dev, endp_down, + cmdbuf, 2))) { + ret = -3; + goto done; + } + + ret = read_data(dev, endp_up, + respbuf, sizeof(respbuf), &num); + + if (ret < 0) + goto done; + if (num != 8) { + ERROR("Short Read! (%d/%d)\n", num, 8); + ret = -4; + goto done; + } + if (respbuf[1] != 0x01) { + ERROR("Received unexpected response\n"); + ret = -5; + goto done; + } + + /* Set up the update command */ + memset(cmdbuf, 0, sizeof(cmdbuf)); + cmdbuf[0] = 0x1b; + cmdbuf[1] = 0xa0; + cmdbuf[2] = 0x02; + cmdbuf[3] = 0x03; + cmdbuf[4] = 0x06; + cmdbuf[5] = 0x10; /* 06 10 == UPDATE_SIZE */ + if ((ret = send_data(dev, endp_down, + cmdbuf, 6))) + goto done; + + /* Send the payload over */ + if ((ret = send_data(dev, endp_down, + (uint8_t *) data, UPDATE_SIZE))) + goto done; + + /* get the response */ + ret = read_data(dev, endp_up, + respbuf, sizeof(respbuf), &num); + + if (ret < 0) + goto done; + if (num != 8) { + ERROR("Short Read! (%d/%d)\n", num, 8); + ret = -6; + goto done; + } + if (respbuf[1] != 0x00) { + ERROR("Received unexpected response!\n"); + ret = -7; + goto done; + } + +done: + free(data); + + return ret; +} + +static void kodak1400_cmdline(void) +{ + DEBUG("\t\t[ -C filename ] # Set tone curve\n"); +} + +int kodak1400_cmdline_arg(void *vctx, int argc, char **argv) +{ + struct kodak1400_ctx *ctx = vctx; + int i, j = 0; + + if (!ctx) + return -1; + + while ((i = getopt(argc, argv, GETOPT_LIST_GLOBAL "C:")) >= 0) { + switch(i) { + GETOPT_PROCESS_GLOBAL + case 'C': + j = kodak1400_set_tonecurve(ctx, optarg); + break; + default: + break; /* Ignore completely */ + } + + if (j) return j; + } + + return 0; +} + +static void *kodak1400_init(void) +{ + struct kodak1400_ctx *ctx = malloc(sizeof(struct kodak1400_ctx)); + if (!ctx) { + ERROR("Memory Allocation Failure!\n"); + return NULL; + } + memset(ctx, 0, sizeof(struct kodak1400_ctx)); + + return ctx; +} + +static void kodak1400_attach(void *vctx, struct libusb_device_handle *dev, + uint8_t endp_up, uint8_t endp_down, uint8_t jobid) +{ + struct kodak1400_ctx *ctx = vctx; + struct libusb_device *device; + struct libusb_device_descriptor desc; + + UNUSED(jobid); + + ctx->dev = dev; + ctx->endp_up = endp_up; + ctx->endp_down = endp_down; + + device = libusb_get_device(dev); + libusb_get_device_descriptor(device, &desc); + + ctx->type = lookup_printer_type(&kodak1400_backend, + desc.idVendor, desc.idProduct); +} + +static void kodak1400_teardown(void *vctx) { + struct kodak1400_ctx *ctx = vctx; + + if (!ctx) + return; + + if (ctx->plane_r) + free(ctx->plane_r); + if (ctx->plane_g) + free(ctx->plane_g); + if (ctx->plane_b) + free(ctx->plane_b); + free(ctx); +} + +static int kodak1400_read_parse(void *vctx, int data_fd) { + struct kodak1400_ctx *ctx = vctx; + int i, ret; + + if (!ctx) + return CUPS_BACKEND_FAILED; + + if (ctx->plane_r) { + free(ctx->plane_r); + ctx->plane_r = NULL; + } + if (ctx->plane_g) { + free(ctx->plane_g); + ctx->plane_g = NULL; + } + if (ctx->plane_b) { + free(ctx->plane_b); + ctx->plane_b = NULL; + } + + /* Read in then validate header */ + ret = read(data_fd, &ctx->hdr, sizeof(ctx->hdr)); + if (ret < 0 || ret != sizeof(ctx->hdr)) { + if (ret == 0) + return CUPS_BACKEND_CANCEL; + ERROR("Read failed (%d/%d/%d)\n", + ret, 0, (int)sizeof(ctx->hdr)); + perror("ERROR: Read failed"); + return CUPS_BACKEND_CANCEL; + } + if (ctx->hdr.hdr[0] != 'P' || + ctx->hdr.hdr[1] != 'G' || + ctx->hdr.hdr[2] != 'H' || + ctx->hdr.hdr[3] != 'D') { + ERROR("Unrecognized data format!\n"); + return CUPS_BACKEND_CANCEL; + } + ctx->hdr.planesize = le32_to_cpu(ctx->hdr.planesize); + ctx->hdr.rows = le16_to_cpu(ctx->hdr.rows); + ctx->hdr.columns = le16_to_cpu(ctx->hdr.columns); + + /* Set up plane data */ + ctx->plane_r = malloc(ctx->hdr.planesize); + ctx->plane_g = malloc(ctx->hdr.planesize); + ctx->plane_b = malloc(ctx->hdr.planesize); + if (!ctx->plane_r || !ctx->plane_g || !ctx->plane_b) { + ERROR("Memory allocation failure!\n"); + return CUPS_BACKEND_FAILED; + } + for (i = 0 ; i < ctx->hdr.rows ; i++) { + int j; + uint8_t *ptr; + for (j = 0 ; j < 3 ; j++) { + int remain; + if (j == 0) + ptr = ctx->plane_r + i * ctx->hdr.columns; + else if (j == 1) + ptr = ctx->plane_g + i * ctx->hdr.columns; + else if (j == 2) + ptr = ctx->plane_b + i * ctx->hdr.columns; + + remain = ctx->hdr.columns; + do { + ret = read(data_fd, ptr, remain); + if (ret < 0) { + ERROR("Read failed (%d/%d/%u) (%d/%u @ %d)\n", + ret, remain, ctx->hdr.columns, + i, ctx->hdr.rows, j); + perror("ERROR: Read failed"); + return CUPS_BACKEND_CANCEL; + } + ptr += ret; + remain -= ret; + } while (remain); + } + } + + return CUPS_BACKEND_OK; +} + +static uint8_t idle_data[READBACK_LEN] = { 0xe4, 0x72, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 }; + +static int kodak1400_main_loop(void *vctx, int copies) { + struct kodak1400_ctx *ctx = vctx; + + uint8_t rdbuf[READBACK_LEN], rdbuf2[READBACK_LEN]; + uint8_t cmdbuf[CMDBUF_LEN]; + int last_state = -1, state = S_IDLE; + int num, ret; + uint16_t temp16; + +top: + if (state != last_state) { + if (dyesub_debug) + DEBUG("last_state %d new %d\n", last_state, state); + } + + /* Send Status Query */ + memset(cmdbuf, 0, CMDBUF_LEN); + cmdbuf[0] = 0x1b; + cmdbuf[1] = 0x72; + + if ((ret = send_data(ctx->dev, ctx->endp_down, + cmdbuf, CMDBUF_LEN))) + return CUPS_BACKEND_FAILED; + + /* Read in the printer status */ + ret = read_data(ctx->dev, ctx->endp_up, + rdbuf, READBACK_LEN, &num); + + if (ret < 0) + return CUPS_BACKEND_FAILED; + if (memcmp(rdbuf, rdbuf2, READBACK_LEN)) { + memcpy(rdbuf2, rdbuf, READBACK_LEN); + } else if (state == last_state) { + sleep(1); + } + last_state = state; + + /* Error handling */ + if (rdbuf[4] || rdbuf[5]) { + ERROR("Error code reported by printer (%02x/%02x), terminating print\n", + rdbuf[4], rdbuf[5]); + return CUPS_BACKEND_STOP; // HOLD/CANCEL/FAILED? XXXX parse error! + } + + fflush(stderr); + + switch (state) { + case S_IDLE: + INFO("Printing started\n"); + + /* Send reset/attention */ + memset(cmdbuf, 0, CMDBUF_LEN); + cmdbuf[0] = 0x1b; + + if ((ret = send_data(ctx->dev, ctx->endp_down, + cmdbuf, CMDBUF_LEN))) + return CUPS_BACKEND_FAILED; + + /* Send page setup */ + memset(cmdbuf, 0, CMDBUF_LEN); + cmdbuf[0] = 0x1b; + cmdbuf[1] = 0x5a; + cmdbuf[2] = 0x53; + temp16 = be16_to_cpu(ctx->hdr.columns); + memcpy(cmdbuf+3, &temp16, 2); + temp16 = be16_to_cpu(ctx->hdr.rows); + memcpy(cmdbuf+5, &temp16, 2); + + if ((ret = send_data(ctx->dev, ctx->endp_down, + cmdbuf, CMDBUF_LEN))) + return CUPS_BACKEND_FAILED; + + /* Send lamination toggle? */ + memset(cmdbuf, 0, CMDBUF_LEN); + cmdbuf[0] = 0x1b; + cmdbuf[1] = 0x59; + cmdbuf[2] = ctx->hdr.matte; // ??? + + if ((ret = send_data(ctx->dev, ctx->endp_down, + cmdbuf, CMDBUF_LEN))) + return CUPS_BACKEND_FAILED; + + /* Send matte toggle */ + memset(cmdbuf, 0, CMDBUF_LEN); + cmdbuf[0] = 0x1b; + cmdbuf[1] = 0x60; + cmdbuf[2] = ctx->hdr.laminate; + + if (send_data(ctx->dev, ctx->endp_down, + cmdbuf, CMDBUF_LEN)) + return CUPS_BACKEND_FAILED; + + /* Send lamination strength */ + memset(cmdbuf, 0, CMDBUF_LEN); + cmdbuf[0] = 0x1b; + cmdbuf[1] = 0x62; + cmdbuf[2] = ctx->hdr.lam_strength; + + if ((ret = send_data(ctx->dev, ctx->endp_down, + cmdbuf, CMDBUF_LEN))) + return CUPS_BACKEND_FAILED; + + /* Send unknown */ + memset(cmdbuf, 0, CMDBUF_LEN); + cmdbuf[0] = 0x1b; + cmdbuf[1] = 0x61; + cmdbuf[2] = ctx->hdr.unk1; // ??? + + if ((ret = send_data(ctx->dev, ctx->endp_down, + cmdbuf, CMDBUF_LEN))) + return CUPS_BACKEND_FAILED; + + state = S_PRINTER_READY_Y; + break; + case S_PRINTER_READY_Y: + INFO("Sending YELLOW plane\n"); + if ((ret = send_plane(ctx, 1, ctx->plane_b, cmdbuf))) + return CUPS_BACKEND_FAILED; + state = S_PRINTER_SENT_Y; + break; + case S_PRINTER_SENT_Y: + if (!memcmp(rdbuf, idle_data, READBACK_LEN)) + state = S_PRINTER_READY_M; + break; + case S_PRINTER_READY_M: + INFO("Sending MAGENTA plane\n"); + if ((ret = send_plane(ctx, 2, ctx->plane_g, cmdbuf))) + return CUPS_BACKEND_FAILED; + state = S_PRINTER_SENT_M; + break; + case S_PRINTER_SENT_M: + if (!memcmp(rdbuf, idle_data, READBACK_LEN)) + state = S_PRINTER_READY_C; + break; + case S_PRINTER_READY_C: + INFO("Sending CYAN plane\n"); + if ((ret = send_plane(ctx, 3, ctx->plane_r, cmdbuf))) + return CUPS_BACKEND_FAILED; + state = S_PRINTER_SENT_C; + break; + case S_PRINTER_SENT_C: + if (!memcmp(rdbuf, idle_data, READBACK_LEN)) { + if (ctx->hdr.laminate) + state = S_PRINTER_READY_L; + else + state = S_PRINTER_DONE; + } + break; + case S_PRINTER_READY_L: + INFO("Laminating page\n"); + if ((ret = send_plane(ctx, 4, NULL, cmdbuf))) + return CUPS_BACKEND_FAILED; + state = S_PRINTER_SENT_L; + break; + case S_PRINTER_SENT_L: + if (!memcmp(rdbuf, idle_data, READBACK_LEN)) + state = S_PRINTER_DONE; + break; + case S_PRINTER_DONE: + INFO("Cleaning up\n"); + /* Cleanup */ + memset(cmdbuf, 0, CMDBUF_LEN); + cmdbuf[0] = 0x1b; + cmdbuf[1] = 0x74; + cmdbuf[2] = 0x00; + cmdbuf[3] = 0x50; + + if ((ret = send_data(ctx->dev, ctx->endp_down, + cmdbuf, CMDBUF_LEN))) + return CUPS_BACKEND_FAILED; + + state = S_FINISHED; + break; + default: + break; + }; + + if (state != S_FINISHED) + goto top; + + /* Clean up */ + if (terminate) + copies = 1; + + INFO("Print complete (%d copies remaining)\n", copies - 1); + + if (copies && --copies) { + state = S_IDLE; + goto top; + } + + return CUPS_BACKEND_OK; +} + +/* Exported */ +#define USB_VID_KODAK 0x040A +#define USB_PID_KODAK_1400 0x4022 +#define USB_PID_KODAK_805 0x4034 + +struct dyesub_backend kodak1400_backend = { + .name = "Kodak 1400/805", + .version = "0.34", + .uri_prefix = "kodak1400", + .cmdline_usage = kodak1400_cmdline, + .cmdline_arg = kodak1400_cmdline_arg, + .init = kodak1400_init, + .attach = kodak1400_attach, + .teardown = kodak1400_teardown, + .read_parse = kodak1400_read_parse, + .main_loop = kodak1400_main_loop, + .devices = { + { USB_VID_KODAK, USB_PID_KODAK_1400, P_KODAK_1400_805, "Kodak"}, + { USB_VID_KODAK, USB_PID_KODAK_805, P_KODAK_1400_805, "Kodak"}, + { 0, 0, 0, ""} + } +}; + +/* Kodak 1400/805 data format + + Spool file consists of 36-byte header followed by row-interleaved BGR data. + Native printer resolution is 2560 pixels per row, and 3010 or 3612 rows. + + Header: + + 50 47 48 44 "PGHD" + XX XX Number of columns, Little endian. Fixed at 2560. + 00 00 NULL + XX XX Number of rows, Little Endian + 00 00 NULL + XX XX XX XX Number of bytes per plane, Little Endian + 00 00 00 00 NULL + XX 00 Glossy, 01 Matte (Note: Kodak805 only supports Glossy) + XX 01 to laminate, 00 to not. + 01 Unkown, always set to 01 + XX Lamination Strength: + + 3c Glossy + 28 Matte +5 + 2e Matte +4 + 34 Matte +3 + 3a Matte +2 + 40 Matte +1 + 46 Matte + 52 Matte -1 + 5e Matte -2 + 6a Matte -3 + 76 Matte -4 + 82 Matte -5 + + 00 00 00 00 00 00 00 00 00 00 00 00 NULL + + ************************************************************************ + + The data format actually sent to the Kodak 1400 is rather different. + + All commands are null-padded to 96 bytes. + All readback values are 8 bytes long. + + Multi-byte numbers are sent BIG ENDIAN. + + Image data is sent via planes, one scanline per URB. + + <-- 1b 72 # Status query + --> e4 72 00 00 00 00 00 00 # Idle response + + <-- 1b 00 # Reset/attention? + <-- 1b 5a 53 0a 00 0b c2 # Setup (ie hdr.columns and hdr.rows) + <-- 1b 59 01 # ?? hdr.matte ? + <-- 1b 60 XX # hdr.lamination + <-- 1b 62 XX # hdr.lam_strength + <-- 1b 61 01 # ?? hdr.unk1 ? + + <-- 1b 5a 54 01 00 00 00 0a 00 0b c2 # start of plane 1 data + <-- row 1 + <-- row 2 + <-- row last + + <-- 1b 74 01 50 # ?? + + <-- 1b 72 # Status query + --> e4 72 00 00 00 00 50 59 # Printing plane 1 + [ repeats until...] + <-- 1b 72 # Status query + --> e4 72 00 00 40 00 50 59 # Paper loaded? + [ repeats until...] + <-- 1b 72 # Status query + --> e4 72 00 00 00 00 50 59 # Printing plane 1 + [ repeats until...] + <-- 1b 72 # Status query + --> e4 72 00 00 00 00 00 00 # Idle response + + <-- 1b 74 00 50 # ?? + <-- 1b 5a 54 02 00 00 00 0a 00 0b c2 # start of plane 2 data + <-- row 1 + <-- row 2 + <-- row last + <-- 1b 74 01 50 # ?? + + <-- 1b 72 # Status query + --> e4 72 00 00 00 00 50 4d # Printing plane 2 + [ repeats until...] + <-- 1b 72 # Status query + --> e4 72 00 00 00 00 00 00 # Idle response + + <-- 1b 74 00 50 # ?? + <-- 1b 5a 54 03 00 00 00 0a 00 0b c2 # start of plane 3 data + <-- row 1 + <-- row 2 + <-- row last + <-- 1b 74 01 50 # ?? + + <-- 1b 72 # Status query + --> e4 72 00 00 00 00 50 43 # Printing plane 3 + [ repeats until...] + <-- 1b 72 # Status query + --> e4 72 00 00 00 00 00 00 # Idle response + + ## this block is only present if lamination is used + + <-- 1b 74 00 50 # ?? + <-- 1b 5a 54 04 # start of lamination + <-- 1b 74 01 50 # ?? + + <-- 1b 72 # Status query + --> e4 72 00 00 00 00 50 50 # Laminating + [ repeats until...] + <-- 1b 72 # Status query + --> e4 72 00 00 00 00 00 00 # Idle response + + ## end lamination block + + <-- 1b 74 00 50 # ?? + + [[ DONE ]] + + Other readback codes seen: + + e4 72 00 00 40 00 50 59 -- ?? paper jam? + e4 72 00 00 10 00 50 59 -- media red blink, error red blink, [media mismatch]] + e4 72 00 00 10 01 50 59 -- ??? + e4 72 00 00 00 04 50 59 -- media red blink, error red [media too small for image ?] + e4 72 00 00 02 00 50 59 -- media off, error red. [out of paper] + e4 72 00 00 02 01 00 00 -- media off, error red. [out of paper] + e4 72 00 00 02 00 00 00 -- media off, error red. [out of paper] + e4 72 00 00 02 00 50 50 -- media on, error red. [paper jam while laminating] + + ********************************************* + Calibration data: + + <-- 1b a2 # ?? Reset cal tables? + --> 00 01 00 00 00 00 00 00 + + <-- 1b a0 02 03 06 10 # 06 10 == 1552 bytes aka the CAL data. + <-- cal data + + [[ Data is organized as three blocks of 512 bytes followed by + 16 NULL bytes. + + Each block appears to be 256 entries of 16-bit LE data, + so each input value is translated into a 16-bit number in the printer. + + Assuming blocks are ordered BGR. + + ]] + + --> 00 00 00 00 00 00 00 00 + +*/ |