aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias Andrée <m@maandree.se>2025-03-02 18:38:02 +0100
committerMattias Andrée <m@maandree.se>2025-03-02 18:38:02 +0100
commitb60d205c290e843e05009cc709f2d3d1c1cd4aea (patch)
tree580efa2c4965d6bdd83f450d341136f7458a4db8
downloadliblss16-b60d205c290e843e05009cc709f2d3d1c1cd4aea.tar.gz
liblss16-b60d205c290e843e05009cc709f2d3d1c1cd4aea.tar.bz2
liblss16-b60d205c290e843e05009cc709f2d3d1c1cd4aea.tar.xz
First commit
Signed-off-by: Mattias Andrée <m@maandree.se>
-rw-r--r--.gitignore15
-rw-r--r--LICENSE15
-rw-r--r--Makefile85
-rw-r--r--TODO3
-rw-r--r--config.mk8
-rw-r--r--liblss16.h216
-rw-r--r--liblss16_decode_strerror.c36
-rw-r--r--liblss16_decode_to_colour_index.c221
-rw-r--r--liblss16_decoder_init.c10
-rw-r--r--lss16toppm.c142
-rw-r--r--mk/linux.mk6
-rw-r--r--mk/macos.mk6
-rw-r--r--mk/windows.mk6
13 files changed, 769 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..db6d357
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*\#*
+*~
+*.o
+*.a
+*.lo
+*.su
+*.so
+*.so.*
+*.dll
+*.dylib
+*.gch
+*.gcov
+*.gcno
+*.gcda
+/lss16toppm
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..0e6be1c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,15 @@
+ISC License
+
+© 2025 Mattias Andrée <m@maandree.se>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..4aa5329
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,85 @@
+.POSIX:
+
+CONFIGFILE = config.mk
+include $(CONFIGFILE)
+
+OS = linux
+# Linux: linux
+# Mac OS: macos
+# Windows: windows
+include mk/$(OS).mk
+
+
+LIB_MAJOR = 1
+LIB_MINOR = 0
+LIB_VERSION = $(LIB_MAJOR).$(LIB_MINOR)
+LIB_NAME = lss16
+
+
+BIN =\
+ lss16toppm
+
+OBJ_LIB =\
+ liblss16_decoder_init.o\
+ liblss16_decode_to_colour_index.o\
+ liblss16_decode_strerror.o
+
+OBJ =\
+ $(OBJ_LIB)\
+ $(BIN:=.o)
+
+HDR =\
+ liblss16.h
+
+LOBJ = $(OBJ_LIB:.o=.lo)
+
+
+all: liblss16.a liblss16.$(LIBEXT) $(BIN)
+$(OBJ): $(HDR)
+$(LOBJ): $(HDR)
+
+.c.o:
+ $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS)
+
+.c.lo:
+ $(CC) -fPIC -c -o $@ $< $(CFLAGS) $(CPPFLAGS)
+
+lss16toppm: lss16toppm.o liblss16.a
+ $(CC) -o $@ lss16toppm.o liblss16.a $(LDFLAGS)
+
+liblss16.a: $(OBJ_LIB)
+ @rm -f -- $@
+ $(AR) rc $@ $(OBJ_LIB)
+ $(AR) ts $@ > /dev/null
+
+liblss16.$(LIBEXT): $(LOBJ)
+ $(CC) $(LIBFLAGS) -o $@ $(LOBJ) $(LDFLAGS)
+
+install: liblss16.a liblss16.$(LIBEXT)
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/bin"
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/lib"
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/include"
+ cp -- $(BIN) "$(DESTDIR)$(PREFIX)/bin/"
+ cp -- liblss16.a "$(DESTDIR)$(PREFIX)/lib/"
+ cp -- liblss16.$(LIBEXT) "$(DESTDIR)$(PREFIX)/lib/liblss16.$(LIBMINOREXT)"
+ $(FIX_INSTALL_NAME) "$(DESTDIR)$(PREFIX)/lib/liblss16.$(LIBMINOREXT)"
+ ln -sf -- liblss16.$(LIBMINOREXT) "$(DESTDIR)$(PREFIX)/lib/liblss16.$(LIBMAJOREXT)"
+ ln -sf -- liblss16.$(LIBMAJOREXT) "$(DESTDIR)$(PREFIX)/lib/liblss16.$(LIBEXT)"
+ cp -- liblss16.h "$(DESTDIR)$(PREFIX)/include/"
+
+uninstall:
+ -cd -- "$(DESTDIR)$(PREFIX)/bin/" && rm -f -- $(BIN)
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/liblss16.a"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/liblss16.$(LIBMAJOREXT)"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/liblss16.$(LIBMINOREXT)"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/liblss16.$(LIBEXT)"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/include/liblss16.h"
+
+clean:
+ -rm -f -- *.o *.a *.lo *.su *.so *.so.* *.dll *.dylib
+ -rm -f -- *.gch *.gcov *.gcno *.gcda *.$(LIBEXT) $(BIN)
+
+.SUFFIXES:
+.SUFFIXES: .lo .o .c
+
+.PHONY: all install uninstall clean
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..df2a093
--- /dev/null
+++ b/TODO
@@ -0,0 +1,3 @@
+Write encoder
+Write man pages
+Write README
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..f4adf12
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,8 @@
+PREFIX = /usr
+MANPREFIX = $(PREFIX)/share/man
+
+CC = c99
+
+CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE
+CFLAGS =
+LDFLAGS =
diff --git a/liblss16.h b/liblss16.h
new file mode 100644
index 0000000..cf51ec0
--- /dev/null
+++ b/liblss16.h
@@ -0,0 +1,216 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef LIBLSS16_H
+#define LIBLSS16_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+
+/**
+ * Image decoding state
+ */
+enum liblss16_decode_state {
+ /**
+ * End of image not yet reached
+ *
+ * File truncated if end of file reached
+ */
+ LIBLSS16_DECODE_RUNNING,
+
+ /**
+ * End of image reached
+ *
+ * Note that pixels may still be returned
+ *
+ * File holds unused data if end of file not reached
+ */
+ LIBLSS16_DECODE_END_OF_IMAGE,
+
+ /**
+ * A decoding error has occurred
+ *
+ * Processing most be aborted
+ */
+ LIBLSS16_DECODE_ERROR
+};
+
+
+/**
+ * Error values for image decoding
+ */
+enum liblss16_decode_error {
+ /**
+ * The file was at most 3 bytes long, but first
+ * bytes where {0x3D, 0x3F, 0x41} (up to the
+ * length of the file)
+ */
+ LIBLSS16_DECODE_TRUNCATED_MAGIC,
+
+ /**
+ * The file did not start with {0x3D, 0x3F, 0x41, 0x01}
+ *
+ * The number of consumed bytes, include the first bad
+ * byte in the magic. The number of consumed bytes is
+ * 1 if the first byte was wrong.
+ */
+ LIBLSS16_DECODE_BAD_MAGIC,
+
+ /**
+ * The file was too short to contain the full header
+ */
+ LIBLSS16_DECODE_TRUNCATED_HEADER,
+
+ /**
+ * At least one of the follow (you can check `.width`
+ * and `.height` to determine which apply):
+ *
+ * - The image width was set to 0
+ *
+ * - The image height was set to 0
+ *
+ * - The image height exceeded 32767
+ */
+ LIBLSS16_DECODE_BAD_IMAGE_SIZE,
+
+ /**
+ * The colour map contain a colour that had at least
+ * once of the two high bits on the encoded byte set
+ *
+ * If you want to know which colour was bad (you can
+ * only find the first one), you have to calculate it
+ * from the number of bytes consumed before the failure
+ * occurred. If the number of consumed bytes is `N`,
+ * `(N - 9) / 3` is the index of the colour (the index
+ * of the first colour is 0), and the remainder maps
+ * to the channel: 0 = red, 1 = green, 2 = blue.
+ */
+ LIBLSS16_DECODE_BAD_COLOUR,
+
+ /**
+ * Row padding was not all zeroes
+ */
+ LIBLSS16_DECODE_BAD_ROW_PADDING,
+
+ /**
+ * Run-length encoding ran across two or more rows
+ */
+ LIBLSS16_DECODE_EXCESSIVE_RUN_LENGTH,
+
+ /**
+ * The file was too short to contain the full image
+ */
+ LIBLSS16_DECODE_TRUNCATED_IMAGE
+};
+
+
+/**
+ * sRGB colour values, with transfer function applied,
+ * encoded in values in [0, 255]
+ */
+struct liblss16_colour {
+ /**
+ * Red channel value
+ */
+ uint8_t r;
+
+ /**
+ * Green channel value
+ */
+ uint8_t g;
+
+ /**
+ * Blue channel value
+ */
+ uint8_t b;
+};
+
+
+/**
+ * Decode state
+ */
+struct liblss16_decoder {
+ /**
+ * The width of the raster
+ */
+ uint16_t width;
+
+ /**
+ * The height of the raster
+ */
+ uint16_t height;
+
+ /**
+ * Image colour map
+ */
+ struct liblss16_colour colour_map[16];
+
+
+ /**
+ * For internal use
+ */
+ struct {
+ uint16_t x_rem; /**< number of pixels (after `.internal.kept`) until end of row */
+ uint16_t y_rem; /**< number of row completions until end of image */
+ uint16_t kept; /**< number of duplications held for next call */
+ uint8_t previous; /**< previous pixel */
+ uint8_t saved; /**< 0 or saved nibble plus 1 */
+ uint8_t init_state; /**< number of header bytes read */
+ uint8_t state;
+ } internal;
+};
+
+
+/**
+ * Initialise a decoder
+ *
+ * @param decoder The decoder
+ */
+void liblss16_decoder_init(struct liblss16_decoder *decoder);
+
+
+/**
+ * @param decoder Decoder state, must have been initialised with
+ * `liblss16_decoder_init` before the first call
+ * @param data Data to decode image from (may be partial)
+ * @param length The number of bytes available in `data`;
+ * end of file can be indicated using the value 0
+ * @param consumed_out The number of bytes processed from `data`
+ * @param pixels Output buffer for the indicies of the colours
+ * for the pixels in the image. They are returned
+ * primarily from to the top down and secondarily
+ * from left to right.
+ * @param pixels_size The number of elements that may be stored in `pixels`
+ * @param npixels_out Output parameter for the number of elements stored
+ * in `pixels`. Always set.
+ * @param error_out Output parameter for the error; only set if
+ * `LIBLSS16_DECODE_ERROR` is returned; may be `NULL`
+ * @return The processing state: normally `LIBLSS16_DECODE_RUNNING`,
+ * but `LIBLSS16_DECODE_END_OF_IMAGE` when the image has
+ * been fully decoded, and `LIBLSS16_DECODE_ERROR` on error
+ *
+ * `decoder->width`, `decoder->height` and `decoder->colour_map` are
+ * set once `*npixels_out` is set to a non-zero value for the first
+ * time (or some time before that). `decoder->width` and `decoder->height`
+ * are also set if `LIBLSS16_DECODE_ERROR` is returned and `*error_out`
+ * is set to `LIBLSS16_DECODE_BAD_HEADER`
+ */
+enum liblss16_decode_state liblss16_decode_to_colour_index(
+ struct liblss16_decoder *decoder,
+ const void *data, size_t length, size_t *consumed_out,
+ uint8_t *pixels, size_t pixels_size, size_t *npixels_out,
+ enum liblss16_decode_error *error_out);
+
+
+/**
+ * Get error description for an error code
+ *
+ * @param error The error code
+ * @return The error description
+ */
+#if defined(__GNUC__)
+__attribute__((__const__))
+#endif
+const char *liblss16_decode_strerror(enum liblss16_decode_error error);
+
+
+#endif
diff --git a/liblss16_decode_strerror.c b/liblss16_decode_strerror.c
new file mode 100644
index 0000000..3bd3c5a
--- /dev/null
+++ b/liblss16_decode_strerror.c
@@ -0,0 +1,36 @@
+/* See LICENSE file for copyright and license details. */
+#include "liblss16.h"
+
+
+const char *
+liblss16_decode_strerror(enum liblss16_decode_error error)
+{
+ switch (error) {
+ case LIBLSS16_DECODE_TRUNCATED_MAGIC:
+ return "Short file: magic number truncated";
+
+ case LIBLSS16_DECODE_BAD_MAGIC:
+ return "Invalid LSS16 file: bad magic number";
+
+ case LIBLSS16_DECODE_TRUNCATED_HEADER:
+ return "Short file: image header truncated";
+
+ case LIBLSS16_DECODE_BAD_IMAGE_SIZE:
+ return "Invalid LSS16 file: invalid image size";
+
+ case LIBLSS16_DECODE_BAD_COLOUR:
+ return "Invalid LSS16 file: invalid colour map";
+
+ case LIBLSS16_DECODE_BAD_ROW_PADDING:
+ return "Invalid LSS16 file: invalid row padding";
+
+ case LIBLSS16_DECODE_EXCESSIVE_RUN_LENGTH:
+ return "Invalid LSS16 file: invalid run-length encoding";
+
+ case LIBLSS16_DECODE_TRUNCATED_IMAGE:
+ return "Short file: image truncated";
+
+ default:
+ return "Unrecognised error";
+ }
+}
diff --git a/liblss16_decode_to_colour_index.c b/liblss16_decode_to_colour_index.c
new file mode 100644
index 0000000..3d184c2
--- /dev/null
+++ b/liblss16_decode_to_colour_index.c
@@ -0,0 +1,221 @@
+/* See LICENSE file for copyright and license details. */
+#include "liblss16.h"
+#include <string.h>
+
+
+enum liblss16_decode_state
+liblss16_decode_to_colour_index(struct liblss16_decoder *decoder,
+ const void *data_, size_t length, size_t *consumed_out,
+ uint8_t *pixels, size_t pixels_size, size_t *npixels_out,
+ enum liblss16_decode_error *error_out)
+{
+ const uint8_t *data = data_;
+ uint8_t n;
+ uint16_t m;
+ size_t cnt;
+
+ *consumed_out = 0;
+ *npixels_out = 0;
+
+ if (decoder->internal.kept) {
+ while (decoder->internal.kept && pixels_size) {
+ *pixels++ = decoder->internal.previous;
+ --decoder->internal.kept;
+ --pixels_size;
+ ++*npixels_out;
+ }
+ if (decoder->internal.kept)
+ return LIBLSS16_DECODE_RUNNING;
+ }
+
+ if (!length) {
+ enum liblss16_decode_error error;
+ if (decoder->internal.init_state < 4U)
+ error = LIBLSS16_DECODE_TRUNCATED_MAGIC;
+ else if (decoder->internal.init_state < 8U + 16U * 3U)
+ error = LIBLSS16_DECODE_TRUNCATED_HEADER;
+ else if (!decoder->internal.y_rem)
+ return LIBLSS16_DECODE_END_OF_IMAGE;
+ else
+ error = LIBLSS16_DECODE_TRUNCATED_IMAGE;
+ if (error_out)
+ *error_out = error;
+ return LIBLSS16_DECODE_ERROR;
+ }
+
+ switch (decoder->internal.init_state) {
+ case 0:
+ if (*data++ != 0x3DU) {
+ bad_magic:
+ if (error_out)
+ *error_out = LIBLSS16_DECODE_BAD_MAGIC;
+ return LIBLSS16_DECODE_ERROR;
+ }
+ ++*consumed_out;
+ ++decoder->internal.init_state;
+ if (!--length)
+ break;
+ /* fall through */
+
+ case 1:
+ if (*data++ != 0xF3U)
+ goto bad_magic;
+ ++*consumed_out;
+ ++decoder->internal.init_state;
+ if (!--length)
+ break;
+ /* fall through */
+
+ case 2:
+ if (*data++ != 0x13U)
+ goto bad_magic;
+ ++*consumed_out;
+ ++decoder->internal.init_state;
+ if (!--length)
+ break;
+ /* fall through */
+
+ case 3:
+ if (*data++ != 0x14U)
+ goto bad_magic;
+ ++*consumed_out;
+ ++decoder->internal.init_state;
+ if (!--length)
+ break;
+ /* fall through */
+
+ case 4:
+ decoder->width = (uint16_t)*data++;
+ ++*consumed_out;
+ ++decoder->internal.init_state;
+ if (!--length)
+ break;
+ /* fall through */
+
+ case 5:
+ decoder->width |= (uint16_t)((uint16_t)*data++ << 8);
+ ++*consumed_out;
+ ++decoder->internal.init_state;
+ if (!--length)
+ break;
+ /* fall through */
+
+ case 6:
+ decoder->height = (uint16_t)*data++;
+ ++*consumed_out;
+ ++decoder->internal.init_state;
+ if (!--length)
+ break;
+ /* fall through */
+
+ case 7:
+ decoder->height |= (uint16_t)((uint16_t)*data++ << 8);
+ ++*consumed_out;
+ ++decoder->internal.init_state;
+
+ decoder->internal.x_rem = decoder->width;
+ decoder->internal.y_rem = decoder->height;
+ if (!decoder->width || !decoder->height || decoder->height >> 15) {
+ if (error_out)
+ *error_out = LIBLSS16_DECODE_BAD_IMAGE_SIZE;
+ return LIBLSS16_DECODE_ERROR;
+ }
+
+ if (!--length)
+ break;
+ /* fall through */
+
+ default:
+ while (decoder->internal.init_state < 8U + 16U * 3U) {
+ size_t i = decoder->internal.init_state++ - 8U;
+ uint8_t value = *data++;
+ ++*consumed_out;
+ if (value & 0xC0) {
+ ((uint8_t *)decoder->colour_map)[i] = value;
+ if (error_out)
+ *error_out = LIBLSS16_DECODE_BAD_COLOUR;
+ return LIBLSS16_DECODE_ERROR;
+ } else {
+ value = (uint8_t)(((unsigned)value * 255U + 31U) / 63U);
+ ((uint8_t *)decoder->colour_map)[i] = value;
+ }
+
+ if (!--length)
+ return LIBLSS16_DECODE_RUNNING;
+ }
+
+ for (;;) {
+ if (!decoder->internal.y_rem)
+ return LIBLSS16_DECODE_END_OF_IMAGE;
+
+ if (decoder->internal.saved) {
+ n = (uint8_t)(decoder->internal.saved - 1U);
+ decoder->internal.saved = 0;
+ } else {
+ uint8_t high, low;
+ if (!pixels_size || !length)
+ return LIBLSS16_DECODE_RUNNING;
+ high = (uint8_t)(*data >> 4);
+ low = (uint8_t)(*data & 15);
+ n = low;
+ decoder->internal.saved = (uint8_t)(high + 1U);
+ ++data;
+ --length;
+ ++*consumed_out;
+ }
+
+ if (decoder->internal.state == 0U) {
+ if (n != decoder->internal.previous) {
+ decoder->internal.previous = n;
+ m = 1U;
+ goto put_m_pixels;
+ } else {
+ decoder->internal.state = 1U;
+ }
+ } else if (decoder->internal.state == 1U) {
+ if (n) {
+ m = (uint16_t)n;
+ put_m_pixels:
+ decoder->internal.state = 0U;
+ if (m > decoder->internal.x_rem) {
+ if (error_out)
+ *error_out = LIBLSS16_DECODE_EXCESSIVE_RUN_LENGTH;
+ return LIBLSS16_DECODE_ERROR;
+ }
+ decoder->internal.x_rem -= m;
+ if (!decoder->internal.x_rem) {
+ decoder->internal.x_rem = decoder->width;
+ decoder->internal.y_rem--;
+ if (decoder->internal.saved > 1U) {
+ if (error_out)
+ *error_out = LIBLSS16_DECODE_BAD_ROW_PADDING;
+ return LIBLSS16_DECODE_ERROR;
+ }
+ decoder->internal.saved = 0;
+ decoder->internal.previous = 0;
+ }
+ cnt = (size_t)m < pixels_size ? (size_t)m : pixels_size;
+ memset(pixels, decoder->internal.previous, cnt);
+ pixels = &pixels[cnt];
+ pixels_size -= cnt;
+ *npixels_out += cnt;
+ m -= (uint16_t)cnt;
+ if (m) {
+ decoder->internal.kept = m;
+ return LIBLSS16_DECODE_RUNNING;
+ }
+ } else {
+ decoder->internal.state = 2U;
+ }
+ } else if (decoder->internal.state == 2U) {
+ decoder->internal.state = (uint8_t)(n | 128U);
+ } else {
+ m = (uint16_t)((n << 4) + (decoder->internal.state & 15U) + 16U);
+ goto put_m_pixels;
+ }
+ }
+ break;
+ }
+
+ return LIBLSS16_DECODE_RUNNING;
+}
diff --git a/liblss16_decoder_init.c b/liblss16_decoder_init.c
new file mode 100644
index 0000000..e0fa309
--- /dev/null
+++ b/liblss16_decoder_init.c
@@ -0,0 +1,10 @@
+/* See LICENSE file for copyright and license details. */
+#include "liblss16.h"
+#include <string.h>
+
+
+void
+liblss16_decoder_init(struct liblss16_decoder *decoder)
+{
+ memset(decoder, 0, sizeof(*decoder));
+}
diff --git a/lss16toppm.c b/lss16toppm.c
new file mode 100644
index 0000000..db4d448
--- /dev/null
+++ b/lss16toppm.c
@@ -0,0 +1,142 @@
+/* See LICENSE file for copyright and license details. */
+#include "liblss16.h"
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+
+static const char *argv0 = "lss16toppm";
+
+
+static void
+writeall(char *buf, size_t n)
+{
+ ssize_t r;
+
+ while (n) {
+ r = write(STDOUT_FILENO, buf, n);
+ if (r < 0) {
+ if (errno == EINTR)
+ continue;
+ fprintf(stderr, "%s: write <stdout>: %s\n", argv0, strerror(errno));
+ exit(2);
+ }
+ buf += (size_t)r;
+ n -= (size_t)r;
+ }
+}
+
+
+int
+main(int argc, char *argv[])
+{
+ char buf[1024], wbuf[1024];
+ uint8_t pixels[1024];
+ struct liblss16_decoder decoder;
+ enum liblss16_decode_state state;
+ ssize_t r;
+ size_t n, off, consumed, npixels, pending;
+ enum liblss16_decode_error error;
+ int head_printed = 0;
+ int print_colour_map = 0;
+ int have_stdout, len;
+ size_t i;
+
+ /* cmdline syntax is inherited from the SYSLINUX implementation,
+ * it's bad, but it's the way it is, and the way it will remain */
+
+ if (argc) {
+ argv0 = *argv++;
+ argc--;
+ }
+
+ for (; argc; argc--, argv++) {
+ if (!strcmp(*argv, "-map")) {
+ print_colour_map = 1;
+ } else {
+ fprintf(stderr, "%s: Unknown option: %s\n", argv0, *argv);
+ }
+ }
+
+ liblss16_decoder_init(&decoder);
+
+ pending = 0;
+ for (;;) {
+ r = read(STDIN_FILENO, buf, sizeof(buf));
+ if (r <= 0) {
+ if (!r)
+ break;
+ if (errno == EINTR)
+ continue;
+ fprintf(stderr, "%s: read <stdin>: %s\n", argv0, strerror(errno));
+ return 2;
+ }
+ n = (size_t)r;
+
+ for (off = 0; off < n;) {
+ state = liblss16_decode_to_colour_index(&decoder, &buf[off], n - off, &consumed,
+ pixels, sizeof(pixels) / sizeof(*pixels), &npixels, &error);
+ off += consumed;
+ if (state == LIBLSS16_DECODE_ERROR) {
+ goto error;
+ } else if (state == LIBLSS16_DECODE_END_OF_IMAGE) {
+ if (off != n) {
+ fprintf(stderr, "%s: image file contains extraneous data\n", argv0);
+ return 1;
+ }
+ }
+ if (!npixels)
+ continue;
+
+ if (!head_printed) {
+ head_printed = 1;
+ len = sprintf(wbuf, "P6\n%u %u\n255\n", decoder.width, decoder.height);
+ if (len < 0 || (size_t)len >= sizeof(wbuf))
+ abort();
+ pending = (size_t)len;
+ if (print_colour_map) {
+ for (i = 0; i < sizeof(decoder.colour_map) / sizeof(*decoder.colour_map); i++) {
+ fprintf(stderr, "#%02x%02x%02x=%zu\n", decoder.colour_map[i].r,
+ decoder.colour_map[i].g, decoder.colour_map[i].b, i);
+ }
+ }
+ }
+ for (i = 0; i < npixels; i++) {
+ if (3U > sizeof(wbuf) - pending) {
+ writeall(wbuf, pending);
+ pending = 0;
+ }
+ wbuf[pending++] = (char)decoder.colour_map[pixels[i]].r;
+ wbuf[pending++] = (char)decoder.colour_map[pixels[i]].g;
+ wbuf[pending++] = (char)decoder.colour_map[pixels[i]].b;
+ }
+ }
+ }
+ if (pending)
+ writeall(wbuf, pending);
+
+ state = liblss16_decode_to_colour_index(&decoder, NULL, 0, &consumed, pixels,
+ sizeof(pixels) / sizeof(*pixels), &npixels, &error);
+ if (state == LIBLSS16_DECODE_ERROR) {
+ error:
+ fprintf(stderr, "%s: %s\n", argv0, liblss16_decode_strerror(error));
+ return 1;
+ } else if (state != LIBLSS16_DECODE_END_OF_IMAGE) {
+ fprintf(stderr, "%s: image file truncated\n", argv0);
+ return 1;
+ }
+
+ while (close(STDOUT_FILENO)) {
+ if (have_stdout && errno == EBADF)
+ break;
+ if (errno == EINTR) {
+ have_stdout = 1;
+ } else {
+ fprintf(stderr, "%s: write <stdout>: %s\n", argv0, strerror(errno));
+ return 2;
+ }
+ }
+ return 0;
+}
diff --git a/mk/linux.mk b/mk/linux.mk
new file mode 100644
index 0000000..ad58f69
--- /dev/null
+++ b/mk/linux.mk
@@ -0,0 +1,6 @@
+LIBEXT = so
+LIBFLAGS = -shared -Wl,-soname,lib$(LIB_NAME).$(LIBEXT).$(LIB_MAJOR)
+LIBMAJOREXT = $(LIBEXT).$(LIB_MAJOR)
+LIBMINOREXT = $(LIBEXT).$(LIB_VERSION)
+
+FIX_INSTALL_NAME = :
diff --git a/mk/macos.mk b/mk/macos.mk
new file mode 100644
index 0000000..7844ac2
--- /dev/null
+++ b/mk/macos.mk
@@ -0,0 +1,6 @@
+LIBEXT = dylib
+LIBFLAGS = -dynamiclib -Wl,-compatibility_version,$(LIB_MAJOR) -Wl,-current_version,$(LIB_VERSION)
+LIBMAJOREXT = $(LIB_MAJOR).$(LIBEXT)
+LIBMINOREXT = $(LIB_VERSION).$(LIBEXT)
+
+FIX_INSTALL_NAME = install_name_tool -id "$(PREFIX)/lib/liblss16.$(LIBMAJOREXT)"
diff --git a/mk/windows.mk b/mk/windows.mk
new file mode 100644
index 0000000..ed5ec8d
--- /dev/null
+++ b/mk/windows.mk
@@ -0,0 +1,6 @@
+LIBEXT = dll
+LIBFLAGS = -shared
+LIBMAJOREXT = $(LIB_MAJOR).$(LIBEXT)
+LIBMINOREXT = $(LIB_VERSION).$(LIBEXT)
+
+FIX_INSTALL_NAME = :