From 1f18417e0ba51e53df1a30f774423ebc054193dc Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C3=98yvind=20Kol=C3=A5s?= Date: Mon, 22 Jul 2019 03:22:11 +0200 Subject: [PATCH] babl-space: add universal convertors between linear and nonlinear Register conversions for all space pairs between "R'G'B'A float" and "RGBA float" and vice versa, both SSE2 and non SIMD versions. --- babl/babl-space.c | 84 ++++++++++++++++++++ babl/babl-space.h | 47 ++++++++++- docs/Makefile.am | 12 ++- docs/index-static.html.in | 163 +++++--------------------------------- docs/meson.build | 45 +++++++++++ tools/babl-verify.c | 85 ++++++++++++++++++-- 6 files changed, 283 insertions(+), 153 deletions(-) diff --git a/babl/babl-space.c b/babl/babl-space.c index 943da85..67e8535 100644 --- a/babl/babl-space.c +++ b/babl/babl-space.c @@ -728,6 +728,23 @@ universal_nonlinear_rgb_linear_converter (const Babl *conversion, babl_matrix_mul_vectorff_buf4 (matrixf, rgba_out, rgba_out, samples); } +static inline void +universal_linear_rgb_nonlinear_converter (const Babl *conversion, + unsigned char *src_char, + unsigned char *dst_char, + long samples, + void *data) +{ + const Babl *destination_space = conversion->conversion.destination->format.space; + float * matrixf = data; + float *rgba_in = (void*)src_char; + float *rgba_out = (void*)dst_char; + + babl_matrix_mul_vectorff_buf4 (matrixf, rgba_in, rgba_out, samples); + + TRC_OUT(rgba_out, rgba_out); +} + static inline void universal_nonlinear_rgba_u8_converter (const Babl *conversion, unsigned char *src_char, @@ -1007,6 +1024,24 @@ universal_nonlinear_rgb_linear_converter_sse2 (const Babl *conversion, babl_matrix_mul_vectorff_buf4_sse2 (matrixf, rgba_out, rgba_out, samples); } + + +static inline void +universal_linear_rgb_nonlinear_converter_sse2 (const Babl *conversion, + unsigned char *src_char, + unsigned char *dst_char, + long samples, + void *data) +{ + const Babl *destination_space = conversion->conversion.destination->format.space; + float * matrixf = data; + float *rgba_in = (void*)src_char; + float *rgba_out = (void*)dst_char; + + babl_matrix_mul_vectorff_buf4_sse2 (matrixf, rgba_in, rgba_out, samples); + + TRC_OUT(rgba_out, rgba_out); +} #endif @@ -1021,6 +1056,8 @@ add_rgb_adapter (Babl *babl, if ((babl_cpu_accel_get_support () & BABL_CPU_ACCEL_X86_SSE) && (babl_cpu_accel_get_support () & BABL_CPU_ACCEL_X86_SSE2)) { + + prep_conversion(babl_conversion_new( babl_format_with_space("RGBA float", space), babl_format_with_space("RGBA float", babl), @@ -1041,6 +1078,29 @@ add_rgb_adapter (Babl *babl, babl_format_with_space("R'G'B'A float", space), "linear", universal_nonlinear_rgba_converter_sse2, NULL)); + + prep_conversion(babl_conversion_new( + babl_format_with_space("R'G'B'A float", space), + babl_format_with_space("RGBA float", babl), + "linear", universal_nonlinear_rgb_linear_converter_sse2, + NULL)); + prep_conversion(babl_conversion_new( + babl_format_with_space("R'G'B'A float", babl), + babl_format_with_space("RGBA float", space), + "linear", universal_nonlinear_rgb_linear_converter_sse2, + NULL)); + + prep_conversion(babl_conversion_new( + babl_format_with_space("RGBA float", babl), + babl_format_with_space("R'G'B'A float", space), + "linear", universal_linear_rgb_nonlinear_converter_sse2, + NULL)); + prep_conversion(babl_conversion_new( + babl_format_with_space("RGBA float", space), + babl_format_with_space("R'G'B'A float", babl), + "linear", universal_linear_rgb_nonlinear_converter_sse2, + NULL)); + prep_conversion(babl_conversion_new( babl_format_with_space("R'G'B'A u8", space), babl_format_with_space("R'G'B'A u8", babl), @@ -1076,6 +1136,7 @@ add_rgb_adapter (Babl *babl, babl_format_with_space("RGBA float", space), "linear", universal_rgba_converter, NULL)); + prep_conversion(babl_conversion_new( babl_format_with_space("R'G'B'A float", space), babl_format_with_space("R'G'B'A float", babl), @@ -1086,6 +1147,18 @@ add_rgb_adapter (Babl *babl, babl_format_with_space("R'G'B'A float", space), "linear", universal_nonlinear_rgba_converter, NULL)); + + prep_conversion(babl_conversion_new( + babl_format_with_space("R'G'B'A float", space), + babl_format_with_space("RGBA float", babl), + "linear", universal_nonlinear_rgb_linear_converter_sse2, + NULL)); + prep_conversion(babl_conversion_new( + babl_format_with_space("R'G'B'A float", babl), + babl_format_with_space("RGBA float", space), + "linear", universal_nonlinear_rgb_linear_converter_sse2, + NULL)); + prep_conversion(babl_conversion_new( babl_format_with_space("R'G'B'A u8", space), babl_format_with_space("R'G'B'A u8", babl), @@ -1107,6 +1180,17 @@ add_rgb_adapter (Babl *babl, babl_format_with_space("R'G'B' u8", space), "linear", universal_nonlinear_rgb_u8_converter, NULL)); + + prep_conversion(babl_conversion_new( + babl_format_with_space("RGBA float", babl), + babl_format_with_space("R'G'B'A float", space), + "linear", universal_linear_rgb_nonlinear_converter, + NULL)); + prep_conversion(babl_conversion_new( + babl_format_with_space("RGBA float", space), + babl_format_with_space("R'G'B'A float", babl), + "linear", universal_linear_rgb_nonlinear_converter, + NULL)); } prep_conversion(babl_conversion_new( diff --git a/babl/babl-space.h b/babl/babl-space.h index bbf0c42..4c97cdb 100644 --- a/babl/babl-space.h +++ b/babl/babl-space.h @@ -41,6 +41,52 @@ typedef struct #endif } BablCMYK; +typedef struct _BablSpectrumType BablSpectrumType; + +struct _BablSpectrumType { + double nm_start; + double nm_gap; + double nm_end; /* last band, computed */ + int bands; +}; + +typedef struct +{ + BablSpectrumType spectrum_type; + int is_spectral; + float *observer_x; + float *observer_y; + float *observer_z; + float *illuminant; + float rev_y_scale; +} BablSpectralSpace; + +typedef struct +{ + BablSpectralSpace *spectral_space; + int inks; + float *on_white; + float *on_black; + float *opaqueness; + float scale; + float trc_gamma; + float *illuminant; +} BablCoat; + +#define BABL_MAX_COATS 16 + +typedef struct +{ + BablSpectralSpace *spectral_space; + BablCoat coat_def[BABL_MAX_COATS]; + int coats; + float *substrate; + + int stochastic_iterations; + float stochastic_diffusion0; + float stochastic_diffusion1; +} BablProcessSpace; + typedef struct { BablInstance instance; @@ -76,7 +122,6 @@ typedef struct */ char *icc_profile; int icc_length; - BablCMYK cmyk; } BablSpace; diff --git a/docs/Makefile.am b/docs/Makefile.am index 07d8305..6c33676 100644 --- a/docs/Makefile.am +++ b/docs/Makefile.am @@ -20,11 +20,15 @@ EXTRA_DIST= \ tools/xml_insert.sh \ tools/changelog2rss \ index-static.html.in \ + ColorManagement.html \ + CMYK.html \ + SymmetricAlpha.html \ COPYING \ - COPYING.LESSER \ + toc \ + COPYING.LESSER \ meson.build -BUILT_EXTRA_DIST = index.html +BUILT_EXTRA_DIST = index.html ColorManagement.html CMYK.html SymmetricAlpha.html CLEANFILES = README changelog.rss DISTCLEANFILES = index-static.html $(BUILT_EXTRA_DIST) @@ -36,6 +40,7 @@ index.html: index-static.html \ $(top_srcdir)/AUTHORS \ $(top_srcdir)/TODO \ $(top_srcdir)/NEWS \ + toc \ Makefile.am echo -n "HTML: $@" cp $< $@ @@ -44,10 +49,11 @@ index.html: index-static.html \ $(SHELL) $(top_srcdir)/docs/tools/xml_insert.sh $@ BablBase $$TMPFILE;\ rm -f $$TMPFILE ) echo -n "." - + $(SHELL) $(top_srcdir)/docs/build-docs.sh $(top_srcdir) $(top_builddir)/docs $(SHELL) $(top_srcdir)/docs/tools/xml_insert.sh $@ AUTHORS $(top_srcdir)/AUTHORS $(SHELL) $(top_srcdir)/docs/tools/xml_insert.sh $@ TODO $(top_srcdir)/TODO $(SHELL) $(top_srcdir)/docs/tools/xml_insert.sh $@ NEWS $(top_srcdir)/NEWS + $(SHELL) $(top_srcdir)/docs/tools/xml_insert.sh $@ TOC $(top_srcdir)/docs/toc echo " [OK]" distclean-local: diff --git a/docs/index-static.html.in b/docs/index-static.html.in index 9f4d228..5ffe554 100644 --- a/docs/index-static.html.in +++ b/docs/index-static.html.in @@ -63,34 +63,8 @@

Babl-@BABL_VERSION@

+ -
-
-

Contents

-
- -
@@ -115,25 +89,24 @@

Features

-

GEGL through GeglBuffer provides - tiled buffers with on disk storage as well as linear buffers with accessor - functions for efficient data access transparently using babl fishes for - translation to the desired pixel formats.

+

The pixel data storage in GIMP uses +GEGL's GeglBuffer which internally stores +tiles and provides an API for retrieving and storing pixel data with implicit +conversions using babl formats. +

Download

@@ -156,114 +129,16 @@ href='#CMYK'>CMYK profiles. to the GEGL release.

- For more news see git log. - - - -

Color Management

- -

All pixel formats in babl have a specified color space, if NULL is passed -as a space constants for (unbounded) linear sRGB data is assumed, data being -sRGB defines the conversion to and from gray-scale as well as the gamma - - or Transfer Response Curve, TRC, used for converting between linear and - non-linear variants of the data.

- -

babl has API for creating a format for a specific space: -babl_format_with_space("R'G'B' u16", babl_space ("Rec2020")) creates a -16 bit integer format for the Rec2020 color space. Babl knows internally about -"sRGB", "Rec2020", "Adobe", "Apple", "ProPhoto", "ACEScg" and "ACES2065-1" -spaces, as they are defined with constants on their wikipedia pages or similar upstream references.

- -

Additional spaces can be loaded from monitor-class matrix+TRC ICC v2 and - v4 profiles. Using babl_icc_make_space (see babl.h for details). The space of - a babl format can also be queried with babl_format_get_space. -

- - -

Symmetric transformations for floating point alpha

- + For more detailed changes see git log. -

babl clamps the alpha used when going from separate alpha to associated -alpha or from associated alpha to separate alpha to BABL_ALPHA_FLOOR. This -replaces asymptotic behavior and direct precision loss of color precision when -multiplying or dividing by alphas near 0.0 with a consistent symmetric -transformation.

- -

Original intent of data as well as non-asymptotic precision loss is thus -maintained when the processing chain might temporarily use the other alpha -representation.

- -
-    #define BABL_ALPHA_FLOOR    (1.0/65536.0)
-    #define BABL_ALPHA_FLOOR_F  (1.0f/65536.0f)
-
- -

The deviation from not clamping near 0.0 is within the quantization margin -of 16bit integer alpha, thus no adaptations for any SIMD or and similar 8bit -and 16bit extensions of pixel format conversions are needed. -

- -

This is the clamping function in use:

-
-static inline float
-babl_epsilon_for_zero_float (float value)
-{
- if (value <= BABL_ALPHA_FLOOR_F)
- {
-   /* for performance one could directly retun BABL_ALPHA_FLOOR_F here
-      and dropping handling negative values consistently. */
-   if (value >= 0.0f)
-     return BABL_ALPHA_FLOOR_F;
-   else if (value >= -BABL_ALPHA_FLOOR_F)
-     return -BABL_ALPHA_FLOOR_F;
- }
- return value;  /* most common case, return input value */
-}
-
-

And an example use of this clamping function that is consistent with babls behavior:

-
-static inline void
-associated_to_separate_rgba (const float *associated_rgba,
-                                   float *separate_rgba)
-{
-  float alpha = associated_rgba[3];
-  float clamped_alpha = babl_epsilon_for_zero_float (alpha);
-  float reciprocal_alpha = 1.0f / clamped_alpha;
-
-  separate_rgba[0] = associated_rgba[0] * reciprocal_alpha;
-  separate_rgba[1] = associated_rgba[1] * reciprocal_alpha;
-  separate_rgba[2] = associated_rgba[2] * reciprocal_alpha;
-  separate_rgba[3] = alpha;
-}
-
- - -

For more detils see the commit message of the most recent refinement as well as blog post with further background.

- - - -

CMYK

- -

CMYK handling is done using babl-spaces created with ICC profiles -containing CMYK profiles. BablSpaces for these ICC profiles handle color conversions using lcms2 - or if compiled without lcms2 support a naive profile independent fallback.

-

When a babl space derived from a CMYK ICC profile is used to instantiate -RGB formats, the resulting formats are using the default/NULL space for -primaries and TRCs.

- -

The CMYK formats that use lcms2 for color interchange with the rest of -babl are the following, which are available for all data types, u8, u16, half -and float:

-
-
CMYK
Cyan Magenta Yellow Key, with 0 being white and 1.0 full ink coverage.
-
CMYKA
as previous, with separate alpha channel
-
CaMaYaKaA
as previous but associated alpha
-
cmyk
inverted CMYK, where 0.0 is full ink coverage and 1.0 is none
-
cmykA
as previous, with separate alpha channel
-
camayakaA
as previous but associated alpha
-

Usage

+

Most users of babl do not know they are using babl and it is GIMP itself + which uses babl, this is documentation for such uses - and others that might + want to use babl for pixel format or color space conversion in other software. +

+

When using BablFishes to do your conversions, you request a fish to convert between two formats, and an optimal fish to babls capability is provided that you can use to do your conversions. Babl also provides diff --git a/docs/meson.build b/docs/meson.build index b63e767..0f46713 100644 --- a/docs/meson.build +++ b/docs/meson.build @@ -41,6 +41,7 @@ index_html = custom_target('index.html', join_paths(meson.source_root(), 'AUTHORS'), join_paths(meson.source_root(), 'TODO'), join_paths(meson.source_root(), 'NEWS'), + 'toc', ], output: [ 'index.html', ], command: [ @@ -50,6 +51,50 @@ index_html = custom_target('index.html', '&&', xml_insert, '@OUTPUT@', 'AUTHORS', '@INPUT2@', '&&', xml_insert, '@OUTPUT@', 'TODO', '@INPUT3@', '&&', xml_insert, '@OUTPUT@', 'NEWS', '@INPUT4@', + '&&', xml_insert, '@OUTPUT@', 'toc', '@INPUT5@', + ], + build_by_default: true, +) + + +CMYK_html = custom_target('CMYK.html', + input : [ + 'CMYK-static.html', + 'toc', + ], + output: [ 'CMYK.html', ], + command: [ + env_bin, + 'cp', '@INPUT0@', '@OUTPUT@', + '&&', xml_insert, '@OUTPUT@', 'toc', '@INPUT1@', + ], + build_by_default: true, +) + +ColorManagement_html = custom_target('ColorManagement.html', + input : [ + 'ColorManagement-static.html', + 'toc', + ], + output: [ 'ColorManagement.html', ], + command: [ + env_bin, + 'cp', '@INPUT0@', '@OUTPUT@', + '&&', xml_insert, '@OUTPUT@', 'toc', '@INPUT1@', + ], + build_by_default: true, +) + +SymmetricAlpha_html = custom_target('SymmetricAlpha.html', + input : [ + 'SymmetricAlpha-static.html', + 'toc', + ], + output: [ 'SymmetricAlpha.html', ], + command: [ + env_bin, + 'cp', '@INPUT0@', '@OUTPUT@', + '&&', xml_insert, '@OUTPUT@', 'toc', '@INPUT1@', ], build_by_default: true, ) diff --git a/tools/babl-verify.c b/tools/babl-verify.c index 8ac6b35..de3bbd1 100644 --- a/tools/babl-verify.c +++ b/tools/babl-verify.c @@ -3,12 +3,67 @@ #include "../config.h" #include "babl/babl-internal.h" +//#define SPACE1 babl_space("ProPhoto") +#define SPACE1 babl_space("Apple") +//#define SPACE1 babl_space("sRGB") +//#define SPACE2 babl_space("Apple") + +static int +file_get_contents (const char *path, + char **contents, + long *length, + void *error) +{ + FILE *file; + long size; + char *buffer; + + file = fopen (path,"rb"); + + if (!file) + return -1; + + if (fseek (file, 0, SEEK_END) == -1 || (size = ftell (file)) == -1) + { + fclose (file); + return -1; + } + if (length) *length = size; + rewind (file); + if ((size_t) size > SIZE_MAX - 8) + { + fclose (file); + return -1; + } + buffer = calloc(size + 8, 1); + + if (!buffer) + { + fclose(file); + return -1; + } + + size -= fread (buffer, 1, size, file); + if (size) + { + fclose (file); + free (buffer); + return -1; + } + fclose (file); + *contents = buffer; + return 0; +} + int main (int argc, char **argv) { int final = 0; const Babl *fish; + const Babl *SPACE2 = NULL; + + if (argc < 3) { fprintf (stderr, "need two args, from and to babl-formats\n"); @@ -25,22 +80,42 @@ main (int argc, babl_init (); - fish = babl_fish (babl_format(argv[1]), babl_format (argv[2])); +#define ICC_PATH "/tmp/my.icc" +//#define ICC_PATH "/usr/share/color/icc/colord/AppleRGB.icc" +//#define ICC_PATH "/tmp/ACEScg-elle-V2-labl.icc" +//#define ICC_PATH "/tmp/ACEScg-elle-V2-g10.icc" +//#define ICC_PATH "/tmp/ACEScg-elle-V4-g10.icc" +//#define ICC_PATH "/tmp/ACEScg-elle-V4-g22.icc" + + + { + char *icc_data = NULL; + long length = 0; + file_get_contents (ICC_PATH, &icc_data, &length, NULL); + SPACE2 = babl_space_from_icc (icc_data, length, BABL_ICC_INTENT_RELATIVE_COLORIMETRIC, NULL); + //SPACE2 = babl_space ("sRGB"); + } + + fish = babl_fish (babl_format_with_space(argv[1], SPACE1), babl_format_with_space (argv[2], SPACE2)); if (!fish) + { + fprintf (stderr, "!!!! %s %s\n", argv[1], argv[2]); return -1; + } if (final) switch (fish->class_type) { case BABL_FISH: - fprintf (stderr, "%s\n", babl_get_name (fish)); + fprintf (stderr, ">%s\n", babl_get_name (fish)); break; case BABL_FISH_PATH: - fprintf (stderr, "chosen %s to %s: steps: %i error: %f cost: %f\n", argv[1], argv[2], fish->fish_path.conversion_list->count, fish->fish.error, fish->fish_path.cost); + fprintf (stderr, "chosen %s to %s: steps: %i error: %.12f cost: %f\n", argv[1], argv[2], fish->fish_path.conversion_list->count, fish->fish.error, fish->fish_path.cost); for (int i = 0; i < fish->fish_path.conversion_list->count; i++) { - fprintf (stderr, "\t%s\n", - babl_get_name(fish->fish_path.conversion_list->items[i] )); + fprintf (stderr, "\t%s (cost: %li)\n", + babl_get_name(fish->fish_path.conversion_list->items[i] ), + fish->fish_path.conversion_list->items[i]->conversion.cost); } break; } -- 2.30.2