[hackers] [farbfeld] Mandate "ProPhoto RGB" color space for farbfeld and handle ICC profiles || FRIGN

From: <git_AT_suckless.org>
Date: Sun, 17 Jan 2016 02:12:53 +0100 (CET)

commit 3bc6c69ae8ff25377848b76a25494aa4a0055544
Author: FRIGN <dev_AT_frign.de>
AuthorDate: Sun Jan 17 01:39:36 2016 +0100
Commit: FRIGN <dev_AT_frign.de>
CommitDate: Sun Jan 17 02:11:54 2016 +0100

    Mandate "ProPhoto RGB" color space for farbfeld and handle ICC profiles
    
    I've literally been thinking about this for quite a while now. The
    initial motivation behind defaulting to sRGB was the idea that most data
    on the web was in sRGB anyway.
    However, my assumption was weakened in the sense that the development
    is clearly moving towards images with supplied ICC profiles and software
    slowly catching up to handle this. My tests have shown that more and
    more people even do that on the web, even though it's been a "tradition"
    that Photoshop users "Save for Web" and convert the gamut lossy into
    sRGB to bring a consistent color-"experience" even to those clients not
    supporting ICC profiles and which always assume sRGB.
    
    What made this decision so difficult is that converting to "ProPhoto
    RGB" requires some advanced knowledge on this topic, however, I came to
    the conclusion to implement it given the *2ff- and ff2*-tools handle it
    silently and well in the background, and given the little cms library is
    actually quite okay to use.
    When converting from ff to png, a proper "Pro Photo RGB" ICC V4-profile is
    automatically included in the exported png by ff2png. V4 is not as
    widespread as V2, but in the worst case (no V4 support somewhere) the
    colors will just be a little off.
    
    As an added bonus, any input files for png2ff which include ICC profiles
    are also correctly handled and the color space conversions are executed
    as expected.
    
    Accordingly, the FORMAT-specification has been changed. While at it,
    I added the note on alpha-multiplication. Now the format is very exact
    and shouldn't leave any ambiguities.
    
    jpeg supports ICC profiles as well, but I hadn't had the chance to look
    into it (not as trivial as PNG probably, help appreciated :)), so I'm
    always assuming sRGB here.
    
    Rationale
    ---------
    It is not obvious why one would go through the lenghts of supporting
    this big-gamut colorspace and not just stick with sRGB. In 99% of the
    cases, there's no reason to do that, but with even more extreme
    developments in the OLED-sector and other advances display hardware is
    slowly getting more powerful than sRGB, asking for color information
    which is suitable for the task and actually uses the full potential.
    The decision in this regard was not difficult in farbfeld because we
    always use 16-Bit anyway and won't have to fear posterization in a big-
    gamut color-space.

diff --git a/FORMAT b/FORMAT
index 5966df8..6821ab2 100644
--- a/FORMAT
+++ b/FORMAT
_AT_@ -1,14 +1,15 @@
 
   FARBFELD IMAGE FORMAT SPECIFICATION
 
- +--------+-----------------------------------------------------------+
- | Bytes | Description |
- +--------+-----------------------------------------------------------+
- | 8 | "farbfeld" magic value |
- +--------+-----------------------------------------------------------+
- | 4 | 32-Bit BE unsigned integer (width) |
- +--------+-----------------------------------------------------------+
- | 4 | 32-Bit BE unsigned integer (height) |
- +--------+-----------------------------------------------------------+
- | [2222] | 4*16-Bit BE unsigned integers [RGBA] / pixel, row-aligned |
- +--------+-----------------------------------------------------------+
+ +--------+-------------------------------------------------------+
+ | Bytes | Description |
+ +--------+-------------------------------------------------------+
+ | 8 | "farbfeld" magic value |
+ +--------+-------------------------------------------------------+
+ | 4 | 32-Bit BE unsigned integer (width) |
+ +--------+-------------------------------------------------------+
+ | 4 | 32-Bit BE unsigned integer (height) |
+ +--------+-------------------------------------------------------+
+ | [2222] | 4*16-Bit BE unsigned integers [RGBA] / pixel |
+ | | pixels in rows, ProPhoto RGB, not alpha-premultiplied |
+ +--------+-------------------------------------------------------+
diff --git a/config.mk b/config.mk
index 37ad05f..55264b0 100644
--- a/config.mk
+++ b/config.mk
_AT_@ -13,8 +13,11 @@ PNGINC = /usr/local/include
 JPGLIB = /usr/local/lib
 JPGINC = /usr/local/include
 
