diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:30:19 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:30:19 +0000 |
commit | 5c1676dfe6d2f3c837a5e074117b45613fd29a72 (patch) | |
tree | cbffb45144febf451e54061db2b21395faf94bfe /app/operations/gimplevelsconfig.c | |
parent | Initial commit. (diff) | |
download | gimp-upstream.tar.xz gimp-upstream.zip |
Adding upstream version 2.10.34.upstream/2.10.34upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'app/operations/gimplevelsconfig.c')
-rw-r--r-- | app/operations/gimplevelsconfig.c | 964 |
1 files changed, 964 insertions, 0 deletions
diff --git a/app/operations/gimplevelsconfig.c b/app/operations/gimplevelsconfig.c new file mode 100644 index 0000000..df5d349 --- /dev/null +++ b/app/operations/gimplevelsconfig.c @@ -0,0 +1,964 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimplevelsconfig.c + * Copyright (C) 2007 Michael Natterer <mitch@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <errno.h> + +#include <cairo.h> +#include <gegl.h> +#include <gdk-pixbuf/gdk-pixbuf.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpcolor/gimpcolor.h" +#include "libgimpmath/gimpmath.h" +#include "libgimpconfig/gimpconfig.h" + +#include "operations-types.h" + +#include "core/gimp-utils.h" +#include "core/gimpcurve.h" +#include "core/gimphistogram.h" + +#include "gimpcurvesconfig.h" +#include "gimplevelsconfig.h" +#include "gimpoperationlevels.h" + +#include "gimp-intl.h" + + +enum +{ + PROP_0, + PROP_LINEAR, + PROP_CHANNEL, + PROP_LOW_INPUT, + PROP_HIGH_INPUT, + PROP_CLAMP_INPUT, + PROP_GAMMA, + PROP_LOW_OUTPUT, + PROP_HIGH_OUTPUT, + PROP_CLAMP_OUTPUT +}; + + +static void gimp_levels_config_iface_init (GimpConfigInterface *iface); + +static void gimp_levels_config_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_levels_config_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); + +static gboolean gimp_levels_config_serialize (GimpConfig *config, + GimpConfigWriter *writer, + gpointer data); +static gboolean gimp_levels_config_deserialize (GimpConfig *config, + GScanner *scanner, + gint nest_level, + gpointer data); +static gboolean gimp_levels_config_equal (GimpConfig *a, + GimpConfig *b); +static void gimp_levels_config_reset (GimpConfig *config); +static gboolean gimp_levels_config_copy (GimpConfig *src, + GimpConfig *dest, + GParamFlags flags); + + +G_DEFINE_TYPE_WITH_CODE (GimpLevelsConfig, gimp_levels_config, + GIMP_TYPE_OPERATION_SETTINGS, + G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG, + gimp_levels_config_iface_init)) + +#define parent_class gimp_levels_config_parent_class + + +static void +gimp_levels_config_class_init (GimpLevelsConfigClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass); + + object_class->set_property = gimp_levels_config_set_property; + object_class->get_property = gimp_levels_config_get_property; + + viewable_class->default_icon_name = "gimp-tool-levels"; + + GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_LINEAR, + "linear", + _("Linear"), + _("Work on linear RGB"), + FALSE, 0); + + GIMP_CONFIG_PROP_ENUM (object_class, PROP_CHANNEL, + "channel", + _("Channel"), + _("The affected channel"), + GIMP_TYPE_HISTOGRAM_CHANNEL, + GIMP_HISTOGRAM_VALUE, 0); + + GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_LOW_INPUT, + "low-input", + _("Low Input"), + _("Low Input"), + 0.0, 1.0, 0.0, 0); + + GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_HIGH_INPUT, + "high-input", + _("High Input"), + _("High Input"), + 0.0, 1.0, 1.0, 0); + + GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_CLAMP_INPUT, + "clamp-input", + _("Clamp Input"), + _("Clamp input values before applying output mapping."), + FALSE, 0); + + GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_GAMMA, + "gamma", + _("Gamma"), + _("Gamma"), + 0.1, 10.0, 1.0, 0); + + GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_LOW_OUTPUT, + "low-output", + _("Low Output"), + _("Low Output"), + 0.0, 1.0, 0.0, 0); + + GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_HIGH_OUTPUT, + "high-output", + _("High Output"), + _("High Output"), + 0.0, 1.0, 1.0, 0); + + GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_CLAMP_OUTPUT, + "clamp-output", + _("Clamp Output"), + _("Clamp final output values."), + FALSE, 0); +} + +static void +gimp_levels_config_iface_init (GimpConfigInterface *iface) +{ + iface->serialize = gimp_levels_config_serialize; + iface->deserialize = gimp_levels_config_deserialize; + iface->equal = gimp_levels_config_equal; + iface->reset = gimp_levels_config_reset; + iface->copy = gimp_levels_config_copy; +} + +static void +gimp_levels_config_init (GimpLevelsConfig *self) +{ + gimp_config_reset (GIMP_CONFIG (self)); +} + +static void +gimp_levels_config_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpLevelsConfig *self = GIMP_LEVELS_CONFIG (object); + + switch (property_id) + { + case PROP_LINEAR: + g_value_set_boolean (value, self->linear); + break; + + case PROP_CHANNEL: + g_value_set_enum (value, self->channel); + break; + + case PROP_LOW_INPUT: + g_value_set_double (value, self->low_input[self->channel]); + break; + + case PROP_HIGH_INPUT: + g_value_set_double (value, self->high_input[self->channel]); + break; + + case PROP_CLAMP_INPUT: + g_value_set_boolean (value, self->clamp_input); + break; + + case PROP_GAMMA: + g_value_set_double (value, self->gamma[self->channel]); + break; + + case PROP_LOW_OUTPUT: + g_value_set_double (value, self->low_output[self->channel]); + break; + + case PROP_HIGH_OUTPUT: + g_value_set_double (value, self->high_output[self->channel]); + break; + + case PROP_CLAMP_OUTPUT: + g_value_set_boolean (value, self->clamp_output); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_levels_config_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpLevelsConfig *self = GIMP_LEVELS_CONFIG (object); + + switch (property_id) + { + case PROP_LINEAR: + self->linear = g_value_get_boolean (value); + break; + + case PROP_CHANNEL: + self->channel = g_value_get_enum (value); + g_object_notify (object, "low-input"); + g_object_notify (object, "high-input"); + g_object_notify (object, "gamma"); + g_object_notify (object, "low-output"); + g_object_notify (object, "high-output"); + break; + + case PROP_LOW_INPUT: + self->low_input[self->channel] = g_value_get_double (value); + break; + + case PROP_HIGH_INPUT: + self->high_input[self->channel] = g_value_get_double (value); + break; + + case PROP_CLAMP_INPUT: + self->clamp_input = g_value_get_boolean (value); + break; + + case PROP_GAMMA: + self->gamma[self->channel] = g_value_get_double (value); + break; + + case PROP_LOW_OUTPUT: + self->low_output[self->channel] = g_value_get_double (value); + break; + + case PROP_HIGH_OUTPUT: + self->high_output[self->channel] = g_value_get_double (value); + break; + + case PROP_CLAMP_OUTPUT: + self->clamp_output = g_value_get_boolean (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gboolean +gimp_levels_config_serialize (GimpConfig *config, + GimpConfigWriter *writer, + gpointer data) +{ + GimpLevelsConfig *l_config = GIMP_LEVELS_CONFIG (config); + GimpHistogramChannel channel; + GimpHistogramChannel old_channel; + gboolean success = TRUE; + + if (! gimp_operation_settings_config_serialize_base (config, writer, data) || + ! gimp_config_serialize_property_by_name (config, "linear", writer) || + ! gimp_config_serialize_property_by_name (config, "clamp-input", writer) || + ! gimp_config_serialize_property_by_name (config, "clamp-output", writer)) + return FALSE; + + old_channel = l_config->channel; + + for (channel = GIMP_HISTOGRAM_VALUE; + channel <= GIMP_HISTOGRAM_ALPHA; + channel++) + { + l_config->channel = channel; + + /* serialize the channel properties manually (not using + * gimp_config_serialize_properties()), so the parent class' + * properties don't end up in the config file one per channel. + * See bug #700653. + */ + success = + (gimp_config_serialize_property_by_name (config, "channel", writer) && + gimp_config_serialize_property_by_name (config, "low-input", writer) && + gimp_config_serialize_property_by_name (config, "high-input", writer) && + gimp_config_serialize_property_by_name (config, "gamma", writer) && + gimp_config_serialize_property_by_name (config, "low-output", writer) && + gimp_config_serialize_property_by_name (config, "high-output", writer)); + + if (! success) + break; + } + + l_config->channel = old_channel; + + return success; +} + +static gboolean +gimp_levels_config_deserialize (GimpConfig *config, + GScanner *scanner, + gint nest_level, + gpointer data) +{ + GimpLevelsConfig *l_config = GIMP_LEVELS_CONFIG (config); + GimpHistogramChannel old_channel; + gboolean success = TRUE; + + old_channel = l_config->channel; + + success = gimp_config_deserialize_properties (config, scanner, nest_level); + + g_object_set (config, "channel", old_channel, NULL); + + return success; +} + +static gboolean +gimp_levels_config_equal (GimpConfig *a, + GimpConfig *b) +{ + GimpLevelsConfig *config_a = GIMP_LEVELS_CONFIG (a); + GimpLevelsConfig *config_b = GIMP_LEVELS_CONFIG (b); + GimpHistogramChannel channel; + + if (! gimp_operation_settings_config_equal_base (a, b) || + config_a->linear != config_b->linear || + config_a->clamp_input != config_b->clamp_input || + config_a->clamp_output != config_b->clamp_output) + return FALSE; + + for (channel = GIMP_HISTOGRAM_VALUE; + channel <= GIMP_HISTOGRAM_ALPHA; + channel++) + { + if (config_a->gamma[channel] != config_b->gamma[channel] || + config_a->low_input[channel] != config_b->low_input[channel] || + config_a->high_input[channel] != config_b->high_input[channel] || + config_a->low_output[channel] != config_b->low_output[channel] || + config_a->high_output[channel] != config_b->high_output[channel]) + return FALSE; + } + + /* don't compare "channel" */ + + return TRUE; +} + +static void +gimp_levels_config_reset (GimpConfig *config) +{ + GimpLevelsConfig *l_config = GIMP_LEVELS_CONFIG (config); + GimpHistogramChannel channel; + + gimp_operation_settings_config_reset_base (config); + + for (channel = GIMP_HISTOGRAM_VALUE; + channel <= GIMP_HISTOGRAM_ALPHA; + channel++) + { + l_config->channel = channel; + gimp_levels_config_reset_channel (l_config); + } + + gimp_config_reset_property (G_OBJECT (config), "linear"); + gimp_config_reset_property (G_OBJECT (config), "channel"); + gimp_config_reset_property (G_OBJECT (config), "clamp-input"); + gimp_config_reset_property (G_OBJECT (config), "clamp_output"); +} + +static gboolean +gimp_levels_config_copy (GimpConfig *src, + GimpConfig *dest, + GParamFlags flags) +{ + GimpLevelsConfig *src_config = GIMP_LEVELS_CONFIG (src); + GimpLevelsConfig *dest_config = GIMP_LEVELS_CONFIG (dest); + GimpHistogramChannel channel; + + if (! gimp_operation_settings_config_copy_base (src, dest, flags)) + return FALSE; + + for (channel = GIMP_HISTOGRAM_VALUE; + channel <= GIMP_HISTOGRAM_ALPHA; + channel++) + { + dest_config->gamma[channel] = src_config->gamma[channel]; + dest_config->low_input[channel] = src_config->low_input[channel]; + dest_config->high_input[channel] = src_config->high_input[channel]; + dest_config->low_output[channel] = src_config->low_output[channel]; + dest_config->high_output[channel] = src_config->high_output[channel]; + } + + g_object_notify (G_OBJECT (dest), "gamma"); + g_object_notify (G_OBJECT (dest), "low-input"); + g_object_notify (G_OBJECT (dest), "high-input"); + g_object_notify (G_OBJECT (dest), "low-output"); + g_object_notify (G_OBJECT (dest), "high-output"); + + dest_config->linear = src_config->linear; + dest_config->channel = src_config->channel; + dest_config->clamp_input = src_config->clamp_input; + dest_config->clamp_output = src_config->clamp_output; + + g_object_notify (G_OBJECT (dest), "linear"); + g_object_notify (G_OBJECT (dest), "channel"); + g_object_notify (G_OBJECT (dest), "clamp-input"); + g_object_notify (G_OBJECT (dest), "clamp-output"); + + return TRUE; +} + + +/* public functions */ + +void +gimp_levels_config_reset_channel (GimpLevelsConfig *config) +{ + g_return_if_fail (GIMP_IS_LEVELS_CONFIG (config)); + + g_object_freeze_notify (G_OBJECT (config)); + + gimp_config_reset_property (G_OBJECT (config), "gamma"); + gimp_config_reset_property (G_OBJECT (config), "low-input"); + gimp_config_reset_property (G_OBJECT (config), "high-input"); + gimp_config_reset_property (G_OBJECT (config), "low-output"); + gimp_config_reset_property (G_OBJECT (config), "high-output"); + + g_object_thaw_notify (G_OBJECT (config)); +} + +void +gimp_levels_config_stretch (GimpLevelsConfig *config, + GimpHistogram *histogram, + gboolean is_color) +{ + g_return_if_fail (GIMP_IS_LEVELS_CONFIG (config)); + g_return_if_fail (histogram != NULL); + + g_object_freeze_notify (G_OBJECT (config)); + + if (is_color) + { + GimpHistogramChannel channel; + + /* Set the overall value to defaults */ + channel = config->channel; + config->channel = GIMP_HISTOGRAM_VALUE; + gimp_levels_config_reset_channel (config); + config->channel = channel; + + for (channel = GIMP_HISTOGRAM_RED; + channel <= GIMP_HISTOGRAM_BLUE; + channel++) + { + gimp_levels_config_stretch_channel (config, histogram, channel); + } + } + else + { + gimp_levels_config_stretch_channel (config, histogram, + GIMP_HISTOGRAM_VALUE); + } + + g_object_thaw_notify (G_OBJECT (config)); +} + +void +gimp_levels_config_stretch_channel (GimpLevelsConfig *config, + GimpHistogram *histogram, + GimpHistogramChannel channel) +{ + gdouble count; + gdouble bias = 0.006; + gint n_bins; + gint i; + + g_return_if_fail (GIMP_IS_LEVELS_CONFIG (config)); + g_return_if_fail (histogram != NULL); + + g_object_freeze_notify (G_OBJECT (config)); + + config->gamma[channel] = 1.0; + config->low_output[channel] = 0.0; + config->high_output[channel] = 1.0; + + n_bins = gimp_histogram_n_bins (histogram); + count = gimp_histogram_get_count (histogram, channel, 0, n_bins - 1); + + if (count == 0.0) + { + config->low_input[channel] = 0.0; + config->high_input[channel] = 0.0; + } + else + { + gdouble new_count; + gdouble percentage; + gdouble next_percentage; + + /* Set the low input */ + new_count = 0.0; + + for (i = 0; i < (n_bins - 1); i++) + { + new_count += gimp_histogram_get_value (histogram, channel, i); + percentage = new_count / count; + next_percentage = (new_count + + gimp_histogram_get_value (histogram, + channel, + i + 1)) / count; + + if (fabs (percentage - bias) < fabs (next_percentage - bias)) + { + config->low_input[channel] = (gdouble) (i + 1) / (n_bins - 1); + break; + } + } + + /* Set the high input */ + new_count = 0.0; + + for (i = (n_bins - 1); i > 0; i--) + { + new_count += gimp_histogram_get_value (histogram, channel, i); + percentage = new_count / count; + next_percentage = (new_count + + gimp_histogram_get_value (histogram, + channel, + i - 1)) / count; + + if (fabs (percentage - bias) < fabs (next_percentage - bias)) + { + config->high_input[channel] = (gdouble) (i - 1) / (n_bins - 1); + break; + } + } + } + + g_object_notify (G_OBJECT (config), "gamma"); + g_object_notify (G_OBJECT (config), "low-input"); + g_object_notify (G_OBJECT (config), "high-input"); + g_object_notify (G_OBJECT (config), "low-output"); + g_object_notify (G_OBJECT (config), "high-output"); + + g_object_thaw_notify (G_OBJECT (config)); +} + +static gdouble +gimp_levels_config_input_from_color (GimpHistogramChannel channel, + const GimpRGB *color) +{ + switch (channel) + { + case GIMP_HISTOGRAM_VALUE: + return MAX (MAX (color->r, color->g), color->b); + + case GIMP_HISTOGRAM_RED: + return color->r; + + case GIMP_HISTOGRAM_GREEN: + return color->g; + + case GIMP_HISTOGRAM_BLUE: + return color->b; + + case GIMP_HISTOGRAM_ALPHA: + return color->a; + + case GIMP_HISTOGRAM_RGB: + return MIN (MIN (color->r, color->g), color->b); + + case GIMP_HISTOGRAM_LUMINANCE: + return GIMP_RGB_LUMINANCE (color->r, color->g, color->b); + } + + return 0.0; +} + +void +gimp_levels_config_adjust_by_colors (GimpLevelsConfig *config, + GimpHistogramChannel channel, + const GimpRGB *black, + const GimpRGB *gray, + const GimpRGB *white) +{ + g_return_if_fail (GIMP_IS_LEVELS_CONFIG (config)); + + g_object_freeze_notify (G_OBJECT (config)); + + if (black) + { + config->low_input[channel] = gimp_levels_config_input_from_color (channel, + black); + g_object_notify (G_OBJECT (config), "low-input"); + } + + + if (white) + { + config->high_input[channel] = gimp_levels_config_input_from_color (channel, + white); + g_object_notify (G_OBJECT (config), "high-input"); + } + + if (gray) + { + gdouble input; + gdouble range; + gdouble inten; + gdouble out_light; + gdouble lightness; + + /* Calculate lightness value */ + lightness = GIMP_RGB_LUMINANCE (gray->r, gray->g, gray->b); + + input = gimp_levels_config_input_from_color (channel, gray); + + range = config->high_input[channel] - config->low_input[channel]; + if (range <= 0) + goto out; + + input -= config->low_input[channel]; + if (input < 0) + goto out; + + /* Normalize input and lightness */ + inten = input / range; + out_light = lightness / range; + + /* See bug 622054: picking pure black or white as gamma doesn't + * work. But we cannot compare to 0.0 or 1.0 because cpus and + * compilers are shit. If you try to check out_light using + * printf() it will give exact 0.0 or 1.0 anyway, probably + * because the generated code is different and out_light doesn't + * live in a register. That must be why the cpu/compiler mafia + * invented epsilon and defined this shit to be the programmer's + * responsibility. + */ + if (out_light <= 0.0001 || out_light >= 0.9999) + goto out; + + /* Map selected color to corresponding lightness */ + config->gamma[channel] = log (inten) / log (out_light); + config->gamma[channel] = CLAMP (config->gamma[channel], 0.1, 10.0); + g_object_notify (G_OBJECT (config), "gamma"); + } + + out: + g_object_thaw_notify (G_OBJECT (config)); +} + +GimpCurvesConfig * +gimp_levels_config_to_curves_config (GimpLevelsConfig *config) +{ + GimpCurvesConfig *curves; + GimpHistogramChannel channel; + + g_return_val_if_fail (GIMP_IS_LEVELS_CONFIG (config), NULL); + + curves = g_object_new (GIMP_TYPE_CURVES_CONFIG, NULL); + + gimp_operation_settings_config_copy_base (GIMP_CONFIG (config), + GIMP_CONFIG (curves), + 0); + + curves->linear = config->linear; + + for (channel = GIMP_HISTOGRAM_VALUE; + channel <= GIMP_HISTOGRAM_ALPHA; + channel++) + { + GimpCurve *curve = curves->curve[channel]; + static const gint n = 8; + gdouble gamma = config->gamma[channel]; + gdouble delta_in; + gdouble delta_out; + gdouble x, y; + + /* clear the points set by default */ + gimp_curve_clear_points (curve); + + delta_in = config->high_input[channel] - config->low_input[channel]; + delta_out = config->high_output[channel] - config->low_output[channel]; + + x = config->low_input[channel]; + y = config->low_output[channel]; + + gimp_curve_add_point (curve, x, y); + + if (delta_out != 0 && gamma != 1.0) + { + /* The Levels tool performs gamma adjustment, which is a + * power law, while the Curves tool uses cubic Bézier + * curves. Here we try to approximate this gamma adjustment + * with a Bézier curve with 5 control points. Two of them + * must be (low_input, low_output) and (high_input, + * high_output), so we need to add 3 more control points in + * the middle. + */ + gint i; + + if (gamma > 1) + { + /* Case no. 1: γ > 1 + * + * The curve should look like a horizontal + * parabola. Since its curvature is greatest when x is + * small, we add more control points there, so the + * approximation is more accurate. I decided to set the + * length of the consecutive segments to x₀, γ⋅x₀, γ²⋅x₀ + * and γ³⋅x₀ and I saw that the curves looked + * good. Still, this is completely arbitrary. + */ + gdouble dx = 0; + gdouble x0; + + for (i = 0; i < n; ++i) + dx = dx * gamma + 1; + x0 = delta_in / dx; + + dx = 0; + for (i = 1; i < n; ++i) + { + dx = dx * gamma + x0; + x = config->low_input[channel] + dx; + y = config->low_output[channel] + delta_out * + gimp_operation_levels_map_input (config, channel, x); + gimp_curve_add_point (curve, x, y); + } + } + else + { + /* Case no. 2: γ < 1 + * + * The curve is the same as the one in case no. 1, + * observed through a reflexion along the y = x axis. So + * if we invert γ and swap the x and y axes we can use + * the same method as in case no. 1. + */ + GimpLevelsConfig *config_inv; + gdouble dy = 0; + gdouble y0; + const gdouble gamma_inv = 1 / gamma; + + config_inv = gimp_config_duplicate (GIMP_CONFIG (config)); + + config_inv->gamma[channel] = gamma_inv; + config_inv->low_input[channel] = config->low_output[channel]; + config_inv->low_output[channel] = config->low_input[channel]; + config_inv->high_input[channel] = config->high_output[channel]; + config_inv->high_output[channel] = config->high_input[channel]; + + for (i = 0; i < n; ++i) + dy = dy * gamma_inv + 1; + y0 = delta_out / dy; + + dy = 0; + for (i = 1; i < n; ++i) + { + dy = dy * gamma_inv + y0; + y = config->low_output[channel] + dy; + x = config->low_input[channel] + delta_in * + gimp_operation_levels_map_input (config_inv, channel, y); + gimp_curve_add_point (curve, x, y); + } + + g_object_unref (config_inv); + } + } + + x = config->high_input[channel]; + y = config->high_output[channel]; + + gimp_curve_add_point (curve, x, y); + } + + return curves; +} + +gboolean +gimp_levels_config_load_cruft (GimpLevelsConfig *config, + GInputStream *input, + GError **error) +{ + GDataInputStream *data_input; + gint low_input[5]; + gint high_input[5]; + gint low_output[5]; + gint high_output[5]; + gdouble gamma[5]; + gchar *line; + gsize line_len; + gint i; + + g_return_val_if_fail (GIMP_IS_LEVELS_CONFIG (config), FALSE); + g_return_val_if_fail (G_IS_INPUT_STREAM (input), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + data_input = g_data_input_stream_new (input); + + line_len = 64; + line = gimp_data_input_stream_read_line_always (data_input, &line_len, + NULL, error); + if (! line) + return FALSE; + + if (strcmp (line, "# GIMP Levels File") != 0) + { + g_set_error_literal (error, GIMP_CONFIG_ERROR, GIMP_CONFIG_ERROR_PARSE, + _("not a GIMP Levels file")); + g_object_unref (data_input); + g_free (line); + return FALSE; + } + + g_free (line); + + for (i = 0; i < 5; i++) + { + gchar float_buf[32]; + gchar *endp; + gint fields; + + line_len = 64; + line = gimp_data_input_stream_read_line_always (data_input, &line_len, + NULL, error); + if (! line) + { + g_object_unref (data_input); + return FALSE; + } + + fields = sscanf (line, "%d %d %d %d %31s", + &low_input[i], + &high_input[i], + &low_output[i], + &high_output[i], + float_buf); + + g_free (line); + + if (fields != 5) + goto error; + + gamma[i] = g_ascii_strtod (float_buf, &endp); + + if (endp == float_buf || errno == ERANGE) + goto error; + } + + g_object_unref (data_input); + + g_object_freeze_notify (G_OBJECT (config)); + + for (i = 0; i < 5; i++) + { + config->low_input[i] = low_input[i] / 255.0; + config->high_input[i] = high_input[i] / 255.0; + config->gamma[i] = gamma[i]; + config->low_output[i] = low_output[i] / 255.0; + config->high_output[i] = high_output[i] / 255.0; + } + + config->linear = FALSE; + config->clamp_input = TRUE; + config->clamp_output = TRUE; + + g_object_notify (G_OBJECT (config), "linear"); + g_object_notify (G_OBJECT (config), "low-input"); + g_object_notify (G_OBJECT (config), "high-input"); + g_object_notify (G_OBJECT (config), "clamp-input"); + g_object_notify (G_OBJECT (config), "gamma"); + g_object_notify (G_OBJECT (config), "low-output"); + g_object_notify (G_OBJECT (config), "high-output"); + g_object_notify (G_OBJECT (config), "clamp-output"); + + g_object_thaw_notify (G_OBJECT (config)); + + return TRUE; + + error: + g_object_unref (data_input); + + g_set_error_literal (error, GIMP_CONFIG_ERROR, GIMP_CONFIG_ERROR_PARSE, + _("parse error")); + return FALSE; +} + +gboolean +gimp_levels_config_save_cruft (GimpLevelsConfig *config, + GOutputStream *output, + GError **error) +{ + GString *string; + gint i; + + g_return_val_if_fail (GIMP_IS_LEVELS_CONFIG (config), FALSE); + g_return_val_if_fail (G_IS_OUTPUT_STREAM (output), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + string = g_string_new ("# GIMP Levels File\n"); + + for (i = 0; i < 5; i++) + { + gchar buf[G_ASCII_DTOSTR_BUF_SIZE]; + + g_string_append_printf (string, + "%d %d %d %d %s\n", + (gint) (config->low_input[i] * 255.999), + (gint) (config->high_input[i] * 255.999), + (gint) (config->low_output[i] * 255.999), + (gint) (config->high_output[i] * 255.999), + g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, + config->gamma[i])); + } + + if (! g_output_stream_write_all (output, string->str, string->len, + NULL, NULL, error)) + { + g_prefix_error (error, _("Writing levels file failed: ")); + g_string_free (string, TRUE); + return FALSE; + } + + g_string_free (string, TRUE); + + return TRUE; +} |