[gegl] Add a new negative-darkroom operation



commit 59e8663d5ae433559be5d4ea543f188a6c2d9f88
Author: JonnyRobbie <marcodv seznam cz>
Date:   Sun Jan 10 12:17:31 2021 +0100

    Add a new negative-darkroom operation
    
    This operation is for artists who use hybrid workflow technique of
    analog photography. After scanning a developed negative, this operation
    is used to invert the scan to create a positive image by simulating
    the light behaviour of darkroom enlarger and common photographic papers.

 operations/common/meson.build                      |   3 +-
 operations/common/negative-darkroom.c              | 318 +++++++++++++++++++++
 .../negative-darkroom-curve-enum.c                 |   7 +
 .../negative-darkroom-curve-enum.h                 |  87 ++++++
 po/POTFILES.in                                     |   1 +
 5 files changed, 415 insertions(+), 1 deletion(-)
---
diff --git a/operations/common/meson.build b/operations/common/meson.build
index 13575b571..01977ebfa 100644
--- a/operations/common/meson.build
+++ b/operations/common/meson.build
@@ -63,6 +63,7 @@ gegl_common_sources = files(
   'mix.c',
   'mono-mixer.c',
   'motion-blur-linear.c',
+  'negative-darkroom.c',
   'newsprint.c',
   'noise-cell.c',
   'noise-cie-lch.c',
@@ -78,7 +79,7 @@ gegl_common_sources = files(
   'opacity.c',
   'open-buffer.c',
   'over.c',
-  'pack.c', 
+  'pack.c',
   'panorama-projection.c',
   'pixelize.c',
   'posterize.c',
diff --git a/operations/common/negative-darkroom.c b/operations/common/negative-darkroom.c
new file mode 100644
index 000000000..21b9218bd
--- /dev/null
+++ b/operations/common/negative-darkroom.c
@@ -0,0 +1,318 @@
+/* This file is an image processing operation for GEGL
+* foo
+* GEGL is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Lesser General Public
+* License as published by the Free Software Foundation; either
+* version 3 of the License, or (at your option) any later version.
+*
+* GEGL is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with GEGL; if not, see <https://www.gnu.org/licenses/>.
+*
+* Copyright 2015 Red Hat, Inc.
+*/
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+#include <math.h>
+#include <stdio.h>
+
+#ifdef GEGL_PROPERTIES
+
+#include "negative-darkroom/negative-darkroom-curve-enum.c"
+
+property_enum (curve, _("Characteristic curve"),
+               NegCurve, neg_curve, 0)
+       description(_("Hardcoded characteristic curve and color data"))
+
+property_double (exposure, _("Exposure"), 0.0)
+       description(_("Base enlargement exposure"))
+       value_range (-20, 10)
+
+property_double (expC, _("Filter cyan"), 0.0)
+       description(_("Cyan exposure compensation for the negative image"))
+       value_range (-2, 2)
+
+property_double (expM, _("Filter magenta"), 0.0)
+       description(_("Magenta exposure compensation for the negative image"))
+       value_range (-2, 2)
+
+property_double (expY, _("Filter yellow"), 0.0)
+       description(_("Yellow exposure compensation for the negative image"))
+       value_range (-2, 2)
+
+property_boolean (clip, _("Clip base + fog"), TRUE)
+       description (_("Clip base + fog to have a pure white output value"))
+
+property_double (boost, _("Density boost"), 1.0)
+       description(_("Boost paper density to take edvantage of increased dynamic range of a monitor compared 
to a photographic paper"))
+       value_range (1, 2)
+
+property_double (dodge, _("Dodge/burn multiplier"), 1.0)
+       description(_("The f-stop of dodge/burn for pure white/black auxillary input"))
+       value_range (-4.0, 4.0)
+
+property_boolean (preflash, _("Enable preflashing"), FALSE)
+       description (_("Show preflash controls"))
+
+property_double (flashC, _("Cyan preflash"), 0)
+       description(_("Preflash the negative with cyan light to redude contrast of the print"))
+       value_range (0, 1)
+       ui_meta("visible", "preflash")
+
+property_double (flashM, _("Magenta preflash"), 0)
+       description(_("Preflash the negative with magenta light to redude contrast of the print"))
+       value_range (0, 1)
+       ui_meta("visible", "preflash")
+
+property_double (flashY, _("Yellow preflash"), 0)
+       description(_("Preflash the negative with yellow light to redude contrast of the print"))
+       value_range (0, 1)
+       ui_meta("visible", "preflash")
+
+#else
+
+#define GEGL_OP_POINT_COMPOSER
+#define GEGL_OP_NAME     negative_darkroom
+#define GEGL_OP_C_SOURCE negative-darkroom.c
+
+#include "gegl-op.h"
+
+// Color space
+typedef struct cieXYZ {
+       gfloat X;
+       gfloat Y;
+       gfloat Z;
+} cieXYZ;
+
+// RGB Hurter–Driffield characteristic curve
+
+typedef struct HDCurve {
+       gfloat *rx;
+       gfloat *ry;
+       guint   rn;
+       gfloat *gx;
+       gfloat *gy;
+       guint   gn;
+       gfloat *bx;
+       gfloat *by;
+       guint   bn;
+       cieXYZ rsens;
+       cieXYZ gsens;
+       cieXYZ bsens;
+       cieXYZ cdens;
+       cieXYZ mdens;
+       cieXYZ ydens;
+} HDCurve;
+
+#include "negative-darkroom/negative-darkroom-curve-enum.h"
+
+static void
+prepare (GeglOperation *operation)
+{
+       const Babl *space = gegl_operation_get_source_space (operation, "input");
+       const Babl *fXYZ;
+       const Babl *fRGB;
+
+       fXYZ  = babl_format_with_space ("CIE XYZ float", space);
+       fRGB = babl_format ("R~G~B~ float");
+
+       gegl_operation_set_format (operation, "input", fXYZ);
+       gegl_operation_set_format (operation, "aux", fRGB);
+       gegl_operation_set_format (operation, "output", fXYZ);
+}
+
+static gfloat
+curve_lerp (gfloat * xs, gfloat * ys, guint n, gfloat in)
+{
+       if (in <= xs[0])
+               return(ys[0]);
+       for (guint i = 1; i <= n; i++)
+       {
+               if (in <= xs[i])
+               {
+                       return(ys[i-1] + (in - xs[i-1]) * \
+                              ((ys[i] - ys[i-1]) / (xs[i] - xs[i-1])));
+               }
+       }       
+       return(ys[n-1]);
+}
+
+static gfloat
+array_min (gfloat * x, guint n)
+{
+       gfloat min = x[0];
+       for (guint i = 1; i < n; i++)
+       {
+               if (x[i] < min)
+                       min = x[i];
+       }
+       return(min);
+}
+
+static gboolean
+process (GeglOperation       *operation,
+        void                *in_buf,
+        void                *aux_buf,
+        void                *out_buf,
+        glong                n_pixels,
+        const GeglRectangle *roi,
+        gint                 level)
+{
+       GeglProperties *o = GEGL_PROPERTIES (operation);
+
+       gfloat *in   = in_buf;
+       gfloat *aux  = aux_buf;
+       gfloat *out  = out_buf;
+
+       gfloat Dfogc = 0;
+       gfloat Dfogm = 0;
+       gfloat Dfogy = 0;
+
+       gfloat rcomp = 0;
+       gfloat gcomp = 0;
+       gfloat bcomp = 0;
+
+       gfloat r = 0;
+       gfloat g = 0;
+       gfloat b = 0;
+
+       // Calculate base+fog
+       if (o->clip)
+       {
+               Dfogc = array_min(curves[o->curve].ry, curves[o->curve].rn) * o->boost;
+               Dfogm = array_min(curves[o->curve].gy, curves[o->curve].gn) * o->boost;
+               Dfogy = array_min(curves[o->curve].by, curves[o->curve].bn) * o->boost;
+       }
+
+       for (glong i = 0; i < n_pixels; i++)
+       {
+               /*printf("---\n");*/
+               /*printf("Input XYZ intensity %f %f %f\n", in[0], in[1], in[2]);*/
+
+               // Calculate exposure compensation from global+filter+dodge
+               if (!aux)
+               {
+                       rcomp = o->exposure + o->expC;
+                       gcomp = o->exposure + o->expM;
+                       bcomp = o->exposure + o->expY;
+               }
+               else
+               {
+                       rcomp = o->exposure + o->expC + 2*o->dodge*(aux[0]-0.5);
+                       gcomp = o->exposure + o->expM + 2*o->dodge*(aux[1]-0.5);
+                       bcomp = o->exposure + o->expY + 2*o->dodge*(aux[2]-0.5);
+                       aux  += 3;
+               }
+
+               /*printf("===\nRGB compensation %f %f %f\n", rcomp, gcomp, bcomp);*/
+
+               // Calculate RGB from XYZ using paper sensitivity
+               r = in[0] * curves[o->curve].rsens.X +
+                   in[1] * curves[o->curve].gsens.X +
+                   in[2] * curves[o->curve].bsens.X;
+               g = in[0] * curves[o->curve].rsens.Y +
+                   in[1] * curves[o->curve].gsens.Y +
+                   in[2] * curves[o->curve].bsens.Y;
+               b = in[0] * curves[o->curve].rsens.Z +
+                   in[1] * curves[o->curve].gsens.Z +
+                   in[2] * curves[o->curve].bsens.Z;
+
+               /*printf("Linear RGB intensity %f %f %f\n", r, g, b);*/
+
+               // Apply the preflash
+               r = r + o->flashC;
+               g = g + o->flashM;
+               b = b + o->flashY;
+
+               /*printf("Linear RGB intensity after preflash %f %f %f\n", r, g, b);*/
+
+               // Logarithmize the input and apply compensation
+               r = log(r / pow(2, rcomp)) / log(10);
+               g = log(g / pow(2, gcomp)) / log(10);
+               b = log(b / pow(2, bcomp)) / log(10);
+
+               /*printf("Logarithmic RGB intensity %f %f %f\n", r, g, b);*/
+
+               // Apply the DH curve
+               r = curve_lerp(curves[o->curve].rx,
+                              curves[o->curve].ry,
+                              curves[o->curve].rn,
+                              r);
+               g = curve_lerp(curves[o->curve].gx,
+                              curves[o->curve].gy,
+                              curves[o->curve].gn,
+                              g);
+               b = curve_lerp(curves[o->curve].bx,
+                              curves[o->curve].by,
+                              curves[o->curve].bn,
+                              b);
+
+               // Apply density boost
+               r = r * o->boost;
+               g = g * o->boost;
+               b = b * o->boost;
+
+               // Compensate for fog
+               r -= Dfogc;
+               g -= Dfogm;
+               b -= Dfogy;
+
+               /*printf("RGB density %f %f %f\n", r, g, b);*/
+
+               // Exponentiate to get the linear representation in tramsmittance back
+               r = 1-1/pow(10, r);
+               g = 1-1/pow(10, g);
+               b = 1-1/pow(10, b);
+
+               /*printf("RGB absolute transparency %f %f %f\n", r, g, b);*/
+
+               // Calculate and return XYZ
+               out[0] = 1 - (r * curves[o->curve].cdens.X) -
+                            (g * curves[o->curve].mdens.X) -
+                            (b * curves[o->curve].ydens.X);
+               out[1] = 1 - (r * curves[o->curve].cdens.Y) -
+                            (g * curves[o->curve].mdens.Y) -
+                            (b * curves[o->curve].ydens.Y);
+               out[2] = 1 - (r * curves[o->curve].cdens.Z) -
+                            (g * curves[o->curve].mdens.Z) -
+                            (b * curves[o->curve].ydens.Z);
+
+               /*printf("XYZ output %f %f %f\n", out[0], out[1], out[2]);*/
+
+               in   += 3;
+               out  += 3;
+       }
+
+       return TRUE;
+}
+
+static void
+gegl_op_class_init (GeglOpClass *klass)
+{
+       GeglOperationClass               *operation_class;
+       GeglOperationPointComposerClass *point_composer_class;
+
+       operation_class      = GEGL_OPERATION_CLASS (klass);
+       point_composer_class = GEGL_OPERATION_POINT_COMPOSER_CLASS (klass);
+
+       operation_class->prepare = prepare;
+       operation_class->threaded = TRUE;
+       operation_class->opencl_support = FALSE;
+
+       point_composer_class->process = process;
+
+       gegl_operation_class_set_keys (operation_class,
+               "name"       , "gegl:negative-darkroom",
+               "title",       _("Negative Darkroom"),
+               "categories" , "color",
+               "description", _("Simulate a negative film enlargement in an analog darkroom."),
+               NULL);
+}
+
+#endif
+
diff --git a/operations/common/negative-darkroom/negative-darkroom-curve-enum.c 
b/operations/common/negative-darkroom/negative-darkroom-curve-enum.c
new file mode 100644
index 000000000..db425f6c1
--- /dev/null
+++ b/operations/common/negative-darkroom/negative-darkroom-curve-enum.c
@@ -0,0 +1,7 @@
+enum_start(neg_curve)
+       enum_value (NEG_FUJI, "fujicrystal", N_("Fujicolor Crystal Archive Digital Pearl Paper"))
+       enum_value (NEG_ILFOBROM1, "ilfobrom1", N_("Ilford Ilfobrom Galerie FB 1"))
+       enum_value (NEG_ILFOBROM2, "ilfobrom2", N_("Ilford Ilfobrom Galerie FB 2"))
+       enum_value (NEG_ILFOBROM3, "ilfobrom3", N_("Ilford Ilfobrom Galerie FB 3"))
+       enum_value (NEG_ILFOBROM4, "ilfobrom4", N_("Ilford Ilfobrom Galerie FB 4"))
+enum_end (NegCurve)
diff --git a/operations/common/negative-darkroom/negative-darkroom-curve-enum.h 
b/operations/common/negative-darkroom/negative-darkroom-curve-enum.h
new file mode 100644
index 000000000..e36651ae2
--- /dev/null
+++ b/operations/common/negative-darkroom/negative-darkroom-curve-enum.h
@@ -0,0 +1,87 @@
+static HDCurve curves[5] = {
+{
+.rx = (gfloat[]){0.944085027726432, 1.0854898336414, 1.26016635859519, 1.38909426987061, 1.49722735674677, 
1.59288354898336, 1.67606284658041, 1.73844731977819, 1.83410351201479, 1.94639556377079, 2.10027726432532, 
2.15850277264325, 2.22088724584103, 2.28327171903882, 2.33733826247689, 2.39556377079482, 2.46210720887246, 
2.55360443622921, 2.64510166358595, 2.76571164510166, 2.85720887245841, 2.95702402957486, 3.04436229205176},
+.ry = (gfloat[]){0.103950103950104, 0.103950103950104, 0.116424116424116, 0.141372141372141, 
0.182952182952183, 0.249480249480249, 0.328482328482329, 0.415800415800416, 0.611226611226611, 
0.906444906444906, 1.38877338877339, 1.55509355509356, 1.72972972972973, 1.88357588357588, 2.02079002079002, 
2.13721413721414, 2.23700623700624, 2.36590436590437, 2.45738045738046, 2.54885654885655, 2.6029106029106, 
2.64449064449064, 2.66528066528067},
+.rn = 23,
+.gx = (gfloat[]){0.944085027726432, 1.0854898336414, 1.26016635859519, 1.38909426987061, 1.49722735674677, 
1.59288354898336, 1.67606284658041, 1.73844731977819, 1.83410351201479, 1.96719038817006, 2.10027726432532, 
2.18761552680222, 2.25, 2.31654343807763, 2.38724584103512, 2.47042513863216, 2.6409426987061, 
2.76155268022181, 2.93207024029575, 3.04852125693161},
+.gy = (gfloat[]){0.103950103950104, 0.103950103950104, 0.116424116424116, 0.141372141372141, 
0.182952182952183, 0.249480249480249, 0.328482328482329, 0.415800415800416, 0.611226611226611, 
0.981288981288981, 1.43451143451143, 1.74220374220374, 1.91683991683992, 2.05821205821206, 2.1954261954262, 
2.2952182952183, 2.42827442827443, 2.48232848232848, 2.51975051975052, 2.53222453222453},
+.gn = 20,
+.bx = (gfloat[]){0.944085027726432, 1.1728280961183, 1.29343807763401, 1.4681146025878, 1.54297597042514, 
1.6677449168207, 1.7634011090573, 1.83826247689464, 1.93391866913124, 2.04205175600739, 2.13770794824399, 
2.20841035120148, 2.28327171903882, 2.33733826247689, 2.43715341959335, 2.49537892791128, 2.62014787430684, 
2.73243992606285, 2.81977818853974, 2.91127541589649, 3.05683918669131},
+.by = (gfloat[]){0.062370062370063, 0.070686070686071, 0.087318087318087, 0.128898128898129, 
0.17047817047817, 0.291060291060291, 0.436590436590437, 0.602910602910603, 0.860706860706861, 
1.21829521829522, 1.57172557172557, 1.8045738045738, 1.995841995842, 2.1039501039501, 2.24116424116424, 
2.30769230769231, 2.39085239085239, 2.43243243243243, 2.45322245322245, 2.46569646569647, 2.47817047817048},
+.bn = 21,
+.rsens = {1.5489263994697402, -0.7601427199734186, -0.13159136565865906},
+.gsens = {-0.6578318104480959, 1.365755119785612, 0.2388452773956612},
+.bsens = {-0.31414778017785455, 0.4138731583264843, 0.8534088408788981},
+.cdens = {0.5817794731231579, 0.3075301664892215, 0.007804784733728515},
+.mdens = {0.25642580250752084, 0.6574414690762187, 0.04462033446059675},
+.ydens = {0.1617947243693214, 0.03502836443455983, 0.9475748808056746}
+},
+{
+.rx = (gfloat[]){1, 5},
+.ry = (gfloat[]){0, 0},
+.rn = 2,
+.gx = (gfloat[]){1, 5},
+.gy = (gfloat[]){0, 0},
+.gn = 2,
+.bx = (gfloat[]){1.493581907, 1.576100244, 1.661369193, 1.754889976, 1.878667482, 1.994193154, 2.159229829, 
2.269254279, 2.503056235, 2.640586797, 2.758863081, 2.849633252, 2.9349022, 3.006418093, 3.099938875, 
3.215464548, 3.319987775, 3.50702934},
+.by = (gfloat[]){0.008241758, 0.041208791, 0.085164835, 0.151098901, 0.263736264, 0.431318681, 0.695054945, 
0.865384615, 1.263736264, 1.519230769, 1.728021978, 1.876373626, 2.010989011, 2.087912088, 2.151098901, 
2.195054945, 2.217032967, 2.21978022},
+.bn = 18,
+.rsens = {0.797668, 0.28804, 0.0},
+.gsens = {0.135193, 0.711884, 0.0},
+.bsens = {0.031342, 9.2e-05, 0.824905},
+.cdens = {0, 0, 0},
+.mdens = {0, 0, 0},
+.ydens = {1, 1, 1}
+},
+{
+.rx = (gfloat[]){1, 5},
+.ry = (gfloat[]){0, 0},
+.rn = 2,
+.gx = (gfloat[]){1, 5},
+.gy = (gfloat[]){0, 0},
+.gn = 2,
+.bx = (gfloat[]){1.644865526, 1.686124694, 1.752139364, 1.848410758, 1.958435208, 2.068459658, 2.186735941, 
2.305012225, 2.42603912, 2.519559902, 2.588325183, 2.676344743, 2.723105134, 2.786369193, 2.830378973, 
2.910146699, 3.009168704, 3.143948655, 3.325488998},
+.by = (gfloat[]){0.008241758, 0.021978022, 0.057692308, 0.131868132, 0.269230769, 0.461538462, 0.736263736, 
1.010989011, 1.282967033, 1.497252747, 1.64010989, 1.821428571, 1.906593407, 2.0, 2.052197802, 2.10989011, 
2.151098901, 2.195054945, 2.217032967},
+.bn = 19,
+.rsens = {0.797668, 0.28804, 0.0},
+.gsens = {0.135193, 0.711884, 0.0},
+.bsens = {0.031342, 9.2e-05, 0.824905},
+.cdens = {0, 0, 0},
+.mdens = {0, 0, 0},
+.ydens = {1, 1, 1}
+},
+{
+.rx = (gfloat[]){1, 5},
+.ry = (gfloat[]){0, 0},
+.rn = 2,
+.gx = (gfloat[]){1, 5},
+.gy = (gfloat[]){0, 0},
+.gn = 2,
+.bx = (gfloat[]){1.754889976, 1.851161369, 1.936430318, 1.999694377, 2.054706601, 2.197738386, 2.329767726, 
2.439792176, 2.533312958, 2.62408313, 2.670843521, 2.756112469, 2.813875306, 2.890892421, 3.006418093, 
3.075183374, 3.182457213, 3.33099022},
+.by = (gfloat[]){0.008241758, 0.071428571, 0.148351648, 0.233516484, 0.357142857, 0.760989011, 1.142857143, 
1.442307692, 1.678571429, 1.854395604, 1.936813187, 2.027472527, 2.074175824, 2.118131868, 2.156593407, 
2.178571429, 2.200549451, 2.217032967},
+.bn = 18,
+.rsens = {0.797668, 0.28804, 0.0},
+.gsens = {0.135193, 0.711884, 0.0},
+.bsens = {0.031342, 9.2e-05, 0.824905},
+.cdens = {0, 0, 0},
+.mdens = {0, 0, 0},
+.ydens = {1, 1, 1}
+},
+{
+.rx = (gfloat[]){1, 5},
+.ry = (gfloat[]){0, 0},
+.rn = 2,
+.gx = (gfloat[]){1, 5},
+.gy = (gfloat[]){0, 0},
+.gn = 2,
+.bx = (gfloat[]){1.807151589, 1.873166259, 1.908924205, 1.952933985, 1.996943765, 2.035452323, 2.090464548, 
2.134474328, 2.18398533, 2.236246944, 2.277506112, 2.307762836, 2.362775061, 2.395782396, 2.42603912, 
2.486552567, 2.527811736, 2.604828851, 2.676344743, 2.734107579, 2.866136919, 3.006418093, 3.165953545},
+.by = (gfloat[]){0.002747253, 0.013736264, 0.043956044, 0.096153846, 0.167582418, 0.244505495, 0.414835165, 
0.546703297, 0.733516484, 0.901098901, 1.071428571, 1.195054945, 1.412087912, 1.546703297, 1.656593407, 
1.818681319, 1.906593407, 2.013736264, 2.085164835, 2.123626374, 2.175824176, 2.208791209, 2.222527473},
+.bn = 23,
+.rsens = {0.797668, 0.28804, 0.0},
+.gsens = {0.135193, 0.711884, 0.0},
+.bsens = {0.031342, 9.2e-05, 0.824905},
+.cdens = {0, 0, 0},
+.mdens = {0, 0, 0},
+.ydens = {1, 1, 1}
+}
+};
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 986916ff4..5f375f695 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -76,6 +76,7 @@ operations/common/mirrors.c
 operations/common/mix.c
 operations/common/mono-mixer.c
 operations/common/motion-blur-linear.c
+operations/common/negative-darkroom.c
 operations/common/newsprint.c
 operations/common/noise-cell.c
 operations/common/noise-cie-lch.c


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]