/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * pikalevelsconfig.c * Copyright (C) 2007 Michael Natterer * * 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 . */ #include "config.h" #include #include #include #include #include "libpikabase/pikabase.h" #include "libpikacolor/pikacolor.h" #include "libpikamath/pikamath.h" #include "libpikaconfig/pikaconfig.h" #include "operations-types.h" #include "core/pika-utils.h" #include "core/pikacurve.h" #include "core/pikahistogram.h" #include "pikacurvesconfig.h" #include "pikalevelsconfig.h" #include "pikaoperationlevels.h" #include "pika-intl.h" enum { PROP_0, PROP_TRC, 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 pika_levels_config_iface_init (PikaConfigInterface *iface); static void pika_levels_config_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void pika_levels_config_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static gboolean pika_levels_config_serialize (PikaConfig *config, PikaConfigWriter *writer, gpointer data); static gboolean pika_levels_config_deserialize (PikaConfig *config, GScanner *scanner, gint nest_level, gpointer data); static gboolean pika_levels_config_equal (PikaConfig *a, PikaConfig *b); static void pika_levels_config_reset (PikaConfig *config); static gboolean pika_levels_config_copy (PikaConfig *src, PikaConfig *dest, GParamFlags flags); G_DEFINE_TYPE_WITH_CODE (PikaLevelsConfig, pika_levels_config, PIKA_TYPE_OPERATION_SETTINGS, G_IMPLEMENT_INTERFACE (PIKA_TYPE_CONFIG, pika_levels_config_iface_init)) #define parent_class pika_levels_config_parent_class static void pika_levels_config_class_init (PikaLevelsConfigClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PikaViewableClass *viewable_class = PIKA_VIEWABLE_CLASS (klass); object_class->set_property = pika_levels_config_set_property; object_class->get_property = pika_levels_config_get_property; viewable_class->default_icon_name = "pika-tool-levels"; PIKA_CONFIG_PROP_ENUM (object_class, PROP_TRC, "trc", _("Linear/Perceptual"), _("Work on linear or perceptual RGB"), PIKA_TYPE_TRC_TYPE, PIKA_TRC_NON_LINEAR, 0); /* compat */ PIKA_CONFIG_PROP_BOOLEAN (object_class, PROP_LINEAR, "linear", _("Linear"), _("Work on linear RGB"), FALSE, 0); PIKA_CONFIG_PROP_ENUM (object_class, PROP_CHANNEL, "channel", _("Channel"), _("The affected channel"), PIKA_TYPE_HISTOGRAM_CHANNEL, PIKA_HISTOGRAM_VALUE, 0); PIKA_CONFIG_PROP_DOUBLE (object_class, PROP_LOW_INPUT, "low-input", _("Low Input"), _("Low Input"), 0.0, 1.0, 0.0, 0); PIKA_CONFIG_PROP_DOUBLE (object_class, PROP_HIGH_INPUT, "high-input", _("High Input"), _("High Input"), 0.0, 1.0, 1.0, 0); PIKA_CONFIG_PROP_BOOLEAN (object_class, PROP_CLAMP_INPUT, "clamp-input", _("Clamp Input"), _("Clamp input values before applying output mapping."), FALSE, 0); PIKA_CONFIG_PROP_DOUBLE (object_class, PROP_GAMMA, "gamma", _("Gamma"), _("Gamma"), 0.1, 10.0, 1.0, 0); PIKA_CONFIG_PROP_DOUBLE (object_class, PROP_LOW_OUTPUT, "low-output", _("Low Output"), _("Low Output"), 0.0, 1.0, 0.0, 0); PIKA_CONFIG_PROP_DOUBLE (object_class, PROP_HIGH_OUTPUT, "high-output", _("High Output"), _("High Output"), 0.0, 1.0, 1.0, 0); PIKA_CONFIG_PROP_BOOLEAN (object_class, PROP_CLAMP_OUTPUT, "clamp-output", _("Clamp Output"), _("Clamp final output values."), FALSE, 0); } static void pika_levels_config_iface_init (PikaConfigInterface *iface) { iface->serialize = pika_levels_config_serialize; iface->deserialize = pika_levels_config_deserialize; iface->equal = pika_levels_config_equal; iface->reset = pika_levels_config_reset; iface->copy = pika_levels_config_copy; } static void pika_levels_config_init (PikaLevelsConfig *self) { pika_config_reset (PIKA_CONFIG (self)); } static void pika_levels_config_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PikaLevelsConfig *self = PIKA_LEVELS_CONFIG (object); switch (property_id) { case PROP_TRC: g_value_set_enum (value, self->trc); break; case PROP_LINEAR: g_value_set_boolean (value, self->trc == PIKA_TRC_LINEAR ? TRUE : FALSE); 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 pika_levels_config_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { PikaLevelsConfig *self = PIKA_LEVELS_CONFIG (object); switch (property_id) { case PROP_TRC: self->trc = g_value_get_enum (value); break; case PROP_LINEAR: self->trc = g_value_get_boolean (value) ? PIKA_TRC_LINEAR : PIKA_TRC_NON_LINEAR; g_object_notify (object, "trc"); 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 pika_levels_config_serialize (PikaConfig *config, PikaConfigWriter *writer, gpointer data) { PikaLevelsConfig *l_config = PIKA_LEVELS_CONFIG (config); PikaHistogramChannel channel; PikaHistogramChannel old_channel; gboolean success = TRUE; if (! pika_operation_settings_config_serialize_base (config, writer, data) || ! pika_config_serialize_property_by_name (config, "trc", writer) || ! pika_config_serialize_property_by_name (config, "clamp-input", writer) || ! pika_config_serialize_property_by_name (config, "clamp-output", writer)) return FALSE; old_channel = l_config->channel; for (channel = PIKA_HISTOGRAM_VALUE; channel <= PIKA_HISTOGRAM_ALPHA; channel++) { l_config->channel = channel; /* serialize the channel properties manually (not using * pika_config_serialize_properties()), so the parent class' * properties don't end up in the config file one per channel. * See bug #700653. */ success = (pika_config_serialize_property_by_name (config, "channel", writer) && pika_config_serialize_property_by_name (config, "low-input", writer) && pika_config_serialize_property_by_name (config, "high-input", writer) && pika_config_serialize_property_by_name (config, "gamma", writer) && pika_config_serialize_property_by_name (config, "low-output", writer) && pika_config_serialize_property_by_name (config, "high-output", writer)); if (! success) break; } l_config->channel = old_channel; return success; } static gboolean pika_levels_config_deserialize (PikaConfig *config, GScanner *scanner, gint nest_level, gpointer data) { PikaLevelsConfig *l_config = PIKA_LEVELS_CONFIG (config); PikaHistogramChannel old_channel; gboolean success = TRUE; old_channel = l_config->channel; success = pika_config_deserialize_properties (config, scanner, nest_level); g_object_set (config, "channel", old_channel, NULL); return success; } static gboolean pika_levels_config_equal (PikaConfig *a, PikaConfig *b) { PikaLevelsConfig *config_a = PIKA_LEVELS_CONFIG (a); PikaLevelsConfig *config_b = PIKA_LEVELS_CONFIG (b); PikaHistogramChannel channel; if (! pika_operation_settings_config_equal_base (a, b) || config_a->trc != config_b->trc || config_a->clamp_input != config_b->clamp_input || config_a->clamp_output != config_b->clamp_output) return FALSE; for (channel = PIKA_HISTOGRAM_VALUE; channel <= PIKA_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 pika_levels_config_reset (PikaConfig *config) { PikaLevelsConfig *l_config = PIKA_LEVELS_CONFIG (config); PikaHistogramChannel channel; pika_operation_settings_config_reset_base (config); for (channel = PIKA_HISTOGRAM_VALUE; channel <= PIKA_HISTOGRAM_ALPHA; channel++) { l_config->channel = channel; pika_levels_config_reset_channel (l_config); } pika_config_reset_property (G_OBJECT (config), "trc"); pika_config_reset_property (G_OBJECT (config), "channel"); pika_config_reset_property (G_OBJECT (config), "clamp-input"); pika_config_reset_property (G_OBJECT (config), "clamp_output"); } static gboolean pika_levels_config_copy (PikaConfig *src, PikaConfig *dest, GParamFlags flags) { PikaLevelsConfig *src_config = PIKA_LEVELS_CONFIG (src); PikaLevelsConfig *dest_config = PIKA_LEVELS_CONFIG (dest); PikaHistogramChannel channel; if (! pika_operation_settings_config_copy_base (src, dest, flags)) return FALSE; for (channel = PIKA_HISTOGRAM_VALUE; channel <= PIKA_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->trc = src_config->trc; 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), "trc"); 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 pika_levels_config_reset_channel (PikaLevelsConfig *config) { g_return_if_fail (PIKA_IS_LEVELS_CONFIG (config)); g_object_freeze_notify (G_OBJECT (config)); pika_config_reset_property (G_OBJECT (config), "gamma"); pika_config_reset_property (G_OBJECT (config), "low-input"); pika_config_reset_property (G_OBJECT (config), "high-input"); pika_config_reset_property (G_OBJECT (config), "low-output"); pika_config_reset_property (G_OBJECT (config), "high-output"); g_object_thaw_notify (G_OBJECT (config)); } void pika_levels_config_stretch (PikaLevelsConfig *config, PikaHistogram *histogram, gboolean is_color) { g_return_if_fail (PIKA_IS_LEVELS_CONFIG (config)); g_return_if_fail (histogram != NULL); g_object_freeze_notify (G_OBJECT (config)); if (is_color) { PikaHistogramChannel channel; /* Set the overall value to defaults */ channel = config->channel; config->channel = PIKA_HISTOGRAM_VALUE; pika_levels_config_reset_channel (config); config->channel = channel; for (channel = PIKA_HISTOGRAM_RED; channel <= PIKA_HISTOGRAM_BLUE; channel++) { pika_levels_config_stretch_channel (config, histogram, channel); } } else { pika_levels_config_stretch_channel (config, histogram, PIKA_HISTOGRAM_VALUE); } g_object_thaw_notify (G_OBJECT (config)); } void pika_levels_config_stretch_channel (PikaLevelsConfig *config, PikaHistogram *histogram, PikaHistogramChannel channel) { gdouble count; gdouble bias = 0.006; gint n_bins; gint i; g_return_if_fail (PIKA_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 = pika_histogram_n_bins (histogram); count = pika_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 += pika_histogram_get_value (histogram, channel, i); percentage = new_count / count; next_percentage = (new_count + pika_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 += pika_histogram_get_value (histogram, channel, i); percentage = new_count / count; next_percentage = (new_count + pika_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 pika_levels_config_input_from_color (PikaHistogramChannel channel, const PikaRGB *color) { switch (channel) { case PIKA_HISTOGRAM_VALUE: return MAX (MAX (color->r, color->g), color->b); case PIKA_HISTOGRAM_RED: return color->r; case PIKA_HISTOGRAM_GREEN: return color->g; case PIKA_HISTOGRAM_BLUE: return color->b; case PIKA_HISTOGRAM_ALPHA: return color->a; case PIKA_HISTOGRAM_RGB: return MIN (MIN (color->r, color->g), color->b); case PIKA_HISTOGRAM_LUMINANCE: return PIKA_RGB_LUMINANCE (color->r, color->g, color->b); } return 0.0; } void pika_levels_config_adjust_by_colors (PikaLevelsConfig *config, PikaHistogramChannel channel, const PikaRGB *black, const PikaRGB *gray, const PikaRGB *white) { g_return_if_fail (PIKA_IS_LEVELS_CONFIG (config)); g_object_freeze_notify (G_OBJECT (config)); if (black) { config->low_input[channel] = pika_levels_config_input_from_color (channel, black); g_object_notify (G_OBJECT (config), "low-input"); } if (white) { config->high_input[channel] = pika_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 = PIKA_RGB_LUMINANCE (gray->r, gray->g, gray->b); input = pika_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)); } PikaCurvesConfig * pika_levels_config_to_curves_config (PikaLevelsConfig *config) { PikaCurvesConfig *curves; PikaHistogramChannel channel; g_return_val_if_fail (PIKA_IS_LEVELS_CONFIG (config), NULL); curves = g_object_new (PIKA_TYPE_CURVES_CONFIG, NULL); pika_operation_settings_config_copy_base (PIKA_CONFIG (config), PIKA_CONFIG (curves), 0); curves->trc = config->trc; for (channel = PIKA_HISTOGRAM_VALUE; channel <= PIKA_HISTOGRAM_ALPHA; channel++) { PikaCurve *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 */ pika_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]; pika_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 * pika_operation_levels_map_input (config, channel, x); pika_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. */ PikaLevelsConfig *config_inv; gdouble dy = 0; gdouble y0; const gdouble gamma_inv = 1 / gamma; config_inv = pika_config_duplicate (PIKA_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 * pika_operation_levels_map_input (config_inv, channel, y); pika_curve_add_point (curve, x, y); } g_object_unref (config_inv); } } x = config->high_input[channel]; y = config->high_output[channel]; pika_curve_add_point (curve, x, y); } return curves; } gboolean pika_levels_config_load_cruft (PikaLevelsConfig *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 (PIKA_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 = pika_data_input_stream_read_line_always (data_input, &line_len, NULL, error); if (! line) return FALSE; if (strcmp (line, "# PIKA Levels File") != 0) { g_set_error_literal (error, PIKA_CONFIG_ERROR, PIKA_CONFIG_ERROR_PARSE, _("not a PIKA 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 = pika_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->trc = PIKA_TRC_NON_LINEAR; config->clamp_input = TRUE; config->clamp_output = TRUE; g_object_notify (G_OBJECT (config), "trc"); 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, PIKA_CONFIG_ERROR, PIKA_CONFIG_ERROR_PARSE, _("parse error")); return FALSE; } gboolean pika_levels_config_save_cruft (PikaLevelsConfig *config, GOutputStream *output, GError **error) { GString *string; gint i; g_return_val_if_fail (PIKA_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 ("# PIKA 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; }