diff options
| author | Mattias Andrée <m@maandree.se> | 2025-03-03 20:33:43 +0100 | 
|---|---|---|
| committer | Mattias Andrée <m@maandree.se> | 2025-03-03 20:33:43 +0100 | 
| commit | 9e1c00c244e3a71a061e12e3ef9e6e27d67c6345 (patch) | |
| tree | 5f17269e0ac06774dcae0ba2a2e1d773b1eb7400 | |
| parent | misc (diff) | |
| download | liblss16-9e1c00c244e3a71a061e12e3ef9e6e27d67c6345.tar.gz liblss16-9e1c00c244e3a71a061e12e3ef9e6e27d67c6345.tar.bz2 liblss16-9e1c00c244e3a71a061e12e3ef9e6e27d67c6345.tar.xz | |
Signed-off-by: Mattias Andrée <m@maandree.se>
Diffstat (limited to '')
| -rw-r--r-- | Makefile | 6 | ||||
| -rw-r--r-- | ppmtolss16.c | 577 | 
2 files changed, 582 insertions, 1 deletions
| @@ -17,7 +17,8 @@ LIB_NAME = lss16  BIN =\ -	lss16toppm +	lss16toppm\ +	ppmtolss16  OBJ_LIB =\  	liblss16_decoder_init.o\ @@ -58,6 +59,9 @@ include mk/$(LINKING).mk  lss16toppm: lss16toppm.o $(BIN_DEP)  	$(CC) -o $@ $@.o $(LDFLAGS) $(BIN_LDFLAGS) +ppmtolss16: ppmtolss16.o $(BIN_DEP) +	$(CC) -o $@ $@.o $(LDFLAGS) $(BIN_LDFLAGS) +  liblss16.a: $(OBJ_LIB)  	@rm -f -- $@  	$(AR) rc $@ $(OBJ_LIB) diff --git a/ppmtolss16.c b/ppmtolss16.c new file mode 100644 index 0000000..c852263 --- /dev/null +++ b/ppmtolss16.c @@ -0,0 +1,577 @@ +/* See LICENSE file for copyright and license details. */ +#include "liblss16.h" +#include <arpa/inet.h> +#include <ctype.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#if defined(__clang__) +# pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +# pragma clang diagnostic ignored "-Wimplicit-fallthrough" +# pragma clang diagnostic ignored "-Wcast-align" /* clang generates false positives, missing in its analysis that +                                                  * `buf` is aligned due to use of malloc(3) and realloc(3) */ +#endif + + +static const char *argv0 = "ppmtolss16"; + +static int eof = 0; +static int in_comment = 0; +static int maptype; +static char *buf; +static size_t buf_size; +static size_t len = 0; +static size_t width = 0; +static size_t height = 0; +static uint16_t maxvalue; +static int invert; + + +static void +writeall(char *data, size_t n) +{ +	ssize_t r; + +	while (n) { +		r = write(STDOUT_FILENO, data, n); +		if (r < 0) { +			if (errno == EINTR) +				continue; +			fprintf(stderr, "%s: write <stdout>: %s\n", argv0, strerror(errno)); +			exit(2); +		} +		data += (size_t)r; +		n -= (size_t)r; +	} +} + + +#if defined(__GNUC__) +__attribute__((__noreturn__)) +#endif +static void +not_pnm(void) +{ +	fprintf(stderr, "%s: <stdin> is no a P1-P6 Portable Anymap file\n", argv0); +	exit(1); +} + + +static void +fill_buffer(void) +{ +	ssize_t r; +	while (len < buf_size) { +		r = read(STDIN_FILENO, &buf[len], buf_size - len); +		if (r <= 0) { +			if (!r) { +				eof = 1; +				break; +			} +			if (errno == EINTR) +				continue; +			fprintf(stderr, "%s: read <stdin>: %s\n", argv0, strerror(errno)); +			exit(3); +		} +		len += (size_t)r; +	} +} + + +static size_t +skip_whitespace(size_t p, int break_on_newline) +{ +	for (;;) { +		if (in_comment) { +			while (p < len && buf[p] != '\n') +				p++; +			if (p < len) { +				if (break_on_newline) +					return ++p; +				goto toggle_comment; +			} +		} else { +			while (p < len && isspace(buf[p])) { +				if (break_on_newline && buf[p] == '\n') +					return ++p; +				p++; +			} +			if (p < len) { +				if (buf[p] != '#') +					break; +			toggle_comment: +				in_comment ^= 1; +				p++; +				continue; +			} +		} +		if (eof) +			not_pnm(); +		len = 0; +		fill_buffer(); +	} +	return p; +} + + +static size_t +read_dimension(size_t p, size_t *value) +{ +	size_t digit; +	for (;;) { +		if (p == len) { +			if (eof) +				not_pnm(); +			len = 0; +			fill_buffer(); +		} +		if (isspace(buf[p])) +			break; +		if (!isdigit(buf[p])) +			not_pnm(); +		digit = (size_t)(buf[p++] & 15); +		if (*value > (UINT16_MAX - digit) / 10U) { +			*value = SIZE_MAX; +			break; +		} +		*value = *value * 10U + digit; +	} +	return p; +} + + +static void +read_header(void) +{ +	size_t p; + +	fill_buffer(); +	if (len < 3U || buf[0] != 'P' || buf[1] < '1' || buf[1] > '6' || !isspace(buf[2])) +		not_pnm(); +	maptype = buf[1] - '0'; + +	p = skip_whitespace(3U, 0); +	p = read_dimension(p, &width); +	if (!width) +		not_pnm(); +	if (width > (size_t)UINT16_MAX) { +		fprintf(stderr, "%s: image from <stdin> is too wide for LSS16 encoding\n", argv0); +		exit(2); +	} +	p = skip_whitespace(p, 0); +	p = read_dimension(p, &height); +	if (!height) +		not_pnm(); +	if (height > (size_t)(UINT16_MAX >> 1)) { +		fprintf(stderr, "%s: image from <stdin> is too tall for LSS16 encoding\n", argv0); +		exit(2); +	} + +	if (maptype == 1 || maptype == 4) { +		maxvalue = 1U; +		invert = 1; +	} else { +		size_t t; +		p = skip_whitespace(p, 0); +		p = read_dimension(p, &t); +		if (!t || t > UINT16_MAX) +			not_pnm(); +		maxvalue = (uint16_t)t; +		invert = 0; +	} + +	p = skip_whitespace(p, maptype > 3); + +	memmove(&buf[0], &buf[p], len -= p); +} + + +static int +read_image_raw(size_t bytes_per_pixel, int decode_big_endian) +{ +	size_t bytes = width * height; +	size_t i; +	int extra; + +	if (bytes > SIZE_MAX / bytes_per_pixel) { +		fprintf(stderr, "%s: image too large to for the address space\n", argv0); +		return 3; +	} +	bytes *= bytes_per_pixel; + +	if (buf_size < bytes + 1U) { +		buf = realloc(buf, buf_size = bytes + 1U); +		if (!buf) { +			fprintf(stderr, "%s: out of memory\n", argv0); +			exit(3); +		} +	} + +	if (len < bytes + 1U) { +		if (!eof) +			fill_buffer(); +		if (len < bytes) { +			fprintf(stderr, "%s: input file truncated\n", argv0); +			exit(1); +		} +	} + +	if (decode_big_endian) +		for (i = 0; i < bytes; i += 2) +			*(uint16_t *)&buf[i] = ntohs(*(const uint16_t *)&buf[i]); + +	extra = len > bytes; +	len = bytes; +	return extra; + +} + + +static int +read_image_plain(size_t words_per_pixel, size_t bytes_per_word) +{ +	size_t r, w, bytes, words = width * height; +	uint16_t value = 0, digit; +	int extra; + +	if (words > SIZE_MAX / words_per_pixel) { +		fprintf(stderr, "%s: image too large to for the address space\n", argv0); +		return 3; +	} +	words *= words_per_pixel; +	if (words > (SIZE_MAX - 16U) / bytes_per_word) { +		fprintf(stderr, "%s: image too large to for the address space\n", argv0); +		return 3; +	} +	bytes = bytes_per_word * words; + +	if (buf_size < bytes + 16U) { +		buf = realloc(buf, buf_size = bytes + 16U); +		if (!buf) { +			fprintf(stderr, "%s: out of memory\n", argv0); +			exit(3); +		} +	} + +	if (!len) { +		if (eof) { +		truncated: +			fprintf(stderr, "%s: input file truncated\n", argv0); +			exit(1); +		} +		fill_buffer(); +		if (!len) +			goto truncated; +	} + +	r = w = 0; +	while (words--) { +		if (!isdigit(buf[r])) +			not_pnm(); +		value = 0; +		do { +			digit = (uint16_t)(buf[r] & 15); +			if (value > (UINT16_MAX - digit) / 10U) +				not_pnm(); +			value = (uint16_t)(value * 10U + digit); +			if (++r == len) { +				r = len = w; +				if (eof) +					break; +				fill_buffer(); +				if (r == len) +					break; +			} +		} while (isdigit(buf[r])); +		if (bytes_per_word == 1U) { +			if (value > UINT8_MAX) +				not_pnm(); +			buf[w++] = (char)(uint8_t)value; +		} else { +			*(uint16_t *)&buf[w] = value; +			w += 2U; +		} +		if (r == len) { +			if (words) +				goto truncated; +			break; +		} + +		while (isspace(buf[r])) { +			if (++r == len) { +				r = len = w; +				if (eof) +					break; +				fill_buffer(); +				if (r == len) +					break; +			} +		} +		if (r == len) { +			if (words) +				goto truncated; +			break; +		} +	} + +	extra = r < len; +	len = w; +	return extra; +} + + +static int +read_image_bits(void) +{ +	size_t bytes = width * height; +	size_t input = ((width + 7U) / 8U) * height; +	char *inbuf; +	size_t r, w, x, y; +	int extra = 0; +	ssize_t rd; + +	if (buf_size < bytes) { +		buf = realloc(buf, buf_size = bytes); +		if (!buf) { +			fprintf(stderr, "%s: out of memory\n", argv0); +			exit(3); +		} +	} + +	inbuf = malloc(input); +	if (!inbuf) { +		fprintf(stderr, "%s: out of memory\n", argv0); +		exit(3); +	} + +	if (len > input) { +		extra = 1; +		len = input; +	} + +	memcpy(inbuf, buf, len); +	if (len < input && eof) { +	truncated: +		fprintf(stderr, "%s: input file truncated\n", argv0); +		exit(1); +	} +	while (len < input) { +		rd = read(STDIN_FILENO, &inbuf[len], input - len); +		if (rd <= 0) { +			if (!rd) +				goto truncated; +			if (errno == EINTR) +				continue; +			fprintf(stderr, "%s: read <stdin>: %s\n", argv0, strerror(errno)); +			exit(3); +		} +		len += (size_t)rd; +	} + +	r = w = 0; +	for (y = 0; y < height; y++) { +		for (x = 0; x + 8U < width; x += 8) { +			buf[w++] = (char)((inbuf[r] >> 7) & 1); +			buf[w++] = (char)((inbuf[r] >> 6) & 1); +			buf[w++] = (char)((inbuf[r] >> 5) & 1); +			buf[w++] = (char)((inbuf[r] >> 4) & 1); +			buf[w++] = (char)((inbuf[r] >> 3) & 1); +			buf[w++] = (char)((inbuf[r] >> 2) & 1); +			buf[w++] = (char)((inbuf[r] >> 1) & 1); +			buf[w++] = (char)((inbuf[r] >> 0) & 1); +			r++; +		} +		if (width & 7U) { +			inbuf[r] >>= 8U - (width & 7U); +			switch (width & 7U) { +			case 7: +				buf[w++] = (char)((inbuf[r] >> 6) & 1); +				/* fall through */ +			case 6: +				buf[w++] = (char)((inbuf[r] >> 5) & 1); +				/* fall through */ +			case 5: +				buf[w++] = (char)((inbuf[r] >> 4) & 1); +				/* fall through */ +			case 4: +				buf[w++] = (char)((inbuf[r] >> 3) & 1); +				/* fall through */ +			case 3: +				buf[w++] = (char)((inbuf[r] >> 2) & 1); +				/* fall through */ +			case 2: +				buf[w++] = (char)((inbuf[r] >> 1) & 1); +				/* fall through */ +			case 1: +				buf[w++] = (char)((inbuf[r] >> 0) & 1); +				break; +			default: +				abort(); +			} +			r++; +		} +	} + +	free(inbuf); + +	if (!extra && !eof) { +	read_extra_again: +		rd = read(STDIN_FILENO, &(char){0}, 1U); +		if (rd < 0) { +			if (errno == EINTR) +				goto read_extra_again; +			fprintf(stderr, "%s: read <stdin>: %s\n", argv0, strerror(errno)); +			exit(3); +		} else if (rd > 0) { +			extra = 1; +		} else { +			eof = 1; +		} +	} + +	len = w; +	return extra; +} + + +int +main(int argc, char *argv[]) +{ +	struct liblss16_header header; +	struct liblss16_encoder encoder; +	enum liblss16_encode_error error; +	enum liblss16_encode_state state; +	uint16_t half_maxvalue; +	size_t i, off, consumed, written; +	char wbuf[1024]; +	int extra, have_stdout = 0; + +	/* cmdline syntax is inherited from the SYSLINUX implementation, +	 * it's bad, but it's the way it is, and the way it will remain, +	 * however removed palette specification operands */ + +	if (argc) { +		argv0 = *argv++; +		argc--; +		if (argc) { +			fprintf(stderr, "%s: Unknown option: %s\n", argv0, *argv); +			return 3; +		} +	} + +	buf_size = 1024U; +	buf = malloc(buf_size); +	if (!buf) { +		fprintf(stderr, "%s: out of memory\n", argv0); +		return 3; +	} + +	read_header(); + +	switch (maptype) { +	case 1: +		extra = read_image_plain(1U, 1U); +		break; +	case 2: +		extra = read_image_plain(1U, maxvalue > 255 ? 2U : 1U); +		break; +	case 3: +		extra = read_image_plain(3U, maxvalue > 255 ? 2U : 1U); +		break; +	case 4: +		extra = read_image_bits(); +		break; +	case 5: +		extra = read_image_raw(maxvalue > 255 ? 2U : 1U, maxvalue > 255); +		break; +	case 6: +		extra = read_image_raw(maxvalue > 255 ? 6U : 3U, maxvalue > 255); +		break; +	default: +#if defined(__GNUC__) +		__builtin_unreachable(); +#else +		abort(); +#endif +	} + +	if (extra) +		fprintf(stderr, "%s: <stdin> contained extraneous data\n", argv0); + +	if (maptype > 3) +		maptype -= 3; + +	half_maxvalue = maxvalue >> 1; +	if (maxvalue > 255) { +		uint16_t value; +		len >>= 1; +		for (i = 0; i < len; i++) { +			value = *(uint16_t *)&buf[i << 1]; +			if (value > maxvalue) +				not_pnm(); +			buf[i] = (char)(uint8_t)(((uint32_t)value * 63U + half_maxvalue) / maxvalue); +		} +	} else if (maxvalue == 1) { +		for (i = 0; i < len; i++) +			if (buf[i] & 0xFE) +				not_pnm(); +		if (maptype == 3) { +			for (i = 0; i < len; i++) +				buf[i] = (char)(uint8_t)((uint8_t)buf[i] * 63U); +		} else { +			maptype = 1; +		} +	} else if (maxvalue != 63) { +		for (i = 0; i < len; i++) { +			if ((uint8_t)buf[i] > maxvalue) +				not_pnm(); +			buf[i] = (char)(uint8_t)(((uint8_t)buf[i] * 63U + half_maxvalue) / maxvalue); +		} +	} + +	memset(&header, 0, sizeof(header)); +	header.width = (uint16_t)width; +	header.height = (uint16_t)height; + +	if (maptype == 1) { +		header.colour_map[!invert].r = 63U; +		header.colour_map[!invert].g = 63U; +		header.colour_map[!invert].b = 63U; +	} else if (maptype == 2) { +		/* TODO convert to 16 colours -- greyscale values [0, 63], quantize to 16 colours and map Y to (Y, Y, Y) */ +	} else { +		/* TODO convert to 16 colours -- [0, 63]-triplet values, quantize to 16 colours */ +	} + +	liblss16_optimise(&header, (uint8_t *)buf); +	if (liblss16_encoder_init(&encoder, &header, 1, &error)) { +		fprintf(stderr, "%s:liblss16_encoder_init: %s\n", argv0, liblss16_encode_strerror(error)); +		exit(3); +	} +	off = 0; +	do { +		state = liblss16_encode_from_colour_index(&encoder, wbuf, sizeof(buf), &written, +		                                          (const uint8_t *)&buf[off], len - off, &consumed); +		off += consumed; +		writeall(wbuf, written); +	} while (state == LIBLSS16_ENCODE_RUNNING); +	if (off != len) +		abort(); + +	free(buf); + +	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 3; +		} +	} +	return 0; +} | 
