Commit d5f6f70d authored by FRIGN's avatar FRIGN

imagefile -> farbfeld

 - Rename the format
 - Change the format specification
 - Drop old tools waiting to be fixed on a later date, just keep
   fixed png for now
 - Simplify other stuff

This is a direct consequence of my slcon2-talk on this topic.
At first I planned to have 64 bits per channel, but this is
overkill.
parent 0b0fcaa7
ISC-License
(c) 2014 Laslo Hunhold <dev@frign.de>
(c) 2014-2015 Laslo Hunhold <dev@frign.de>
(c) 2014 sin <sin@2f30.org>
(c) 2014 Hiltjo Posthuma <hiltjo@codemadness.org>
......
# imagefile - tools to convert between png and if
# imagefile - tools to convert between png and ff
# See LICENSE file for copyright and license details
include config.mk
SRC = png2if.c if2png.c jpg2if.c gif2if.c
SRC = png2ff.c ff2png.c
OBJ = ${SRC:.c=.o}
all: options png2if if2png jpg2if gif2if
all: options png2ff ff2png
options:
@echo imagefile build options:
@echo farbfeld build options:
@echo "CFLAGS = ${CFLAGS}"
@echo "LDFLAGS = ${LDFLAGS}"
@echo "CC = ${CC}"
......@@ -20,40 +20,28 @@ options:
${OBJ}: config.mk
gif2if: gif2if.o
png2ff: png2ff.o
@echo CC -o $@
@${CC} -o $@ gif2if.o ${GIF_LIBS} ${LDFLAGS}
@${CC} -o $@ png2ff.o ${PNG_LIBS} ${LDFLAGS}
jpg2if: jpg2if.o
ff2png: ff2png.o
@echo CC -o $@
@${CC} -o $@ jpg2if.o ${JPEG_LIBS} ${LDFLAGS}
png2if: png2if.o
@echo CC -o $@
@${CC} -o $@ png2if.o ${PNG_LIBS} ${LDFLAGS}
if2png: if2png.o
@echo CC -o $@
@${CC} -o $@ if2png.o ${PNG_LIBS} ${LDFLAGS}
@${CC} -o $@ ff2png.o ${PNG_LIBS} ${LDFLAGS}
clean:
@echo cleaning
@rm -f png2if if2png jpg2if gif2if ${OBJ}
@rm -f png2ff ff2png ${OBJ}
install: all
@echo installing executable files to ${DESTDIR}${PREFIX}/bin
@mkdir -p ${DESTDIR}${PREFIX}/bin
@cp -f png2if jpg2if if2png gif2if ${DESTDIR}${PREFIX}/bin
@chmod 755 ${DESTDIR}${PREFIX}/bin/png2if
@chmod 755 ${DESTDIR}${PREFIX}/bin/jpg2if
@chmod 755 ${DESTDIR}${PREFIX}/bin/if2png
@chmod 755 ${DESTDIR}${PREFIX}/bin/gif2if
@cp -f png2ff ff2png ${DESTDIR}${PREFIX}/bin
@chmod 755 ${DESTDIR}${PREFIX}/bin/png2ff
@chmod 755 ${DESTDIR}${PREFIX}/bin/ff2png
uninstall:
@echo removing executable files from ${DESTDIR}${PREFIX}/bin
@rm -f ${DESTDIR}${PREFIX}/bin/png2if
@rm -f ${DESTDIR}${PREFIX}/bin/jpg2if
@rm -f ${DESTDIR}${PREFIX}/bin/if2png
@rm -f ${DESTDIR}${PREFIX}/bin/gif2if
@rm -f ${DESTDIR}${PREFIX}/bin/png2ff
@rm -f ${DESTDIR}${PREFIX}/bin/ff2png
.PHONY: all options clean install uninstall
The imagefile-format is meant to be parsed easily
The farbfeld-format is meant to be parsed easily
and used to pipe images losslessly.
# WHY IMAGEFILE?
# WHY FARBFELD?
Most current image-formats have their compression
incorporated in their format itself.
This has some advantages, but reaches its limits
with lossless formats (e.g. PNG).
The basic idea of the imagefile-format is to separate
The basic idea of the farbfeld-format is to separate
these to and having a completely transparent image-format.
Pattern resolution is done while compressing, not while
converting the image.
For example, imagefile always stores an alpha-channel,
For example, farbfeld always stores an alpha-channel,
even if the image doesn't have alpha-variation.
This may sound like a big waste, but as soon as you
compress an image of this kind, the bzip2-algorithm
......@@ -20,24 +20,30 @@ takes care of the easy pattern, that each 4th character
has the same value.
This leads to almost no overhead while keeping parsing
really simple.
Same applies to the idea of having 64 bits per channel.
It sounds excessive, but if you only have 8 bits of
entropy, the compression will take care of it, while
you only need to do the endian-handling for 64 bit
chunks.
# FORMAT:
Bytes Description
9 imagefile
4 32 bit BE Integer (width)
4 32 bit BE Integer (height)
[1111] RGBA-row-aligned-pixel-array
8 farbfeld
4 32 bit BE Unsigned Integer (width)
4 32 bit BE Unsigned Integer (height)
[2222] RGBA-row-aligned-pixel-array
16 bit BE Unsigned Integers (per channel)
# EXAMPLES:
encoding:
png2if < example.png > example.if
png2if < example.png | bzip2 > example.if.bz2
png2ff < example.png > example.ff
png2ff < example.png | bzip2 > example.ff.bz2
decoding:
if2png < example.if > example.png
bzcat example.if.bz2 | if2png > example.png
ff2png < example.ff > example.png
bzcat example.ff.bz2 | ff2png > example.png
# WHY BZ2?
......@@ -47,4 +53,4 @@ For normal pictures, the bz2-compression yields roughly
the same sizes as png does.
Always keep in mind that using PNG involves having to
rely on libpng to decode the image for you, whereas
imagefile is a completely transparent format.
farbfeld is a completely transparent format.
o handle truecolor PNG's properly (libpng is painful to use)
o write simpler Makefile
o re-add the old imagefile-tools for gif, jpg, ...
......@@ -16,7 +16,7 @@ CPPFLAGS =
#CFLAGS = -std=c99 -pedantic -Wall -Wextra -O0 -g -ggdb ${CPPFLAGS}
#LDFLAGS = ${LIBS}
# optimized
CFLAGS = -std=c99 -pedantic -Wall -Wextra -Os ${CPPFLAGS}
CFLAGS = -std=c99 -pedantic -Wall -Wextra -D_DEFAULT_SOURCE -Os ${CPPFLAGS}
LDFLAGS = -s ${LIBS}
# compiler and linker
......
/* See LICENSE file for copyright and license details. */
#include <arpa/inet.h>
#include <errno.h>
#include <endian.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <png.h>
#include "arg.h"
char *argv0;
#define HEADER_FORMAT "imagefile########"
#define HEADER_FORMAT "farbfeld########"
static void
usage(void)
{
fprintf(stderr, "usage: %s\n", argv0);
exit(EXIT_FAILURE);
exit(1);
}
int
......@@ -26,28 +25,29 @@ main(int argc, char *argv[])
png_structp png_struct_p;
png_infop png_info_p;
uint8_t hdr[17], *png_row;
uint16_t tmp16;
png_uint_32 width, height, i;
png_size_t png_row_len;
png_size_t png_row_len, j;
ARGBEGIN {
default:
usage();
} ARGEND;
if (argc != 0)
if (argc)
usage();
/* header */
if (fread(hdr, 1, strlen(HEADER_FORMAT), stdin) != strlen(HEADER_FORMAT)) {
fprintf(stderr, "failed to read from stdin or input too short\n");
return EXIT_FAILURE;
return 1;
}
if (memcmp("imagefile", hdr, 9)) {
if (memcmp("farbfeld", hdr, strlen("farbfeld"))) {
fprintf(stderr, "invalid magic in header\n");
return EXIT_FAILURE;
return 1;
}
width = ntohl((hdr[9] << 0) | (hdr[10] << 8) | (hdr[11] << 16) | (hdr[12] << 24));
height = ntohl((hdr[13] << 0) | (hdr[14] << 8) | (hdr[15] << 16) | (hdr[16] << 24));
width = be32toh((hdr[9] << 0) | (hdr[10] << 8) | (hdr[11] << 16) | (hdr[12] << 24));
height = be32toh((hdr[13] << 0) | (hdr[14] << 8) | (hdr[15] << 16) | (hdr[16] << 24));
/* load png */
png_struct_p = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
......@@ -55,7 +55,7 @@ main(int argc, char *argv[])
if (!png_struct_p || !png_info_p || setjmp(png_jmpbuf(png_struct_p))) {
fprintf(stderr, "failed to initialize libpng\n");
return EXIT_FAILURE;
return 1;
}
png_init_io(png_struct_p, stdout);
png_set_IHDR(png_struct_p, png_info_p, width, height, 8, PNG_COLOR_TYPE_RGB_ALPHA,
......@@ -67,13 +67,16 @@ main(int argc, char *argv[])
png_row = malloc(png_row_len);
if (!png_row) {
fprintf(stderr, "failed to allocate row-buffer\n");
return EXIT_FAILURE;
return 1;
}
for (i = 0; i < height; ++i) {
if (fread(png_row, 1, png_row_len, stdin) != png_row_len) {
fprintf(stderr, "unexpected EOF or row-skew at %lu\n", (unsigned long)i);
return EXIT_FAILURE;
for (j = 0; j < png_row_len; ++j) {
if (fread(&tmp16, 1, sizeof(uint16_t), stdin) != sizeof(uint16_t)) {
fprintf(stderr, "unexpected EOF or row-skew\n");
return 1;
}
png_row[j] = be16toh(tmp16) / (1 << 8);
}
png_write_row(png_struct_p, png_row);
}
......@@ -83,5 +86,5 @@ main(int argc, char *argv[])
png_free_data(png_struct_p, png_info_p, PNG_FREE_ALL, -1);
png_destroy_write_struct(&png_struct_p, NULL);
free(png_row);
return EXIT_SUCCESS;
return 0;
}
/* See LICENSE file for copyright and license details.
* code borrowed from util/gif2rgb.c from giflib-5.0 */
#include <arpa/inet.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <gif_lib.h>
#include "arg.h"
char *argv0;
static void
usage(void)
{
fprintf(stderr, "usage: %s\n", argv0);
exit(EXIT_FAILURE);
}
static void
die(const char *s) {
fputs(s, stderr);
exit(EXIT_FAILURE);
}
void *
emalloc(size_t size) {
void *p;
if(!(p = malloc(size)))
die("can't malloc\n");
return p;
}
int
main(int argc, char *argv[])
{
GifRecordType recordtype;
GifFileType *giffile;
GifRowType * gifrows;
GifByteType *extension;
GifColorType *colormapentry;
ColorMapObject *colormap;
uint32_t width, height, rwidth, rheight, val_be;
uint32_t i, j, k;
size_t gif_row_len;
uint8_t *if_row;
int extcode, row, col, imagenum = 0;
int interlacedoffset[] = { 0, 4, 2, 1 };
int interlacedjumps[] = { 8, 8, 4, 2 };
ARGBEGIN {
default:
usage();
} ARGEND;
if (argc != 0)
usage();
/* load gif */
#if GIFLIB_MAJOR >= 5
if ((giffile = DGifOpenFileHandle(0, NULL)) == NULL)
die("gif error\n");
#else
if ((giffile = DGifOpenFileHandle(0)) == NULL)
die("gif error\n");
#endif
width = (uint32_t)giffile->SWidth;
height = (uint32_t)giffile->SHeight;
gif_row_len = giffile->SWidth * sizeof(GifPixelType);
gifrows = emalloc(giffile->SHeight * sizeof(GifRowType));
gifrows[0] = emalloc(gif_row_len);
/* set color to BackGround. */
for (i = 0; i < width; i++)
gifrows[0][i] = giffile->SBackGroundColor;
for(i = 1; i < height; i++) {
gifrows[i] = emalloc(gif_row_len);
memcpy(gifrows[i], gifrows[0], gif_row_len);
}
/* scan the content of the GIF file and load the image(s) in: */
do {
if (DGifGetRecordType(giffile, &recordtype) == GIF_ERROR)
die("gif error\n");
switch (recordtype) {
case IMAGE_DESC_RECORD_TYPE:
if (DGifGetImageDesc(giffile) == GIF_ERROR)
die("gif error\n");
/* image position relative to Screen. */
row = giffile->Image.Top;
col = giffile->Image.Left;
rwidth = giffile->Image.Width;
rheight = giffile->Image.Height;
if (giffile->Image.Left + giffile->Image.Width > giffile->SWidth ||
giffile->Image.Top + giffile->Image.Height > giffile->SHeight) {
fprintf(stderr, "Image %d is not confined to screen dimension, aborted.\n", imagenum);
exit(EXIT_FAILURE);
}
if (giffile->Image.Interlace) {
/* need to perform 4 passes on the images: */
for (i = 0; i < 4; i++) {
for (j = row + interlacedoffset[i]; j < row + rheight; j += interlacedjumps[i]) {
if (DGifGetLine(giffile, &gifrows[j][col], rwidth) == GIF_ERROR)
die("gif error\n");
}
}
} else {
for (i = 0; i < rheight; i++) {
if (DGifGetLine(giffile, &gifrows[row++][col], rwidth) == GIF_ERROR)
die("gif error\n");
}
}
/* this is set to disallow multiple images */
recordtype = TERMINATE_RECORD_TYPE;
imagenum++;
break;
case EXTENSION_RECORD_TYPE:
/* Skip any extension blocks in file: */
if (DGifGetExtension(giffile, &extcode, &extension) == GIF_ERROR)
die("gif error\n");
while (extension != NULL) {
if (DGifGetExtensionNext(giffile, &extension) == GIF_ERROR)
die("gif error\n");
}
break;
case TERMINATE_RECORD_TYPE:
break;
default:
break;
}
} while (recordtype != TERMINATE_RECORD_TYPE);
if(giffile->Image.ColorMap)
colormap = giffile->Image.ColorMap;
else
colormap = giffile->SColorMap;
if(colormap == NULL)
die("Gif Image does not have a colormap\n");
/* write header with big endian width and height-values */
fprintf(stdout, "imagefile");
val_be = htonl(width);
fwrite(&val_be, sizeof(uint32_t), 1, stdout);
val_be = htonl(height);
fwrite(&val_be, sizeof(uint32_t), 1, stdout);
gif_row_len = width * strlen("RGBA");
if_row = emalloc(gif_row_len);
/* write data */
for (i = 0; i < height; i++) {
for(j = 0, k = 0; j < width; j++, k += 4) {
colormapentry = &colormap->Colors[gifrows[i][j]];
if_row[k] = colormapentry->Red;
if_row[k+1] = colormapentry->Green;
if_row[k+2] = colormapentry->Blue;
if_row[k+3] = 255; /* TODO: mask? */
}
if (fwrite(if_row, 1, gif_row_len, stdout) != gif_row_len)
die("fwrite() failed\n");
}
/* cleanup */
for(i = 0; i < height; i++)
free(gifrows[i]);
free(gifrows);
free(if_row);
return EXIT_SUCCESS;
}
/* See LICENSE file for copyright and license details. */
#include <arpa/inet.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
#include <jpeglib.h>
#include "arg.h"
char *argv0;
static jmp_buf setjmp_buffer;
static void
usage(void)
{
fprintf(stderr, "usage: %s\n", argv0);
exit(EXIT_FAILURE);
}
METHODDEF(void)
if_jpeg_error(j_common_ptr cinfo)
{
/* Always display the message. */
/* We could postpone this until after returning, if we chose. */
(*cinfo->err->output_message) (cinfo);
/* Return control to the setjmp point */
longjmp(setjmp_buffer, 1);
}
int
main(int argc, char *argv[])
{
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
uint32_t width, height, val_be;
uint8_t *if_row = NULL;
size_t jpeg_row_len, if_row_len, i, dx, sx;
int status = EXIT_FAILURE;
JSAMPARRAY buffer; /* output row buffer */
ARGBEGIN {
default:
usage();
} ARGEND;
if (argc != 0)
usage();
/* load jpeg */
cinfo.err = jpeg_std_error(&jerr);
jerr.error_exit = if_jpeg_error;
/* Establish the setjmp return context for my_error_exit to use. */
if (setjmp(setjmp_buffer)) {
/* If we get here, the JPEG code has signaled an error.
* We need to clean up the JPEG object, close the input file, and return. */
goto cleanup;
}
jpeg_create_decompress(&cinfo);
jpeg_stdio_src(&cinfo, stdin);
jpeg_read_header(&cinfo, TRUE);
width = cinfo.image_width;
height = cinfo.image_height;
/* change output for imagefile */
cinfo.output_components = 3; /* # of color components per pixel */
cinfo.out_color_space = JCS_RGB; /* colorspace of input image */
jpeg_start_decompress(&cinfo);
jpeg_row_len = width * cinfo.output_components;
/* Make a one-row-high sample array that will go away when done with image */
buffer = (*cinfo.mem->alloc_sarray)
((j_common_ptr) &cinfo, JPOOL_IMAGE, jpeg_row_len, 1);
if_row_len = strlen("RGBA") * width;
if(!(if_row = malloc(if_row_len))) {
fprintf(stderr, "Can't malloc\n");
return EXIT_FAILURE;
}
/* write header with big endian width and height-values */
fprintf(stdout, "imagefile");
val_be = htonl(width);
fwrite(&val_be, sizeof(uint32_t), 1, stdout);
val_be = htonl(height);
fwrite(&val_be, sizeof(uint32_t), 1, stdout);
while (cinfo.output_scanline < cinfo.output_height) {
/* jpeg_read_scanlines expects an array of pointers to scanlines.
* Here the array is only one element long, but you could ask for
* more than one scanline at a time if that's more convenient. */
(void)jpeg_read_scanlines(&cinfo, buffer, 1);
for(i = 0, dx = 0, sx = 0; i < width; i++, sx += 3, dx += 4) {
if_row[dx] = buffer[0][sx];
if_row[dx+1] = buffer[0][sx+1];
if_row[dx+2] = buffer[0][sx+2];
if_row[dx+3] = 255;
}
/* write data */
if (fwrite(if_row, 1, if_row_len, stdout) != if_row_len) {
fprintf(stderr, "fwrite() failed\n");
goto cleanup;
}
}
jpeg_finish_decompress(&cinfo);
status = EXIT_SUCCESS;
cleanup:
free(if_row);
jpeg_destroy_decompress(&cinfo);
return status;
}
/* See LICENSE file for copyright and license details. */
#include <arpa/inet.h>
#include <errno.h>
#include <endian.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <png.h>
#include "arg.h"
char *argv0;
......@@ -14,8 +12,8 @@ char *argv0;
static void
usage(void)
{
fprintf(stderr, "usage: %s\n", argv0);
exit(EXIT_FAILURE);
fprintf(stderr, "usage:%s\n", argv0);
exit(1);
}
int
......@@ -24,54 +22,54 @@ main(int argc, char *argv[])
png_structp png_struct_p;
png_infop png_info_p;
png_bytepp png_row_p;
png_uint_32 width, height, i;
png_size_t png_row_len;
uint32_t val_be;
int depth, color, interlace;
uint32_t width, height, png_row_len, tmp32, r, i;
uint16_t tmp16;
ARGBEGIN {
default:
usage();
} ARGEND;
} ARGEND
if (argc != 0)
if (argc)
usage();
/* load png */
png_struct_p = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
png_struct_p = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL,
NULL, NULL);
png_info_p = png_create_info_struct(png_struct_p);
if (!png_struct_p || !png_info_p || setjmp(png_jmpbuf(png_struct_p))) {
fprintf(stderr, "failed to initialize libpng");
return EXIT_FAILURE;
fprintf(stderr, "failed to initialize libpng\n");
return 1;
}
png_init_io(png_struct_p, stdin);
png_set_add_alpha(png_struct_p, 255, PNG_FILLER_AFTER);
png_set_gray_to_rgb(png_struct_p);
png_read_png(png_struct_p, png_info_p, PNG_TRANSFORM_STRIP_16 |
PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND , NULL);
PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND, NULL);
png_get_IHDR(png_struct_p, png_info_p, &width, &height, &depth,
&color, &interlace, NULL, NULL);
&color, &interlace, NULL, NULL);
png_row_len = png_get_rowbytes(png_struct_p, png_info_p);
png_row_p = png_get_rows(png_struct_p, png_info_p);
/* write header with big endian width and height-values */
fprintf(stdout, "imagefile");
val_be = htonl(width);
fwrite(&val_be, sizeof(uint32_t), 1, stdout);
val_be = htonl(height);
fwrite(&val_be, sizeof(uint32_t), 1, stdout);
/* write header */
fprintf(stdout, "farbfeld");
tmp32 = htobe32(width);
fwrite(&tmp32, sizeof(uint32_t), 1, stdout);
tmp32 = htobe32(height);
fwrite(&tmp32, sizeof(uint32_t), 1, stdout);
/* write data */
for (i = 0; i < height; i++) {
if (fwrite(png_row_p[i], 1, png_row_len, stdout) != png_row_len) {
fprintf(stderr, "fwrite() failed\n");
return EXIT_FAILURE;
/* TODO: allow 16 bit PNGs to be converted losslessly */
for (r = 0; r < height; ++r) {
for (i = 0; i < png_row_len; i++) {
tmp16 = htobe16((uint16_t)png_row_p[r][i]);
fwrite(&tmp16, sizeof(uint16_t), 1, stdout);
}
}
/* clean up */
png_free_data(png_struct_p, png_info_p, PNG_FREE_ALL, -1);
/* cleanup */
png_destroy_read_struct(&png_struct_p, &png_info_p, NULL);
return EXIT_SUCCESS;
return 0;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment