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