1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * gimplevelsconfig.c
5  * Copyright (C) 2007 Michael Natterer <mitch@gimp.org>
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  */
20 
21 #include "config.h"
22 
23 #include <errno.h>
24 
25 #include <cairo.h>
26 #include <gegl.h>
27 #include <gdk-pixbuf/gdk-pixbuf.h>
28 
29 #include "libgimpbase/gimpbase.h"
30 #include "libgimpcolor/gimpcolor.h"
31 #include "libgimpmath/gimpmath.h"
32 #include "libgimpconfig/gimpconfig.h"
33 
34 #include "operations-types.h"
35 
36 #include "core/gimp-utils.h"
37 #include "core/gimpcurve.h"
38 #include "core/gimphistogram.h"
39 
40 #include "gimpcurvesconfig.h"
41 #include "gimplevelsconfig.h"
42 #include "gimpoperationlevels.h"
43 
44 #include "gimp-intl.h"
45 
46 
47 enum
48 {
49   PROP_0,
50   PROP_LINEAR,
51   PROP_CHANNEL,
52   PROP_LOW_INPUT,
53   PROP_HIGH_INPUT,
54   PROP_CLAMP_INPUT,
55   PROP_GAMMA,
56   PROP_LOW_OUTPUT,
57   PROP_HIGH_OUTPUT,
58   PROP_CLAMP_OUTPUT
59 };
60 
61 
62 static void     gimp_levels_config_iface_init   (GimpConfigInterface *iface);
63 
64 static void     gimp_levels_config_get_property (GObject          *object,
65                                                  guint             property_id,
66                                                  GValue           *value,
67                                                  GParamSpec       *pspec);
68 static void     gimp_levels_config_set_property (GObject          *object,
69                                                  guint             property_id,
70                                                  const GValue     *value,
71                                                  GParamSpec       *pspec);
72 
73 static gboolean gimp_levels_config_serialize    (GimpConfig       *config,
74                                                  GimpConfigWriter *writer,
75                                                  gpointer          data);
76 static gboolean gimp_levels_config_deserialize  (GimpConfig       *config,
77                                                  GScanner         *scanner,
78                                                  gint              nest_level,
79                                                  gpointer          data);
80 static gboolean gimp_levels_config_equal        (GimpConfig       *a,
81                                                  GimpConfig       *b);
82 static void     gimp_levels_config_reset        (GimpConfig       *config);
83 static gboolean gimp_levels_config_copy         (GimpConfig       *src,
84                                                  GimpConfig       *dest,
85                                                  GParamFlags       flags);
86 
87 
G_DEFINE_TYPE_WITH_CODE(GimpLevelsConfig,gimp_levels_config,GIMP_TYPE_OPERATION_SETTINGS,G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG,gimp_levels_config_iface_init))88 G_DEFINE_TYPE_WITH_CODE (GimpLevelsConfig, gimp_levels_config,
89                          GIMP_TYPE_OPERATION_SETTINGS,
90                          G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG,
91                                                 gimp_levels_config_iface_init))
92 
93 #define parent_class gimp_levels_config_parent_class
94 
95 
96 static void
97 gimp_levels_config_class_init (GimpLevelsConfigClass *klass)
98 {
99   GObjectClass      *object_class   = G_OBJECT_CLASS (klass);
100   GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
101 
102   object_class->set_property        = gimp_levels_config_set_property;
103   object_class->get_property        = gimp_levels_config_get_property;
104 
105   viewable_class->default_icon_name = "gimp-tool-levels";
106 
107   GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_LINEAR,
108                             "linear",
109                             _("Linear"),
110                             _("Work on linear RGB"),
111                             FALSE, 0);
112 
113   GIMP_CONFIG_PROP_ENUM (object_class, PROP_CHANNEL,
114                          "channel",
115                          _("Channel"),
116                          _("The affected channel"),
117                          GIMP_TYPE_HISTOGRAM_CHANNEL,
118                          GIMP_HISTOGRAM_VALUE, 0);
119 
120   GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_LOW_INPUT,
121                            "low-input",
122                            _("Low Input"),
123                            _("Low Input"),
124                            0.0, 1.0, 0.0, 0);
125 
126   GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_HIGH_INPUT,
127                            "high-input",
128                            _("High Input"),
129                            _("High Input"),
130                            0.0, 1.0, 1.0, 0);
131 
132   GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_CLAMP_INPUT,
133                            "clamp-input",
134                            _("Clamp Input"),
135                            _("Clamp input values before applying output mapping."),
136                             FALSE, 0);
137 
138   GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_GAMMA,
139                            "gamma",
140                            _("Gamma"),
141                            _("Gamma"),
142                            0.1, 10.0, 1.0, 0);
143 
144   GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_LOW_OUTPUT,
145                            "low-output",
146                            _("Low Output"),
147                            _("Low Output"),
148                            0.0, 1.0, 0.0, 0);
149 
150   GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_HIGH_OUTPUT,
151                            "high-output",
152                            _("High Output"),
153                            _("High Output"),
154                            0.0, 1.0, 1.0, 0);
155 
156   GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_CLAMP_OUTPUT,
157                            "clamp-output",
158                            _("Clamp Output"),
159                            _("Clamp final output values."),
160                             FALSE, 0);
161 }
162 
163 static void
gimp_levels_config_iface_init(GimpConfigInterface * iface)164 gimp_levels_config_iface_init (GimpConfigInterface *iface)
165 {
166   iface->serialize   = gimp_levels_config_serialize;
167   iface->deserialize = gimp_levels_config_deserialize;
168   iface->equal       = gimp_levels_config_equal;
169   iface->reset       = gimp_levels_config_reset;
170   iface->copy        = gimp_levels_config_copy;
171 }
172 
173 static void
gimp_levels_config_init(GimpLevelsConfig * self)174 gimp_levels_config_init (GimpLevelsConfig *self)
175 {
176   gimp_config_reset (GIMP_CONFIG (self));
177 }
178 
179 static void
gimp_levels_config_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)180 gimp_levels_config_get_property (GObject    *object,
181                                  guint       property_id,
182                                  GValue     *value,
183                                  GParamSpec *pspec)
184 {
185   GimpLevelsConfig *self = GIMP_LEVELS_CONFIG (object);
186 
187   switch (property_id)
188     {
189     case PROP_LINEAR:
190       g_value_set_boolean (value, self->linear);
191       break;
192 
193     case PROP_CHANNEL:
194       g_value_set_enum (value, self->channel);
195       break;
196 
197     case PROP_LOW_INPUT:
198       g_value_set_double (value, self->low_input[self->channel]);
199       break;
200 
201     case PROP_HIGH_INPUT:
202       g_value_set_double (value, self->high_input[self->channel]);
203       break;
204 
205     case PROP_CLAMP_INPUT:
206       g_value_set_boolean (value, self->clamp_input);
207       break;
208 
209     case PROP_GAMMA:
210       g_value_set_double (value, self->gamma[self->channel]);
211       break;
212 
213     case PROP_LOW_OUTPUT:
214       g_value_set_double (value, self->low_output[self->channel]);
215       break;
216 
217     case PROP_HIGH_OUTPUT:
218       g_value_set_double (value, self->high_output[self->channel]);
219       break;
220 
221     case PROP_CLAMP_OUTPUT:
222       g_value_set_boolean (value, self->clamp_output);
223       break;
224 
225     default:
226       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
227       break;
228     }
229 }
230 
231 static void
gimp_levels_config_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)232 gimp_levels_config_set_property (GObject      *object,
233                                  guint         property_id,
234                                  const GValue *value,
235                                  GParamSpec   *pspec)
236 {
237   GimpLevelsConfig *self = GIMP_LEVELS_CONFIG (object);
238 
239   switch (property_id)
240     {
241     case PROP_LINEAR:
242       self->linear = g_value_get_boolean (value);
243       break;
244 
245     case PROP_CHANNEL:
246       self->channel = g_value_get_enum (value);
247       g_object_notify (object, "low-input");
248       g_object_notify (object, "high-input");
249       g_object_notify (object, "gamma");
250       g_object_notify (object, "low-output");
251       g_object_notify (object, "high-output");
252       break;
253 
254     case PROP_LOW_INPUT:
255       self->low_input[self->channel] = g_value_get_double (value);
256       break;
257 
258     case PROP_HIGH_INPUT:
259       self->high_input[self->channel] = g_value_get_double (value);
260       break;
261 
262     case PROP_CLAMP_INPUT:
263       self->clamp_input = g_value_get_boolean (value);
264       break;
265 
266     case PROP_GAMMA:
267       self->gamma[self->channel] = g_value_get_double (value);
268       break;
269 
270     case PROP_LOW_OUTPUT:
271       self->low_output[self->channel] = g_value_get_double (value);
272       break;
273 
274     case PROP_HIGH_OUTPUT:
275       self->high_output[self->channel] = g_value_get_double (value);
276       break;
277 
278     case PROP_CLAMP_OUTPUT:
279       self->clamp_output = g_value_get_boolean (value);
280       break;
281 
282    default:
283       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
284       break;
285     }
286 }
287 
288 static gboolean
gimp_levels_config_serialize(GimpConfig * config,GimpConfigWriter * writer,gpointer data)289 gimp_levels_config_serialize (GimpConfig       *config,
290                               GimpConfigWriter *writer,
291                               gpointer          data)
292 {
293   GimpLevelsConfig     *l_config = GIMP_LEVELS_CONFIG (config);
294   GimpHistogramChannel  channel;
295   GimpHistogramChannel  old_channel;
296   gboolean              success = TRUE;
297 
298   if (! gimp_operation_settings_config_serialize_base (config, writer, data)    ||
299       ! gimp_config_serialize_property_by_name (config, "linear",       writer) ||
300       ! gimp_config_serialize_property_by_name (config, "clamp-input",  writer) ||
301       ! gimp_config_serialize_property_by_name (config, "clamp-output", writer))
302     return FALSE;
303 
304   old_channel = l_config->channel;
305 
306   for (channel = GIMP_HISTOGRAM_VALUE;
307        channel <= GIMP_HISTOGRAM_ALPHA;
308        channel++)
309     {
310       l_config->channel = channel;
311 
312       /*  serialize the channel properties manually (not using
313        *  gimp_config_serialize_properties()), so the parent class'
314        *  properties don't end up in the config file one per channel.
315        *  See bug #700653.
316        */
317       success =
318         (gimp_config_serialize_property_by_name (config, "channel",     writer) &&
319          gimp_config_serialize_property_by_name (config, "low-input",   writer) &&
320          gimp_config_serialize_property_by_name (config, "high-input",  writer) &&
321          gimp_config_serialize_property_by_name (config, "gamma",       writer) &&
322          gimp_config_serialize_property_by_name (config, "low-output",  writer) &&
323          gimp_config_serialize_property_by_name (config, "high-output", writer));
324 
325       if (! success)
326         break;
327     }
328 
329   l_config->channel = old_channel;
330 
331   return success;
332 }
333 
334 static gboolean
gimp_levels_config_deserialize(GimpConfig * config,GScanner * scanner,gint nest_level,gpointer data)335 gimp_levels_config_deserialize (GimpConfig *config,
336                                 GScanner   *scanner,
337                                 gint        nest_level,
338                                 gpointer    data)
339 {
340   GimpLevelsConfig     *l_config = GIMP_LEVELS_CONFIG (config);
341   GimpHistogramChannel  old_channel;
342   gboolean              success = TRUE;
343 
344   old_channel = l_config->channel;
345 
346   success = gimp_config_deserialize_properties (config, scanner, nest_level);
347 
348   g_object_set (config, "channel", old_channel, NULL);
349 
350   return success;
351 }
352 
353 static gboolean
gimp_levels_config_equal(GimpConfig * a,GimpConfig * b)354 gimp_levels_config_equal (GimpConfig *a,
355                           GimpConfig *b)
356 {
357   GimpLevelsConfig     *config_a = GIMP_LEVELS_CONFIG (a);
358   GimpLevelsConfig     *config_b = GIMP_LEVELS_CONFIG (b);
359   GimpHistogramChannel  channel;
360 
361   if (! gimp_operation_settings_config_equal_base (a, b) ||
362       config_a->linear       != config_b->linear         ||
363       config_a->clamp_input  != config_b->clamp_input    ||
364       config_a->clamp_output != config_b->clamp_output)
365     return FALSE;
366 
367   for (channel = GIMP_HISTOGRAM_VALUE;
368        channel <= GIMP_HISTOGRAM_ALPHA;
369        channel++)
370     {
371       if (config_a->gamma[channel]       != config_b->gamma[channel]      ||
372           config_a->low_input[channel]   != config_b->low_input[channel]  ||
373           config_a->high_input[channel]  != config_b->high_input[channel] ||
374           config_a->low_output[channel]  != config_b->low_output[channel] ||
375           config_a->high_output[channel] != config_b->high_output[channel])
376         return FALSE;
377     }
378 
379   /* don't compare "channel" */
380 
381   return TRUE;
382 }
383 
384 static void
gimp_levels_config_reset(GimpConfig * config)385 gimp_levels_config_reset (GimpConfig *config)
386 {
387   GimpLevelsConfig     *l_config = GIMP_LEVELS_CONFIG (config);
388   GimpHistogramChannel  channel;
389 
390   gimp_operation_settings_config_reset_base (config);
391 
392   for (channel = GIMP_HISTOGRAM_VALUE;
393        channel <= GIMP_HISTOGRAM_ALPHA;
394        channel++)
395     {
396       l_config->channel = channel;
397       gimp_levels_config_reset_channel (l_config);
398     }
399 
400   gimp_config_reset_property (G_OBJECT (config), "linear");
401   gimp_config_reset_property (G_OBJECT (config), "channel");
402   gimp_config_reset_property (G_OBJECT (config), "clamp-input");
403   gimp_config_reset_property (G_OBJECT (config), "clamp_output");
404 }
405 
406 static gboolean
gimp_levels_config_copy(GimpConfig * src,GimpConfig * dest,GParamFlags flags)407 gimp_levels_config_copy (GimpConfig  *src,
408                          GimpConfig  *dest,
409                          GParamFlags  flags)
410 {
411   GimpLevelsConfig     *src_config  = GIMP_LEVELS_CONFIG (src);
412   GimpLevelsConfig     *dest_config = GIMP_LEVELS_CONFIG (dest);
413   GimpHistogramChannel  channel;
414 
415   if (! gimp_operation_settings_config_copy_base (src, dest, flags))
416     return FALSE;
417 
418   for (channel = GIMP_HISTOGRAM_VALUE;
419        channel <= GIMP_HISTOGRAM_ALPHA;
420        channel++)
421     {
422       dest_config->gamma[channel]       = src_config->gamma[channel];
423       dest_config->low_input[channel]   = src_config->low_input[channel];
424       dest_config->high_input[channel]  = src_config->high_input[channel];
425       dest_config->low_output[channel]  = src_config->low_output[channel];
426       dest_config->high_output[channel] = src_config->high_output[channel];
427     }
428 
429   g_object_notify (G_OBJECT (dest), "gamma");
430   g_object_notify (G_OBJECT (dest), "low-input");
431   g_object_notify (G_OBJECT (dest), "high-input");
432   g_object_notify (G_OBJECT (dest), "low-output");
433   g_object_notify (G_OBJECT (dest), "high-output");
434 
435   dest_config->linear       = src_config->linear;
436   dest_config->channel      = src_config->channel;
437   dest_config->clamp_input  = src_config->clamp_input;
438   dest_config->clamp_output = src_config->clamp_output;
439 
440   g_object_notify (G_OBJECT (dest), "linear");
441   g_object_notify (G_OBJECT (dest), "channel");
442   g_object_notify (G_OBJECT (dest), "clamp-input");
443   g_object_notify (G_OBJECT (dest), "clamp-output");
444 
445   return TRUE;
446 }
447 
448 
449 /*  public functions  */
450 
451 void
gimp_levels_config_reset_channel(GimpLevelsConfig * config)452 gimp_levels_config_reset_channel (GimpLevelsConfig *config)
453 {
454   g_return_if_fail (GIMP_IS_LEVELS_CONFIG (config));
455 
456   g_object_freeze_notify (G_OBJECT (config));
457 
458   gimp_config_reset_property (G_OBJECT (config), "gamma");
459   gimp_config_reset_property (G_OBJECT (config), "low-input");
460   gimp_config_reset_property (G_OBJECT (config), "high-input");
461   gimp_config_reset_property (G_OBJECT (config), "low-output");
462   gimp_config_reset_property (G_OBJECT (config), "high-output");
463 
464   g_object_thaw_notify (G_OBJECT (config));
465 }
466 
467 void
gimp_levels_config_stretch(GimpLevelsConfig * config,GimpHistogram * histogram,gboolean is_color)468 gimp_levels_config_stretch (GimpLevelsConfig *config,
469                             GimpHistogram    *histogram,
470                             gboolean          is_color)
471 {
472   g_return_if_fail (GIMP_IS_LEVELS_CONFIG (config));
473   g_return_if_fail (histogram != NULL);
474 
475   g_object_freeze_notify (G_OBJECT (config));
476 
477   if (is_color)
478     {
479       GimpHistogramChannel channel;
480 
481       /*  Set the overall value to defaults  */
482       channel = config->channel;
483       config->channel = GIMP_HISTOGRAM_VALUE;
484       gimp_levels_config_reset_channel (config);
485       config->channel = channel;
486 
487       for (channel = GIMP_HISTOGRAM_RED;
488            channel <= GIMP_HISTOGRAM_BLUE;
489            channel++)
490         {
491           gimp_levels_config_stretch_channel (config, histogram, channel);
492         }
493     }
494   else
495     {
496       gimp_levels_config_stretch_channel (config, histogram,
497                                           GIMP_HISTOGRAM_VALUE);
498     }
499 
500   g_object_thaw_notify (G_OBJECT (config));
501 }
502 
503 void
gimp_levels_config_stretch_channel(GimpLevelsConfig * config,GimpHistogram * histogram,GimpHistogramChannel channel)504 gimp_levels_config_stretch_channel (GimpLevelsConfig     *config,
505                                     GimpHistogram        *histogram,
506                                     GimpHistogramChannel  channel)
507 {
508   gdouble count;
509   gdouble bias = 0.006;
510   gint    n_bins;
511   gint    i;
512 
513   g_return_if_fail (GIMP_IS_LEVELS_CONFIG (config));
514   g_return_if_fail (histogram != NULL);
515 
516   g_object_freeze_notify (G_OBJECT (config));
517 
518   config->gamma[channel]       = 1.0;
519   config->low_output[channel]  = 0.0;
520   config->high_output[channel] = 1.0;
521 
522   n_bins = gimp_histogram_n_bins (histogram);
523   count  = gimp_histogram_get_count (histogram, channel, 0, n_bins - 1);
524 
525   if (count == 0.0)
526     {
527       config->low_input[channel]  = 0.0;
528       config->high_input[channel] = 0.0;
529     }
530   else
531     {
532       gdouble new_count;
533       gdouble percentage;
534       gdouble next_percentage;
535 
536       /*  Set the low input  */
537       new_count = 0.0;
538 
539       for (i = 0; i < (n_bins - 1); i++)
540         {
541           new_count += gimp_histogram_get_value (histogram, channel, i);
542           percentage = new_count / count;
543           next_percentage = (new_count +
544                              gimp_histogram_get_value (histogram,
545                                                        channel,
546                                                        i + 1)) / count;
547 
548           if (fabs (percentage - bias) < fabs (next_percentage - bias))
549             {
550               config->low_input[channel] = (gdouble) (i + 1) / (n_bins - 1);
551               break;
552             }
553         }
554 
555       /*  Set the high input  */
556       new_count = 0.0;
557 
558       for (i = (n_bins - 1); i > 0; i--)
559         {
560           new_count += gimp_histogram_get_value (histogram, channel, i);
561           percentage = new_count / count;
562           next_percentage = (new_count +
563                              gimp_histogram_get_value (histogram,
564                                                        channel,
565                                                        i - 1)) / count;
566 
567           if (fabs (percentage - bias) < fabs (next_percentage - bias))
568             {
569               config->high_input[channel] = (gdouble) (i - 1) / (n_bins - 1);
570               break;
571             }
572         }
573     }
574 
575   g_object_notify (G_OBJECT (config), "gamma");
576   g_object_notify (G_OBJECT (config), "low-input");
577   g_object_notify (G_OBJECT (config), "high-input");
578   g_object_notify (G_OBJECT (config), "low-output");
579   g_object_notify (G_OBJECT (config), "high-output");
580 
581   g_object_thaw_notify (G_OBJECT (config));
582 }
583 
584 static gdouble
gimp_levels_config_input_from_color(GimpHistogramChannel channel,const GimpRGB * color)585 gimp_levels_config_input_from_color (GimpHistogramChannel  channel,
586                                      const GimpRGB        *color)
587 {
588   switch (channel)
589     {
590     case GIMP_HISTOGRAM_VALUE:
591       return MAX (MAX (color->r, color->g), color->b);
592 
593     case GIMP_HISTOGRAM_RED:
594       return color->r;
595 
596     case GIMP_HISTOGRAM_GREEN:
597       return color->g;
598 
599     case GIMP_HISTOGRAM_BLUE:
600       return color->b;
601 
602     case GIMP_HISTOGRAM_ALPHA:
603       return color->a;
604 
605     case GIMP_HISTOGRAM_RGB:
606       return MIN (MIN (color->r, color->g), color->b);
607 
608     case GIMP_HISTOGRAM_LUMINANCE:
609       return GIMP_RGB_LUMINANCE (color->r, color->g, color->b);
610     }
611 
612   return 0.0;
613 }
614 
615 void
gimp_levels_config_adjust_by_colors(GimpLevelsConfig * config,GimpHistogramChannel channel,const GimpRGB * black,const GimpRGB * gray,const GimpRGB * white)616 gimp_levels_config_adjust_by_colors (GimpLevelsConfig     *config,
617                                      GimpHistogramChannel  channel,
618                                      const GimpRGB        *black,
619                                      const GimpRGB        *gray,
620                                      const GimpRGB        *white)
621 {
622   g_return_if_fail (GIMP_IS_LEVELS_CONFIG (config));
623 
624   g_object_freeze_notify (G_OBJECT (config));
625 
626   if (black)
627     {
628       config->low_input[channel] = gimp_levels_config_input_from_color (channel,
629                                                                         black);
630       g_object_notify (G_OBJECT (config), "low-input");
631     }
632 
633 
634   if (white)
635     {
636       config->high_input[channel] = gimp_levels_config_input_from_color (channel,
637                                                                          white);
638       g_object_notify (G_OBJECT (config), "high-input");
639     }
640 
641   if (gray)
642     {
643       gdouble input;
644       gdouble range;
645       gdouble inten;
646       gdouble out_light;
647       gdouble lightness;
648 
649       /* Calculate lightness value */
650       lightness = GIMP_RGB_LUMINANCE (gray->r, gray->g, gray->b);
651 
652       input = gimp_levels_config_input_from_color (channel, gray);
653 
654       range = config->high_input[channel] - config->low_input[channel];
655       if (range <= 0)
656         goto out;
657 
658       input -= config->low_input[channel];
659       if (input < 0)
660         goto out;
661 
662       /* Normalize input and lightness */
663       inten = input / range;
664       out_light = lightness / range;
665 
666       /* See bug 622054: picking pure black or white as gamma doesn't
667        * work. But we cannot compare to 0.0 or 1.0 because cpus and
668        * compilers are shit. If you try to check out_light using
669        * printf() it will give exact 0.0 or 1.0 anyway, probably
670        * because the generated code is different and out_light doesn't
671        * live in a register. That must be why the cpu/compiler mafia
672        * invented epsilon and defined this shit to be the programmer's
673        * responsibility.
674        */
675       if (out_light <= 0.0001 || out_light >= 0.9999)
676         goto out;
677 
678       /* Map selected color to corresponding lightness */
679       config->gamma[channel] = log (inten) / log (out_light);
680       config->gamma[channel] = CLAMP (config->gamma[channel], 0.1, 10.0);
681       g_object_notify (G_OBJECT (config), "gamma");
682     }
683 
684  out:
685   g_object_thaw_notify (G_OBJECT (config));
686 }
687 
688 GimpCurvesConfig *
gimp_levels_config_to_curves_config(GimpLevelsConfig * config)689 gimp_levels_config_to_curves_config (GimpLevelsConfig *config)
690 {
691   GimpCurvesConfig     *curves;
692   GimpHistogramChannel  channel;
693 
694   g_return_val_if_fail (GIMP_IS_LEVELS_CONFIG (config), NULL);
695 
696   curves = g_object_new (GIMP_TYPE_CURVES_CONFIG, NULL);
697 
698   gimp_operation_settings_config_copy_base (GIMP_CONFIG (config),
699                                             GIMP_CONFIG (curves),
700                                             0);
701 
702   curves->linear = config->linear;
703 
704   for (channel = GIMP_HISTOGRAM_VALUE;
705        channel <= GIMP_HISTOGRAM_ALPHA;
706        channel++)
707     {
708       GimpCurve  *curve    = curves->curve[channel];
709       static const gint n  = 8;
710       gdouble     gamma    = config->gamma[channel];
711       gdouble     delta_in;
712       gdouble     delta_out;
713       gdouble     x, y;
714 
715       /* clear the points set by default */
716       gimp_curve_clear_points (curve);
717 
718       delta_in  = config->high_input[channel] - config->low_input[channel];
719       delta_out = config->high_output[channel] - config->low_output[channel];
720 
721       x = config->low_input[channel];
722       y = config->low_output[channel];
723 
724       gimp_curve_add_point (curve, x, y);
725 
726       if (delta_out != 0 && gamma != 1.0)
727         {
728           /* The Levels tool performs gamma adjustment, which is a
729            * power law, while the Curves tool uses cubic Bézier
730            * curves. Here we try to approximate this gamma adjustment
731            * with a Bézier curve with 5 control points. Two of them
732            * must be (low_input, low_output) and (high_input,
733            * high_output), so we need to add 3 more control points in
734            * the middle.
735            */
736           gint i;
737 
738           if (gamma > 1)
739             {
740               /* Case no. 1: γ > 1
741                *
742                * The curve should look like a horizontal
743                * parabola. Since its curvature is greatest when x is
744                * small, we add more control points there, so the
745                * approximation is more accurate. I decided to set the
746                * length of the consecutive segments to x₀, γ⋅x₀, γ²⋅x₀
747                * and γ³⋅x₀ and I saw that the curves looked
748                * good. Still, this is completely arbitrary.
749                */
750               gdouble dx = 0;
751               gdouble x0;
752 
753               for (i = 0; i < n; ++i)
754                 dx = dx * gamma + 1;
755               x0 = delta_in / dx;
756 
757               dx = 0;
758               for (i = 1; i < n; ++i)
759                 {
760                   dx = dx * gamma + x0;
761                   x = config->low_input[channel] + dx;
762                   y = config->low_output[channel] + delta_out *
763                       gimp_operation_levels_map_input (config, channel, x);
764                   gimp_curve_add_point (curve, x, y);
765                 }
766             }
767           else
768             {
769               /* Case no. 2: γ < 1
770                *
771                * The curve is the same as the one in case no. 1,
772                * observed through a reflexion along the y = x axis. So
773                * if we invert γ and swap the x and y axes we can use
774                * the same method as in case no. 1.
775                */
776               GimpLevelsConfig *config_inv;
777               gdouble           dy = 0;
778               gdouble           y0;
779               const gdouble     gamma_inv = 1 / gamma;
780 
781               config_inv = gimp_config_duplicate (GIMP_CONFIG (config));
782 
783               config_inv->gamma[channel]       = gamma_inv;
784               config_inv->low_input[channel]   = config->low_output[channel];
785               config_inv->low_output[channel]  = config->low_input[channel];
786               config_inv->high_input[channel]  = config->high_output[channel];
787               config_inv->high_output[channel] = config->high_input[channel];
788 
789               for (i = 0; i < n; ++i)
790                 dy = dy * gamma_inv + 1;
791               y0 = delta_out / dy;
792 
793               dy = 0;
794               for (i = 1; i < n; ++i)
795                 {
796                   dy = dy * gamma_inv + y0;
797                   y = config->low_output[channel] + dy;
798                   x = config->low_input[channel] + delta_in *
799                       gimp_operation_levels_map_input (config_inv, channel, y);
800                   gimp_curve_add_point (curve, x, y);
801                 }
802 
803               g_object_unref (config_inv);
804             }
805         }
806 
807       x = config->high_input[channel];
808       y = config->high_output[channel];
809 
810       gimp_curve_add_point (curve, x, y);
811     }
812 
813   return curves;
814 }
815 
816 gboolean
gimp_levels_config_load_cruft(GimpLevelsConfig * config,GInputStream * input,GError ** error)817 gimp_levels_config_load_cruft (GimpLevelsConfig  *config,
818                                GInputStream      *input,
819                                GError           **error)
820 {
821   GDataInputStream *data_input;
822   gint              low_input[5];
823   gint              high_input[5];
824   gint              low_output[5];
825   gint              high_output[5];
826   gdouble           gamma[5];
827   gchar            *line;
828   gsize             line_len;
829   gint              i;
830 
831   g_return_val_if_fail (GIMP_IS_LEVELS_CONFIG (config), FALSE);
832   g_return_val_if_fail (G_IS_INPUT_STREAM (input), FALSE);
833   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
834 
835   data_input = g_data_input_stream_new (input);
836 
837   line_len = 64;
838   line = gimp_data_input_stream_read_line_always (data_input, &line_len,
839                                                   NULL, error);
840   if (! line)
841     return FALSE;
842 
843   if (strcmp (line, "# GIMP Levels File") != 0)
844     {
845       g_set_error_literal (error, GIMP_CONFIG_ERROR, GIMP_CONFIG_ERROR_PARSE,
846                            _("not a GIMP Levels file"));
847       g_object_unref (data_input);
848       g_free (line);
849       return FALSE;
850     }
851 
852   g_free (line);
853 
854   for (i = 0; i < 5; i++)
855     {
856       gchar  float_buf[32];
857       gchar *endp;
858       gint   fields;
859 
860       line_len = 64;
861       line = gimp_data_input_stream_read_line_always (data_input, &line_len,
862                                                       NULL, error);
863       if (! line)
864         {
865           g_object_unref (data_input);
866           return FALSE;
867         }
868 
869       fields = sscanf (line, "%d %d %d %d %31s",
870                        &low_input[i],
871                        &high_input[i],
872                        &low_output[i],
873                        &high_output[i],
874                        float_buf);
875 
876       g_free (line);
877 
878       if (fields != 5)
879         goto error;
880 
881       gamma[i] = g_ascii_strtod (float_buf, &endp);
882 
883       if (endp == float_buf || errno == ERANGE)
884         goto error;
885     }
886 
887   g_object_unref (data_input);
888 
889   g_object_freeze_notify (G_OBJECT (config));
890 
891   for (i = 0; i < 5; i++)
892     {
893       config->low_input[i]   = low_input[i]   / 255.0;
894       config->high_input[i]  = high_input[i]  / 255.0;
895       config->gamma[i]       = gamma[i];
896       config->low_output[i]  = low_output[i]  / 255.0;
897       config->high_output[i] = high_output[i] / 255.0;
898     }
899 
900   config->linear       = FALSE;
901   config->clamp_input  = TRUE;
902   config->clamp_output = TRUE;
903 
904   g_object_notify (G_OBJECT (config), "linear");
905   g_object_notify (G_OBJECT (config), "low-input");
906   g_object_notify (G_OBJECT (config), "high-input");
907   g_object_notify (G_OBJECT (config), "clamp-input");
908   g_object_notify (G_OBJECT (config), "gamma");
909   g_object_notify (G_OBJECT (config), "low-output");
910   g_object_notify (G_OBJECT (config), "high-output");
911   g_object_notify (G_OBJECT (config), "clamp-output");
912 
913   g_object_thaw_notify (G_OBJECT (config));
914 
915   return TRUE;
916 
917  error:
918   g_object_unref (data_input);
919 
920   g_set_error_literal (error, GIMP_CONFIG_ERROR, GIMP_CONFIG_ERROR_PARSE,
921                        _("parse error"));
922   return FALSE;
923 }
924 
925 gboolean
gimp_levels_config_save_cruft(GimpLevelsConfig * config,GOutputStream * output,GError ** error)926 gimp_levels_config_save_cruft (GimpLevelsConfig  *config,
927                                GOutputStream     *output,
928                                GError           **error)
929 {
930   GString *string;
931   gint     i;
932 
933   g_return_val_if_fail (GIMP_IS_LEVELS_CONFIG (config), FALSE);
934   g_return_val_if_fail (G_IS_OUTPUT_STREAM (output), FALSE);
935   g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
936 
937   string = g_string_new ("# GIMP Levels File\n");
938 
939   for (i = 0; i < 5; i++)
940     {
941       gchar buf[G_ASCII_DTOSTR_BUF_SIZE];
942 
943       g_string_append_printf (string,
944                               "%d %d %d %d %s\n",
945                               (gint) (config->low_input[i]   * 255.999),
946                               (gint) (config->high_input[i]  * 255.999),
947                               (gint) (config->low_output[i]  * 255.999),
948                               (gint) (config->high_output[i] * 255.999),
949                               g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE,
950                                               config->gamma[i]));
951     }
952 
953   if (! g_output_stream_write_all (output, string->str, string->len,
954                                    NULL, NULL, error))
955     {
956       g_prefix_error (error, _("Writing levels file failed: "));
957       g_string_free (string, TRUE);
958       return FALSE;
959     }
960 
961   g_string_free (string, TRUE);
962 
963   return TRUE;
964 }
965