-INCS = -I${PNGINC} -I${JPGINC}
-LIBS = -L${PNGLIB} -L${JPGLIB} -lpng -ljpeg
+LCMSLIB = /usr/local/lib
+LCMSINC = /usr/local/include
+
+INCS = -I${PNGINC} -I${JPGINC} -I${LCMSINC}
+LIBS = -L${PNGLIB} -L${JPGLIB} -L${LCMSLIB} -lpng -ljpeg -llcms2
 
 # flags
 CPPFLAGS = -D_DEFAULT_SOURCE
diff --git a/farbfeld.5 b/farbfeld.5
index 996c532..92cdec0 100644
--- a/farbfeld.5
+++ b/farbfeld.5
_AT_@ -15,11 +15,9 @@ BYTES DESCRIPTION
 8 "farbfeld" magic value
 4 32-Bit BE unsigned integer (width)
 4 32-Bit BE unsigned integer (height)
-[2222] 4*16-Bit BE unsigned integers [RGBA] / pixel, row-aligned
+[2222] 4*16-Bit BE unsigned integers [RGBA] / pixel
+ pixels in rows, ProPhoto RGB, not alpha-premultiplied
 .Ed
-.sp
-The RGB-data should be sRGB for best interoperability and not
-alpha-premultiplied.
 .Sh USAGE
 .Nm
 provides
diff --git a/ff2png.c b/ff2png.c
index a1276d7..94a6b95 100644
--- a/ff2png.c
+++ b/ff2png.c
_AT_@ -7,12 +7,21 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include <lcms2.h>
 #include <png.h>
 
 #define HEADER "farbfeld########"
 
 static char *argv0;
 
