diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8b68ae4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.o +*.gpl diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f927c48 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +CC := gcc +CFLAGS_ANSI = -ansi -pedantic +CFLAGS := -Wall -Wextra -O2 +LDFLAGS := +LIBS := -lm + +all: aseconv + +aseconv: ase.c lab.c gimp.c + $(CC) $(CFLAGS) $(CFLAGS_ANSI) $? -o $@ $(LDFLAGS) $(LIBS) + +clean: aseconv + rm -f $? + +.PHONY: clean diff --git a/ase.c b/ase.c new file mode 100644 index 0000000..5126451 --- /dev/null +++ b/ase.c @@ -0,0 +1,200 @@ +#include +#include +#include +#include +#include +#include + +#include "color.h" +#include "readbytes.h" +#include "gimp.h" + +float bytes_to_float(const unsigned char *bytes); +unsigned int bytes_to_rgb(const unsigned char *bytes); +struct rgb from_cmyk(const unsigned char *bytes); + +int main(int argc, const char *argv[]) +{ + char *p; + int count; + FILE *out, *in; + float f1, f2, f3; + char filename[512]; + unsigned int r, g, b; + struct rgb rgb_colors; + unsigned char buf[1024]; + unsigned short chunk_size; + const char *ase = "ASEF", *rgb = "RGB ", *cmyk = "CMYK", *lab = "LAB ", *gray = "Gray"; + + if(argc != 2) + { + printf("usage: %s \n", argv[0]); + return 1; + } + + strcpy(filename, argv[1]); + p = strrchr(filename, '.'); + + if(!p || p[1] == '\0' || p[2] == '\0' || p[3] == '\0') + { + printf("usage: %s \n", argv[0]); + return 1; + } + + p[1] = 'g'; + p[2] = 'p'; + p[3] = 'l'; + p[4] = '\0'; + + if(!(in = fopen(argv[1], "rb"))) + { + printf("%s: %s\n", argv[0], strerror(errno)); + return 1; + } + + if(!(out = fopen(filename, "wb"))) + { + printf("%s: %s\n", argv[0], strerror(errno)); + return 1; + } + + /* read header, version and chunk count */ + fread(buf, 1, 12, in); + + if(memcmp(ase, buf, 4)) + { + puts("not an ASE file"); + exit(1); + } + + /* TODO: read ASE version, currently at buf 4-7 */ +#if 0 + printf("%x\n", buf[8]); printf("%x\n", buf[9]); printf("%x\n", buf[10]); printf("%x\n", buf[11]); +#endif + + count = read_uint32_be(&buf[8]); + printf("chunk count: %d\n", count); + + /* CHUNK types: */ + /* palette name = 0xC0010000 */ + /* mystery = 0xC0020000 */ + /* swatch = 0x00010000 */ + + gimp_emit_header(out); + + while(count) + { + if(fread(buf, 1, 6, in) != 6) + { + puts("ERROR: truncated chunk"); + break; + } + + /* palette name chunk, no valuable data to be found, skip */ + if(!memcmp(&buf[0], "\xC0\x01", 2)) + { + chunk_size = read_uint16_be(&buf[4]); + fseek(in, chunk_size, SEEK_CUR); + } + /* swatch chunk */ + else if(!memcmp(&buf[0], "\x00\x01", 2)) + { + chunk_size = read_uint16_be(&buf[4]); + fread(buf, 1, chunk_size, in); + + if(!memcmp(rgb, &buf[chunk_size - 18], 4)) + { + r = bytes_to_float(&buf[chunk_size - 14]) * 255; + g = bytes_to_float(&buf[chunk_size - 10]) * 255; + b = bytes_to_float(&buf[chunk_size - 6]) * 255; + } + else if(!memcmp(cmyk, &buf[chunk_size - 22], 4)) + { + rgb_colors = from_cmyk(&buf[chunk_size - 18]); + r = rgb_colors.r; + g = rgb_colors.g; + b = rgb_colors.b; + } + else if(!memcmp(gray, &buf[chunk_size - 10], 4)) + { + r = bytes_to_float(&buf[chunk_size - 6]) * 255; + g = r; + b = r; + } + else if(!memcmp(lab, &buf[chunk_size - 18], 4)) + { + /* approximate conversion */ + f1 = bytes_to_float(&buf[chunk_size - 14]); + f2 = bytes_to_float(&buf[chunk_size - 10]); + f3 = bytes_to_float(&buf[chunk_size - 6]); + rgb_colors = from_lab(f1 * 100, f2, f3); + r = rgb_colors.r; + g = rgb_colors.g; + b = rgb_colors.b; + } + else + { + puts("FIXME: unsupported color sequences"); + count--; + continue; + } + + gimp_emit_line(out, r, g, b); + } + /* I don't even know */ + else if(!memcmp(&buf[0], "\xC0\x02", 2)) + { + /* these mystery chunks appear to only ever be empty */ + /* do nothing... */ + } + else + { + /* end of the line */ + break; + } + count--; + } + + if(count != 0) + printf("BUG: %d chunks remain\n", count); + + return 0; +} + +float bytes_to_float(const unsigned char *bytes) +{ + unsigned int u; + float f; + u = read_uint32_be(bytes); + memcpy(&f, &u, 4); + return f; +} + +struct rgb from_cmyk(const unsigned char *bytes) +{ + struct rgb color; + float f1, f2, f3, f4; + uint32_t u1, u2, u3, u4; + + u1 = read_uint32_be(&bytes[0]); + u2 = read_uint32_be(&bytes[4]); + u3 = read_uint32_be(&bytes[8]); + u4 = read_uint32_be(&bytes[12]); + + memcpy(&f1, &u1, 4); + memcpy(&f2, &u2, 4); + memcpy(&f3, &u3, 4); + memcpy(&f4, &u4, 4); + +#if 0 + color.r = (uint32_t)round(255 * (1 - f1) * (1 - f4)); + color.g = (uint32_t)round(255 * (1 - f2) * (1 - f4)); + color.b = (uint32_t)round(255 * (1 - f3) * (1 - f4)); +#else + color.r = (uint32_t)floor((255 * (1 - f1) * (1 - f4)) + 0.5); + color.g = (uint32_t)floor((255 * (1 - f2) * (1 - f4)) + 0.5); + color.b = (uint32_t)floor((255 * (1 - f3) * (1 - f4)) + 0.5); +#endif + + return color; +} diff --git a/color.h b/color.h new file mode 100644 index 0000000..843231c --- /dev/null +++ b/color.h @@ -0,0 +1,12 @@ +#ifndef COLOR_H +#define COLOR_H + +struct rgb +{ + unsigned int r, g, b; +}; + +struct rgb from_hsv(float hue, float saturation, float value); +struct rgb from_lab(float l, float a, float b); + +#endif diff --git a/gimp.c b/gimp.c new file mode 100644 index 0000000..41b3cb9 --- /dev/null +++ b/gimp.c @@ -0,0 +1,12 @@ +#include + +void gimp_emit_header(FILE *file) +{ + fprintf(file, "GIMP Palette\nName: RGB palette\nColumns: 10\n"); +} + +void gimp_emit_line(FILE *file, unsigned int r, unsigned int g, unsigned int b) +{ + fprintf(file, "%u%c%u%c%u%c%c%02x%02x%02x%c", r, 0x09, g, 0x09, b, 0x09, '#', r, g, b, 0x0a); + printf("%u%c%u%c%u%c%c%02x%02x%02x%c", r, 0x09, g, 0x09, b, 0x09, '#', r, g, b, 0x0a); +} diff --git a/gimp.h b/gimp.h new file mode 100644 index 0000000..a312e15 --- /dev/null +++ b/gimp.h @@ -0,0 +1,7 @@ +#ifndef GIMP_H +#define GIMP_H + +void gimp_emit_header(FILE *file); +void gimp_emit_line(FILE *file, unsigned int r, unsigned int g, unsigned int b); + +#endif diff --git a/lab.c b/lab.c new file mode 100644 index 0000000..72d76ed --- /dev/null +++ b/lab.c @@ -0,0 +1,83 @@ +#include + +#include "color.h" + +struct rgb from_lab(float l, float a, float b) +{ + struct rgb color; + float z, y, x, fr, fg, fb; + + /* L*a*b* -> XYZ */ + + y = (l + 16) / 116; + x = a / 500 + y; + z = y - b / 200; + + if(x * x * x > 0.008856) + x = x * x * x; + else + x = (x - 16 / 116) / 7.787; + + if(y * y * y > 0.008856) + y = y * y * y; + else + y = (y - 16 / 116) / 7.787; + + if(z * z * z > 0.008856) + z = z * z * z; + else + z = (z - 16 / 116) / 7.787; + + x = x * 95.047; + y = y * 100.000; + z = z * 108.883; + + /* XYZ -> RGB */ + + x = x / 100; + y = y / 100; + z = z / 100; + + fr = x * 3.2406 + y * -1.5372 + z * -0.4986; + fg = x * -0.9689 + y * 1.8758 + z * 0.0415; + fb = x * 0.0557 + y * -0.2040 + z * 1.0570; + + if(fr > 0.0031308) + fr = 1.055 * pow(fr, 1 / 2.4) - 0.055; + else + fr = 12.92 * fr; + if(fg > 0.0031308) + fg = 1.055 * pow(fg, 1 / 2.4) - 0.055; + else + fg = 12.92 * fg; + if(fb > 0.0031308) + fb = 1.055 * pow(fb, 1 / 2.4) - 0.055; + else + fb = 12.92 * fb; + + if(fr > 0) + if(fr < 1) + color.r = fr * 255; + else + color.r = 255; + else + color.r = 0; + + if(fg > 0) + if(fg < 1) + color.g = fg * 255; + else + color.g = 255; + else + color.g = 0; + + if(fb > 0) + if(fb < 1) + color.b = fb * 255; + else + color.b = 255; + else + color.b = 0; + + return color; +} diff --git a/readbytes.h b/readbytes.h new file mode 100644 index 0000000..5ebabf4 --- /dev/null +++ b/readbytes.h @@ -0,0 +1,27 @@ +#ifndef INCLUDE_READBYTES_H +#define INCLUDE_READBYTES_H + +#include +#include + +uint32_t read_uint32_be(const unsigned char *bytes) +{ + return((bytes[3]) | (bytes[2] << 8) | (bytes[1] << 16) | (bytes[0] << 24)); +} + +uint32_t read_uint32_le(const unsigned char *bytes) +{ + return((bytes[0]) | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24)); +} + +uint16_t read_uint16_be(const unsigned char *bytes) +{ + return((bytes[0] << 8) | (bytes[1])); +} + +uint16_t read_uint16_le(const unsigned char *bytes) +{ + return((bytes[0]) | (bytes[1] << 8)); +} + +#endif