summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSolomon Peachy <pizza@shaftnet.org>2018-04-19 08:15:01 -0400
committerSolomon Peachy <pizza@shaftnet.org>2018-04-20 16:23:03 -0400
commitfacf010d1079d26655e8a56177afcee2c1ab4f9b (patch)
treea5b6e9d5b70287887d79bfd4cc14867fcd78a3fd
parentbce5aaed4568de99b62a19de3b88c88c2d2159e7 (diff)
downloadselphy_print-facf010d1079d26655e8a56177afcee2c1ab4f9b.tar.gz
selphy_print-facf010d1079d26655e8a56177afcee2c1ab4f9b.tar.bz2
selphy_print-facf010d1079d26655e8a56177afcee2c1ab4f9b.zip
mitsu98xx: Continue to rejigger internals to prepare for library.
* "raw" expects lamination data, always * non-raw now expects packed 8bpp BGR data, single plane * gamma correction function converts packed BGR8 -> planar YMC16. * 3D LUTs implemented. (lifted from lib70x)
-rw-r--r--backend_mitsu9550.c428
-rw-r--r--lib70x/data/M98XXL01.lutbin0 -> 14739 bytes
2 files changed, 321 insertions, 107 deletions
diff --git a/backend_mitsu9550.c b/backend_mitsu9550.c
index d7197a4..85ab5c4 100644
--- a/backend_mitsu9550.c
+++ b/backend_mitsu9550.c
@@ -65,7 +65,7 @@
#define MITSU_M98xx_LAMINATE_FILE CORRTABLE_PATH "M98MATTE.raw"
#define MITSU_M98xx_DATATABLE_FILE CORRTABLE_PATH "M98TABLE.dat"
-
+#define MITSU_M98xx_LUT_FILE CORRTABLE_PATH "M98XXL01.lut"
#define LAMINATE_STRIDE 1868
#define DATATABLE_SIZE 42204
@@ -110,18 +110,35 @@ struct mitsu9550_hdr4 {
/* Data plane header */
struct mitsu9550_plane {
uint8_t cmd[4]; /* 1b 5a 54 XX */ /* XX == 0x10 if 16bpp, 0x00 for 8bpp */
+ uint16_t col_offset; /* BE, normally 0, where we start dumping data */
uint16_t row_offset; /* BE, normally 0, where we start dumping data */
- uint16_t null; /* ??? */
uint16_t cols; /* BE */
uint16_t rows; /* BE */
} __attribute__((packed));
/* CP98xx Tabular Data */
struct mitsu98xx_data {
- uint16_t GNMby[256];
- uint16_t GNMgm[256];
- uint16_t GNMrc[256];
- uint8_t WMAM[12532]; /* Unknown for now */
+ uint16_t GNMby[256]; // @0
+ uint16_t GNMgm[256]; // @512
+ uint16_t GNMrc[256]; // @1024
+ double GammaParams[3]; // @1536
+ uint8_t KH[2048]; // @1560
+ uint32_t unk_b[3]; // @3608
+
+ struct {
+ double unka[256]; // @0
+ double unkb[256]; // @2048
+ uint32_t unkc[10]; // @4096
+ double unkd[256]; // @4136
+ double unke[256]; // @6184
+ uint32_t unkf[10]; // @8232
+ double unkg[256]; // @10320
+ // @12368
+ } WMAM; // @3620
+ uint8_t unc_d[4]; // @13940
+ uint8_t sharp[104]; // @13944
+ uint8_t unk_e[20]; // @14048
+ // @14068
} __attribute__((packed));
struct mitsu98xx_tables {
@@ -271,14 +288,241 @@ struct mitsu9550_status2 {
} \
} while (0);
-static void mitsu98xx_dogamma(uint8_t *src, uint16_t *dest,
+static void mitsu98xx_dogamma(uint8_t *src, uint16_t *dest, uint8_t plane,
uint16_t *table, uint32_t len)
{
+ src += plane;
while(len--) {
- *dest++ = table[*src++];
+ *dest++ = table[*src];
+ src += 3;
+ }
+}
+
+static int mitsu98xx_fillmatte(struct mitsu9550_ctx *ctx)
+{
+ int fd, i;
+ uint32_t j, remain;
+
+ DEBUG("Reading %d bytes of matte data from disk (%d/%d)\n", ctx->cols * ctx->rows, ctx->cols, LAMINATE_STRIDE);
+ fd = open(MITSU_M98xx_LAMINATE_FILE, O_RDONLY);
+ if (fd < 0) {
+ WARNING("Unable to open matte lamination data file '%s'\n", MITSU_M98xx_LAMINATE_FILE);
+ ctx->hdr1.matte = 0;
+ goto done;
+ }
+
+ /* Fill in the lamination plane header */
+ struct mitsu9550_plane *matte = (struct mitsu9550_plane *)(ctx->databuf + ctx->datalen);
+ matte->cmd[0] = 0x1b;
+ matte->cmd[1] = 0x5a;
+ matte->cmd[2] = 0x54;
+ matte->cmd[3] = 0x10;
+ matte->row_offset = 0;
+ matte->col_offset = 0;
+ matte->cols = ctx->hdr1.cols;
+ matte->rows = ctx->hdr1.rows;
+ ctx->datalen += sizeof(struct mitsu9550_plane);
+
+ /* Read in the matte data plane */
+ for (j = 0 ; j < ctx->rows ; j++) {
+ remain = LAMINATE_STRIDE * 2;
+
+ /* Read one row of lamination data at a time */
+ while (remain) {
+ i = read(fd, ctx->databuf + ctx->datalen, remain);
+ if (i < 0)
+ return CUPS_BACKEND_CANCEL;
+ if (i == 0) {
+ /* We hit EOF, restart from beginning */
+ lseek(fd, 0, SEEK_SET);
+ continue;
+ }
+ ctx->datalen += i;
+ remain -= i;
+ }
+ /* Back off the buffer so we "wrap" on the print row. */
+ ctx->datalen -= ((LAMINATE_STRIDE - ctx->cols) * 2);
+ }
+ /* We're done! */
+ close(fd);
+
+ /* Fill in the lamination plane footer */
+ ctx->databuf[ctx->datalen++] = 0x1b;
+ ctx->databuf[ctx->datalen++] = 0x50;
+ ctx->databuf[ctx->datalen++] = 0x56;
+ ctx->databuf[ctx->datalen++] = 0x00;
+
+done:
+ return CUPS_BACKEND_OK;
+}
+
+/*** 3D color Lookup table stuff. Taken out of lib70x ****/
+#define LUT_LEN 14739
+#define COLORCONV_RGB 0
+#define COLORCONV_BGR 1
+
+struct CColorConv3D {
+ uint8_t lut[17][17][17][3];
+};
+
+/* Load the Lookup table off of disk into *PRE-ALLOCATED* buffer */
+int CColorConv3D_Get3DColorTable(uint8_t *buf, const char *filename)
+{
+ FILE *stream;
+
+ if (!filename)
+ return 1;
+ if (!*filename)
+ return 2;
+ if (!buf)
+ return 3;
+
+ stream = fopen(filename, "rb");
+ if (!stream)
+ return 4;
+
+ fseek(stream, 0, SEEK_END);
+ if (ftell(stream) < LUT_LEN) {
+ fclose(stream);
+ return 5;
+ }
+ fseek(stream, 0, SEEK_SET);
+ fread(buf, 1, LUT_LEN, stream);
+ fclose(stream);
+
+ return 0;
+}
+
+/* Parse the on-disk LUT data into the structure.... */
+struct CColorConv3D *CColorConv3D_Load3DColorTable(const uint8_t *ptr)
+{
+ struct CColorConv3D *this;
+ this = malloc(sizeof(*this));
+ if (!this)
+ return NULL;
+
+ int i, j, k;
+
+ for (i = 0 ; i <= 16 ; i++) {
+ for (j = 0 ; j <= 16 ; j++) {
+ for (k = 0; k <= 16; k++) {
+ this->lut[k][j][i][2] = *ptr++;
+ this->lut[k][j][i][1] = *ptr++;
+ this->lut[k][j][i][0] = *ptr++;
+ }
+ }
}
+ return this;
+}
+void CColorConv3D_Destroy3DColorTable(struct CColorConv3D *this)
+{
+ free(this);
+}
+
+/* Transform a single pixel. */
+static void CColorConv3D_DoColorConvPixel(struct CColorConv3D *this, uint8_t *redp, uint8_t *grnp, uint8_t *blup)
+{
+ int red_h;
+ int grn_h;
+ int blu_h;
+ int grn_li;
+ int red_li;
+ int blu_li;
+ int red_l;
+ int grn_l;
+ int blu_l;
+
+ uint8_t *tab0; // @ 14743
+ uint8_t *tab1; // @ 14746
+ uint8_t *tab2; // @ 14749
+ uint8_t *tab3; // @ 14752
+ uint8_t *tab4; // @ 14755
+ uint8_t *tab5; // @ 14758
+ uint8_t *tab6; // @ 14761
+ uint8_t *tab7; // @ 14764
+
+ red_h = *redp >> 4;
+ red_l = *redp & 0xF;
+ red_li = 16 - red_l;
+
+ grn_h = *grnp >> 4;
+ grn_l = *grnp & 0xF;
+ grn_li = 16 - grn_l;
+
+ blu_h = *blup >> 4;
+ blu_l = *blup & 0xF;
+ blu_li = 16 - blu_l;
+
+// printf("%d %d %d =>", *redp, *grnp, *blup);
+
+ tab0 = this->lut[red_h+0][grn_h+0][blu_h+0];
+ tab1 = this->lut[red_h+1][grn_h+0][blu_h+0];
+ tab2 = this->lut[red_h+0][grn_h+1][blu_h+0];
+ tab3 = this->lut[red_h+1][grn_h+1][blu_h+0];
+ tab4 = this->lut[red_h+0][grn_h+0][blu_h+1];
+ tab5 = this->lut[red_h+1][grn_h+0][blu_h+1];
+ tab6 = this->lut[red_h+0][grn_h+1][blu_h+1];
+ tab7 = this->lut[red_h+1][grn_h+1][blu_h+1];
+
+#if 0
+ printf(" %d %d %d ", tab0[0], tab0[1], tab0[2]);
+ printf(" %d %d %d ", tab1[0], tab1[1], tab1[2]);
+ printf(" %d %d %d ", tab2[0], tab2[1], tab2[2]);
+ printf(" %d %d %d ", tab3[0], tab3[1], tab3[2]);
+ printf(" %d %d %d ", tab4[0], tab4[1], tab4[2]);
+ printf(" %d %d %d ", tab5[0], tab5[1], tab5[2]);
+ printf(" %d %d %d ", tab6[0], tab6[1], tab6[2]);
+ printf(" %d %d %d ", tab7[0], tab7[1], tab7[2]);
+#endif
+ *redp = (blu_li
+ * (grn_li * (red_li * tab0[0] + red_l * tab1[0])
+ + grn_l * (red_li * tab2[0] + red_l * tab3[0]))
+ + blu_l
+ * (grn_li * (red_li * tab4[0] + red_l * tab5[0])
+ + grn_l * (red_li * tab6[0] + red_l * tab7[0]))
+ + 2048) >> 12;
+ *grnp = (blu_li
+ * (grn_li * (red_li * tab0[1] + red_l * tab1[1])
+ + grn_l * (red_li * tab2[1] + red_l * tab3[1]))
+ + blu_l
+ * (grn_li * (red_li * tab4[1] + red_l * tab5[1])
+ + grn_l * (red_li * tab6[1] + red_l * tab7[1]))
+ + 2048) >> 12;
+ *blup = (blu_li
+ * (grn_li * (red_li * tab0[2] + red_l * tab1[2])
+ + grn_l * (red_li * tab2[2] + red_l * tab3[2]))
+ + blu_l
+ * (grn_li * (red_li * tab4[2] + red_l * tab5[2])
+ + grn_l * (red_li * tab6[2] + red_l * tab7[2]))
+ + 2048) >> 12;
+
+// printf("=> %d %d %d\n", *redp, *grnp, *blup);
}
+/* Perform a total conversion on an entire image */
+void CColorConv3D_DoColorConv(struct CColorConv3D *this, uint8_t *data, uint16_t cols, uint16_t rows, uint32_t stride, int rgb_bgr)
+{
+ uint16_t i, j;
+
+ uint8_t *ptr;
+
+ for ( i = 0; i < rows ; i++ )
+ {
+ ptr = data;
+ for ( j = 0; cols > j; j++ )
+ {
+ if (rgb_bgr) {
+ CColorConv3D_DoColorConvPixel(this, ptr + 2, ptr + 1, ptr);
+ } else {
+ CColorConv3D_DoColorConvPixel(this, ptr, ptr + 1, ptr + 2);
+ }
+ ptr += 3;
+ }
+ data += stride;
+ }
+}
+/* ---- end 3D LUT ---- */
+
static void *mitsu9550_init(void)
{
struct mitsu9550_ctx *ctx = malloc(sizeof(struct mitsu9550_ctx));
@@ -378,7 +622,7 @@ top:
/* We're in the data portion now */
if (buf[3] == 0x10)
planelen *= 2;
- else if (ctx->is_98xx)
+ else if (ctx->is_98xx && buf[3] == 0x80)
is_raw = 0;
goto hdr_done;
@@ -421,19 +665,19 @@ top:
hdr_done:
- /* We have three planes and the final terminator to read */
- remain = 3 * (planelen + sizeof(struct mitsu9550_plane)) + sizeof(struct mitsu9550_cmd);
+ if (is_raw) {
+ /* We have three planes and the final terminator to read */
+ remain = 3 * (planelen + sizeof(struct mitsu9550_plane)) + sizeof(struct mitsu9550_cmd);
+ } else {
+ /* We have one planes and the final terminator to read */
+ remain = planelen * 3 + sizeof(struct mitsu9550_plane) + sizeof(struct mitsu9550_cmd);
+ }
/* Mitsu9600 windows spool uses more, smaller blocks, but plane data is the same */
if (ctx->type == P_MITSU_9600) {
remain += 128 * sizeof(struct mitsu9550_plane); /* 39 extra seen on 4x6" */
}
- /* Don't forget the matte plane! */
- if (ctx->hdr1.matte) {
- remain += planelen + sizeof(struct mitsu9550_plane) + sizeof(struct mitsu9550_cmd);
- }
-
/* 9550S/9800S doesn't typically sent over hdr4! */
if (ctx->type == P_MITSU_9550S ||
ctx->type == P_MITSU_9800S) {
@@ -468,6 +712,16 @@ hdr_done:
close(fd);
}
+ /* Disable matte if the printer doesn't support it */
+ if (ctx->hdr1.matte) {
+ if (ctx->type != P_MITSU_9810) {
+ WARNING("Matte not supported on this printer, disabling\n");
+ ctx->hdr1.matte = 0;
+ } else if (is_raw) {
+ remain += planelen + sizeof(struct mitsu9550_plane) + sizeof(struct mitsu9550_cmd);
+ }
+ }
+
/* Allocate buffer for the payload */
ctx->datalen = 0;
ctx->databuf = malloc(remain);
@@ -476,11 +730,6 @@ hdr_done:
return CUPS_BACKEND_RETRY_CURRENT;
}
- /* Back off the data to read if the backend generates the matte data internally */
- if (ctx->hdr1.matte == 0x80) {
- remain -= planelen + sizeof(struct mitsu9550_plane) + sizeof(struct mitsu9550_cmd);
- }
-
/* Load up the data blocks.*/
while(1) {
/* Note that 'buf' needs to be already filled here! */
@@ -553,17 +802,35 @@ hdr_done:
}
}
- /* Disable matte if the printer doesn't support it */
- if (ctx->hdr1.matte && ctx->type != P_MITSU_9810) {
- WARNING("Matte not supported on this printer, disabling\n");
- ctx->hdr1.matte = 0;
- }
-
/* Do the 98xx processing here */
if (ctx->is_98xx && !is_raw) {
uint8_t *newbuf;
uint32_t newlen = 0;
struct mitsu98xx_data *table;
+ struct CColorConv3D *lut;
+
+ /* Apply LUT */
+ if (ctx->hdr2.unkc[9]) {
+ uint8_t *buf = malloc(LUT_LEN);
+ if (!buf) {
+ ERROR("Memory allocation failure!\n");
+ return CUPS_BACKEND_RETRY_CURRENT;
+ }
+ if (CColorConv3D_Get3DColorTable(buf, MITSU_M98xx_LUT_FILE)) {
+ ERROR("Unable to open LUT file '%s'\n", MITSU_M98xx_LUT_FILE);
+ return CUPS_BACKEND_CANCEL;
+ }
+ lut = CColorConv3D_Load3DColorTable(buf);
+ free(buf);
+ if (!lut) {
+ ERROR("Unable to parse LUT\n");
+ return CUPS_BACKEND_CANCEL;
+ }
+ CColorConv3D_DoColorConv(lut, ctx->databuf + sizeof(struct mitsu9550_plane),
+ ctx->cols, ctx->rows, ctx->cols * 3, COLORCONV_BGR);
+ CColorConv3D_Destroy3DColorTable(lut);
+ ctx->hdr2.unkc[9] = 0;
+ }
planelen *= 2;
remain = 4 * (planelen + sizeof(struct mitsu9550_plane)) + sizeof(struct mitsu9550_cmd);
@@ -586,109 +853,55 @@ hdr_done:
break;
}
- ctx->datalen = 0;
-
/* For B/Y plane */
- memcpy(newbuf + newlen, ctx->databuf + ctx->datalen, sizeof(struct mitsu9550_plane));
- ctx->databuf[ctx->datalen + 3] = 0x10; /* ie 16bpp data */
- ctx->datalen += sizeof(struct mitsu9550_plane);
+ memcpy(newbuf + newlen, ctx->databuf, sizeof(struct mitsu9550_plane));
+ newbuf[newlen + 3] = 0x10; /* ie 16bpp data */
newlen += sizeof(struct mitsu9550_plane);
- mitsu98xx_dogamma(ctx->databuf + ctx->datalen,
+ mitsu98xx_dogamma(ctx->databuf + sizeof(struct mitsu9550_plane),
(uint16_t*) (newbuf + newlen),
+ 0,
table->GNMby,
planelen / 2);
- ctx->datalen += planelen / 2;
newlen += planelen;
/* For G/M plane */
- memcpy(newbuf + newlen, ctx->databuf + ctx->datalen, sizeof(struct mitsu9550_plane));
- ctx->databuf[ctx->datalen + 3] = 0x10; /* ie 16bpp data */
- ctx->datalen += sizeof(struct mitsu9550_plane);
+ memcpy(newbuf + newlen, ctx->databuf, sizeof(struct mitsu9550_plane));
+ newbuf[newlen + 3] = 0x10; /* ie 16bpp data */
newlen += sizeof(struct mitsu9550_plane);
- mitsu98xx_dogamma(ctx->databuf + ctx->datalen,
+ mitsu98xx_dogamma(ctx->databuf + sizeof(struct mitsu9550_plane),
(uint16_t*) (newbuf + newlen),
+ 1,
table->GNMgm,
planelen / 2);
- ctx->datalen += planelen / 2;
newlen += planelen;
/* For R/C plane */
- memcpy(newbuf + newlen, ctx->databuf + ctx->datalen, sizeof(struct mitsu9550_plane));
- ctx->databuf[ctx->datalen + 3] = 0x10; /* ie 16bpp data */
- ctx->datalen += sizeof(struct mitsu9550_plane);
+ memcpy(newbuf + newlen, ctx->databuf, sizeof(struct mitsu9550_plane));
+ newbuf[newlen + 3] = 0x10; /* ie 16bpp data */
newlen += sizeof(struct mitsu9550_plane);
- mitsu98xx_dogamma(ctx->databuf + ctx->datalen,
+ mitsu98xx_dogamma(ctx->databuf + sizeof(struct mitsu9550_plane),
(uint16_t*) (newbuf + newlen),
+ 2,
table->GNMrc,
planelen / 2);
- ctx->datalen += planelen / 2;
newlen += planelen;
+ /* And finally, the job footer. */
+ memcpy(newbuf + newlen, ctx->databuf + sizeof(struct mitsu9550_plane) + planelen * 3, sizeof(struct mitsu9550_cmd));
+ newlen += sizeof(struct mitsu9550_cmd);
+
/* Clean up */
free(ctx->databuf);
ctx->databuf = newbuf;
ctx->datalen = newlen;
- }
- /* If the backend has to generate the matte data... */
- if (ctx->hdr1.matte == 0x80) {
- int fd;
- uint32_t j;
-
- DEBUG("Reading %d bytes of matte data from disk (%d/%d)\n", be16_to_cpu(ctx->hdr1.cols) * be16_to_cpu(ctx->hdr1.rows), be16_to_cpu(ctx->hdr1.cols), LAMINATE_STRIDE);
- fd = open(MITSU_M98xx_LAMINATE_FILE, O_RDONLY);
- if (fd < 0) {
- WARNING("Unable to open matte lamination data file '%s'\n", MITSU_M98xx_LAMINATE_FILE);
- ctx->hdr1.matte = 0;
- goto done;
+ /* Now handle the matte plane generation */
+ if (ctx->hdr1.matte) {
+ if ((i = mitsu98xx_fillmatte(ctx)))
+ return i;
}
-
- /* Fill in the lamination plane header */
- struct mitsu9550_plane *matte = (struct mitsu9550_plane *)(ctx->databuf + ctx->datalen);
- matte->cmd[0] = 0x1b;
- matte->cmd[1] = 0x5a;
- matte->cmd[2] = 0x54;
- matte->cmd[3] = 0x10;
- matte->row_offset = 0;
- matte->null = 0;
- matte->cols = ctx->hdr1.cols;
- matte->rows = ctx->hdr1.rows;
- ctx->datalen += sizeof(struct mitsu9550_plane);
-
- /* Read in the matte data plane */
- for (j = 0 ; j < be16_to_cpu(ctx->hdr1.rows) ; j++) {
- remain = LAMINATE_STRIDE * 2;
-
- /* Read one row of lamination data at a time */
- while (remain) {
- i = read(fd, ctx->databuf + ctx->datalen, remain);
- if (i < 0)
- return CUPS_BACKEND_CANCEL;
- if (i == 0) {
- /* We hit EOF, restart from beginning */
- lseek(fd, 0, SEEK_SET);
- continue;
- }
- ctx->datalen += i;
- remain -= i;
- }
- /* Back off the buffer so we "wrap" on the print row. */
- ctx->datalen -= ((LAMINATE_STRIDE - ctx->hdr1.cols) * 2);
- }
- /* We're done! */
- close(fd);
-
- /* Set laminate header to a sane value */
- ctx->hdr1.matte = 0x01;
-
- /* Fill in the lamination plane footer */
- ctx->databuf[ctx->datalen++] = 0x1b;
- ctx->databuf[ctx->datalen++] = 0x50;
- ctx->databuf[ctx->datalen++] = 0x56;
- ctx->databuf[ctx->datalen++] = 0x00;
}
-done:
return CUPS_BACKEND_OK;
}
@@ -1458,7 +1671,8 @@ struct dyesub_backend mitsu9550_backend = {
1b 57 21 2e 00 80 00 22 QQ QQ 00 00 00 00 00 00 :: ZZ ZZ = num copies (>= 0x01)
00 00 00 00 00 00 00 00 00 00 00 00 ZZ ZZ 00 00 :: YY = 00/80 Fine/SuperFine (9550), 10/80 Fine/Superfine (98x0), 00 (9600)
XX 00 00 00 00 00 YY 00 00 00 00 00 00 00 00 00 :: XX = 00 normal, 83 Cut 2x6 (9550 only!)
- 00 01 :: QQ QQ = 0x0803 on 9550, 0x0801 on 98x0, 0x0003 on 9600, 0xa803 on 9500
+ RR 01 :: QQ QQ = 0x0803 on 9550, 0x0801 on 98x0, 0x0003 on 9600, 0xa803 on 9500
+ :: RR = 01 for "use LUT" on 98xx, 0x00 otherwise. Extension to stock.
~~~ Header 3 (9550 and 9800-S only..)
@@ -1476,13 +1690,13 @@ struct dyesub_backend mitsu9550_backend = {
~~~~ Data follows:
- Format is: planar YMC16 for 98x0 (only 12 bits used)
- planar BGR for 9550DW
- planar RGB for 9550DW-S and 9600DW
+ Format is: planar YMC16 for 98x0 (but only 12 bits used, BIG endian)
+ planar RGB for all others
- 1b 5a 54 ?? RR RR 00 00 07 14 04 d8 :: 0714 == columns, 04d8 == rows
- :: RRRR == row offset for data
+ 1b 5a 54 ?? RR RR CC CC 07 14 04 d8 :: 0714 == columns, 04d8 == rows
+ :: RRRR == row offset for data, CCCC == col offset for data
:: ?? == 0x00 for 8bpp, 0x10 for 16/12bpp.
+ :: 0x80 for PACKED BGR!
Data follows immediately, no padding.
diff --git a/lib70x/data/M98XXL01.lut b/lib70x/data/M98XXL01.lut
new file mode 100644
index 0000000..7634108
--- /dev/null
+++ b/lib70x/data/M98XXL01.lut
Binary files differ