+/* ProPhoto RGB */
+static cmsCIExyYTRIPLE primaries = {
+ /* x, y, Y */
+ { 0.7347, 0.2653, 0.288040 }, /* red */
+ { 0.1596, 0.8404, 0.711874 }, /* green */
+ { 0.0366, 0.0001, 0.000086 }, /* blue */
+};
+
 void
 pngerr(png_structp png_struct_p, png_const_charp msg)
 {
_AT_@ -23,12 +32,17 @@ pngerr(png_structp png_struct_p, png_const_charp msg)
 int
 main(int argc, char *argv[])
 {
+ cmsContext icc_context;
+ cmsHPROFILE out_profile;
+ cmsMLU *mlu1, *mlu2;
+ cmsToneCurve *gamma18, *out_curves[3];
         png_structp png_struct_p;
         png_infop png_info_p;
         png_size_t png_row_len, j;
         png_uint_32 width, height, i;
+ uint32_t icclen;
         uint16_t tmp16, *png_row;
- uint8_t hdr[16];
+ uint8_t hdr[16], *icc;
 
         argv0 = argv[0], argc--, argv++;
 
_AT_@ -49,6 +63,32 @@ main(int argc, char *argv[])
         width = ntohl(*((uint32_t *)(hdr + 8)));
         height = ntohl(*((uint32_t *)(hdr + 12)));
 
+ /* icc profile (ProPhoto RGB) */
+ if (!(icc_context = cmsCreateContext(NULL, NULL)))
+ goto lcmserr;
+ if (!(gamma18 = cmsBuildGamma(icc_context, 1.8)))
+ goto lcmserr;
+ out_curves[0] = out_curves[1] = out_curves[2] = gamma18;
+ if (!(out_profile = cmsCreateRGBProfileTHR(icc_context, cmsD50_xyY(),
+ &primaries, out_curves)))
+ goto lcmserr;
+ cmsSetHeaderFlags(out_profile, cmsEmbeddedProfileTrue | cmsUseAnywhere);
+ cmsSetHeaderRenderingIntent(out_profile, INTENT_RELATIVE_COLORIMETRIC);
+ cmsSetDeviceClass(out_profile, cmsSigColorSpaceClass);
+ if (!(mlu1 = cmsMLUalloc(NULL, 1)) || !(mlu2 = cmsMLUalloc(NULL, 1)))
+ goto lcmserr;
+ cmsMLUsetASCII(mlu1, "en", "US", "Public Domain");
+ cmsWriteTag(out_profile, cmsSigCopyrightTag, mlu1);
+ cmsMLUsetASCII(mlu2, "en", "US", "ProPhoto RGB");
+ cmsWriteTag(out_profile, cmsSigProfileDescriptionTag, mlu2);
+ cmsWriteTag(out_profile, cmsSigDeviceModelDescTag, mlu2);
+ cmsSaveProfileToMem(out_profile, NULL, &icclen);
+ if (!(icc = malloc(icclen))) {
+ fprintf(stderr, "%s: malloc: out of memory\n", argv0);
+ return 1;
+ }
+ cmsSaveProfileToMem(out_profile, icc, &icclen);
+
         /* load png */
         png_struct_p = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL,
                                                pngerr, NULL);
_AT_@ -62,6 +102,7 @@ main(int argc, char *argv[])
         png_set_IHDR(png_struct_p, png_info_p, width, height, 16,
                      PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
                      PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
+ png_set_iCCP(png_struct_p, png_info_p, "ProPhoto RGB", 0, icc, icclen);
         png_write_info(png_struct_p, png_info_p);
 
         /* write rows */
_AT_@ -84,4 +125,8 @@ main(int argc, char *argv[])
         png_destroy_write_struct(&png_struct_p, NULL);
 
         return 0;
+lcmserr:
+ fprintf(stderr, "%s: lcms error\n", argv0);
+
+ return 1;
 }
diff --git a/jpg2ff.c b/jpg2ff.c
index eaed74b..aec6dd0 100644
--- a/jpg2ff.c
+++ b/jpg2ff.c
_AT_@ -8,9 +8,19 @@
 #include <string.h>
 
 #include <jpeglib.h>
+#include <lcms2.h>
 
 static char *argv0;
 
+/* ProPhoto RGB */
+static cmsCIExyYTRIPLE primaries = {
+ /* x, y, Y */
+ { 0.7347, 0.2653, 0.288040 }, /* red */
+ { 0.1596, 0.8404, 0.711874 }, /* green */
+ { 0.0366, 0.0001, 0.000086 }, /* blue */
+};
+
+
 METHODDEF(void)
 jpeg_error(j_common_ptr cinfo)
 {
_AT_@ -22,6 +32,9 @@ jpeg_error(j_common_ptr cinfo)
 int
 main(int argc, char *argv[])
 {
+ cmsHPROFILE in_profile, out_profile;
+ cmsHTRANSFORM transform;
+ cmsToneCurve *gamma18, *out_curves[3];
         struct jpeg_decompress_struct cinfo;
         struct jpeg_error_mgr jerr;
         uint32_t width, height, val_be;
_AT_@ -57,12 +70,26 @@ main(int argc, char *argv[])
         /* create output buffers */
         buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo,
                                             JPOOL_IMAGE, jpeg_row_len, 1);
- ff_row_len = strlen("RGBA") * sizeof(uint16_t) * width;
- if(!(ff_row = malloc(ff_row_len))) {
+ ff_row_len = strlen("RGBA") * width;
+ if(!(ff_row = malloc(ff_row_len * sizeof(uint16_t)))) {
                 fprintf(stderr, "%s: malloc: out of memory\n", argv0);
                 return 1;
         }
 
+ /* icc profile (output ProPhoto RGB) */
+ if (!(in_profile = cmsCreate_sRGBProfile()))
+ goto lcmserr;
+ if (!(gamma18 = cmsBuildGamma(NULL, 1.8)))
+ goto lcmserr;
+ out_curves[0] = out_curves[1] = out_curves[2] = gamma18;
+ if (!(out_profile = cmsCreateRGBProfile(cmsD50_xyY(), &primaries,
+ out_curves)))
+ goto lcmserr;
+ if (!(transform = cmsCreateTransform(in_profile, TYPE_RGBA_16,
+ out_profile, TYPE_RGBA_16,
+ INTENT_RELATIVE_COLORIMETRIC, 0)))
+ goto lcmserr;
+
         /* write header */
         fprintf(stdout, "farbfeld");
         val_be = htonl(width);
_AT_@ -78,15 +105,25 @@ main(int argc, char *argv[])
                  * more than one scanline at a time if that's more convenient. */
                 jpeg_read_scanlines(&cinfo, buffer, 1);
 
- for(i = 0, dx = 0, sx = 0; i < width; i++, sx += 3, dx += 4) {
- ff_row[dx] = htons(buffer[0][sx] * 257);
- ff_row[dx+1] = htons(buffer[0][sx+1] * 257);
- ff_row[dx+2] = htons(buffer[0][sx+2] * 257);
- ff_row[dx+3] = htons(65535);
+ for (i = 0, dx = 0, sx = 0; i < width; i++, sx += 3, dx += 4) {
+ ff_row[dx] = buffer[0][sx] * 257;
+ ff_row[dx+1] = buffer[0][sx+1] * 257;
+ ff_row[dx+2] = buffer[0][sx+2] * 257;
+ ff_row[dx+3] = 65535;
+ }
+
+ cmsDoTransform(transform, ff_row, ff_row, ff_row_len);
+
+ for (i = 0; i < ff_row_len; i++) {
+ /* re-add alpha */
+ if (i >= 3 && (i - 3) % 4 == 0)
+ ff_row[i] = 65535;
+ /* swap endianness to BE */
+ ff_row[i] = htons(ff_row[i]);
                 }
 
                 /* write data */
- if (fwrite(ff_row, 1, ff_row_len, stdout) != ff_row_len)
+ if (fwrite(ff_row, 2, ff_row_len, stdout) != ff_row_len)
                         goto writerr;
         }
         jpeg_finish_decompress(&cinfo);
_AT_@ -96,5 +133,10 @@ main(int argc, char *argv[])
 writerr:
         fprintf(stderr, "%s: fwrite: ", argv0);
         perror(NULL);
+
+ return 1;
+lcmserr:
+ fprintf(stderr, "%s: lcms error\n", argv0);
+
         return 1;
 }
diff --git a/png2ff.c b/png2ff.c
index 4963e86..aea7a50 100644
--- a/png2ff.c
+++ b/png2ff.c
_AT_@ -5,11 +5,21 @@
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 
+#include <lcms2.h>
 #include <png.h>
 
 static char *argv0;
 
+/* ProPhoto RGB */
+static cmsCIExyYTRIPLE primaries = {
+ /* x, y, Y */
+ { 0.7347, 0.2653, 0.288040 }, /* red */
+ { 0.1596, 0.8404, 0.711874 }, /* green */
+ { 0.0366, 0.0001, 0.000086 }, /* blue */
+};
+
 void
 pngerr(png_structp png_struct_p, png_const_charp msg)
 {
_AT_@ -20,11 +30,16 @@ pngerr(png_structp png_struct_p, png_const_charp msg)
 int
 main(int argc, char *argv[])
 {
+ cmsHPROFILE in_profile, out_profile;
+ cmsHTRANSFORM transform;
+ cmsToneCurve *gamma18, *out_curves[3];
         png_structp png_struct_p;
         png_infop png_info_p;
- png_bytepp png_row_p;
- uint32_t width, height, png_row_len, tmp32, r, i;
- uint16_t tmp16;
+ png_bytepp png_row_p, icc_data = NULL;
+ png_charpp icc_name = NULL;
+ uint32_t width, height, icc_len, outrowlen, tmp32, r, i;
+ uint16_t *inrow, *outrow;
+ int icc_compression;
 
         argv0 = argv[0], argc--, argv++;
 
_AT_@ -53,9 +68,33 @@ main(int argc, char *argv[])
                      PNG_TRANSFORM_EXPAND, NULL);
         width = png_get_image_width(png_struct_p, png_info_p);
         height = png_get_image_height(png_struct_p, png_info_p);
- png_row_len = png_get_rowbytes(png_struct_p, png_info_p);
         png_row_p = png_get_rows(png_struct_p, png_info_p);
 
+ /* icc profile (output ProPhoto RGB) */
+ if (png_get_valid(png_struct_p, png_info_p, PNG_INFO_iCCP)) {
+ png_get_iCCP(png_struct_p, png_info_p, icc_name,
+ &icc_compression, icc_data, &icc_len);
+ if (!(in_profile = cmsOpenProfileFromMem(icc_data,
+ icc_len)))
+ goto lcmserr;
+ } else {
+ if (!(in_profile = cmsCreate_sRGBProfile()))
+ goto lcmserr;
+ }
+ if (!(gamma18 = cmsBuildGamma(NULL, 1.8)))
+ goto lcmserr;
+ out_curves[0] = out_curves[1] = out_curves[2] = gamma18;
+ if (!(out_profile = cmsCreateRGBProfile(cmsD50_xyY(), &primaries,
+ out_curves)))
+ goto lcmserr;
+
+ /* allocate row buffer */
+ outrowlen = width * strlen("RGBA");
+ if (!(outrow = malloc(outrowlen * sizeof(uint16_t)))) {
+ fprintf(stderr, "%s: malloc: out of memory\n", argv0);
+ return 1;
+ }
+
         /* write header */
         fputs("farbfeld", stdout);
         tmp32 = htonl(width);
_AT_@ -68,25 +107,46 @@ main(int argc, char *argv[])
         /* write data */
         switch(png_get_bit_depth(png_struct_p, png_info_p)) {
         case 8:
+ if (!(transform = cmsCreateTransform(in_profile,
+ TYPE_RGBA_8, out_profile, TYPE_RGBA_16,
+ INTENT_RELATIVE_COLORIMETRIC, 0)))
+ goto lcmserr;
                 for (r = 0; r < height; ++r) {
- for (i = 0; i < png_row_len; i++) {
- /* ((2^16-1) / 255) == 257 */
- tmp16 = htons(257 * png_row_p[r][i]);
- if (fwrite(&tmp16, sizeof(uint16_t), 1,
- stdout) != 1)
- goto writerr;
+ cmsDoTransform(transform, png_row_p[r], outrow, width);
+ for (i = 0; i < outrowlen; i++) {
+ /* re-add alpha */
+ if (i >= 3 && (i - 3) % 4 == 0)
+ outrow[i] = 257 * png_row_p[r][i];
+ /* swap endiannes to BE */
+ outrow[i] = htons(outrow[i]);
                         }
+ if (fwrite(outrow, sizeof(uint16_t), outrowlen,
+ stdout) != outrowlen)
+ goto writerr;
                 }
                 break;
         case 16:
+ if (!(transform = cmsCreateTransform(in_profile,
+ TYPE_RGBA_16, out_profile, TYPE_RGBA_16,
+ INTENT_RELATIVE_COLORIMETRIC, 0)))
+ goto lcmserr;
                 for (r = 0; r < height; ++r) {
- for (i = 0; i < png_row_len / 2; i++) {
- tmp16 = *((uint16_t *)(png_row_p[r] +
- 2 * i));
- if (fwrite(&tmp16, sizeof(uint16_t), 1,
- stdout) != 1)
- goto writerr;
+ inrow = (uint16_t *)png_row_p[r];
+ for (i = 0; i < outrowlen; i++) {
+ /* swap endianness to LE */
+ inrow[i] = ntohs(inrow[i]);
+ }
+ cmsDoTransform(transform, png_row_p[r], outrow, width);
+ for (i = 0; i < outrowlen; ++i) {
+ /* re-add alpha */
+ if (i >= 3 && (i - 3) % 4 == 0)
+ outrow[i] = inrow[i];
+ /* swap endianness to BE */
+ outrow[i] = htons(outrow[i]);
                         }
+ if (fwrite(outrow, sizeof(uint16_t), outrowlen,
+ stdout) != outrowlen)
+ goto writerr;
                 }
                 break;
         default:
_AT_@ -102,4 +162,8 @@ writerr:
         perror(NULL);
 
         return 1;
+lcmserr:
+ fprintf(stderr, "%s: lcms error\n", argv0);
+
+ return 1;
 }
Received on Sun Jan 17 2016 - 02:12:53 CET

This archive was generated by hypermail 2.3.0 : Sun Jan 17 2016 - 02:24:13 CET