[gegl] Add a new negative-darkroom operation
- From: Øyvind "pippin" Kolås <ok src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gegl] Add a new negative-darkroom operation
- Date: Wed, 13 Jan 2021 18:50:51 +0000 (UTC)
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]