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_to_cruft (GimpLevelsConfig *config, Levels *cruft, gboolean is_color) { GimpHistogramChannel channel; g_return_if_fail (GIMP_IS_LEVELS_CONFIG (config)); g_return_if_fail (cruft != NULL); for (channel = GIMP_HISTOGRAM_VALUE; channel <= GIMP_HISTOGRAM_ALPHA; channel++) { cruft->gamma[channel] = config->gamma[channel]; cruft->low_input[channel] = config->low_input[channel] * 255.999; cruft->high_input[channel] = config->high_input[channel] * 255.999; cruft->low_output[channel] = config->low_output[channel] * 255.999; cruft->high_output[channel] = config->high_output[channel] * 255.999; } if (! is_color) { cruft->gamma[1] = cruft->gamma[GIMP_HISTOGRAM_ALPHA]; cruft->low_input[1] = cruft->low_input[GIMP_HISTOGRAM_ALPHA]; cruft->high_input[1] = cruft->high_input[GIMP_HISTOGRAM_ALPHA]; cruft->low_output[1] = cruft->low_output[GIMP_HISTOGRAM_ALPHA]; cruft->high_output[1] = cruft->high_output[GIMP_HISTOGRAM_ALPHA]; } }
gboolean gimp_levels_config_save_cruft (GimpLevelsConfig *config, gpointer fp, GError **error) { FILE *file = fp; gint i; g_return_val_if_fail (GIMP_IS_LEVELS_CONFIG (config), FALSE); g_return_val_if_fail (file != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); fprintf (file, "# GIMP Levels File\n"); for (i = 0; i < 5; i++) { gchar buf[G_ASCII_DTOSTR_BUF_SIZE]; fprintf (file, "%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_formatd (buf, G_ASCII_DTOSTR_BUF_SIZE, "%f", config->gamma[i])); } return TRUE; }
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)); }
gboolean gimp_levels_config_save_cruft (GimpLevelsConfig *config, GOutputStream *output, GError **error) { GString *string; gsize bytes_written; 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_formatd (buf, G_ASCII_DTOSTR_BUF_SIZE, "%f", config->gamma[i])); } if (! g_output_stream_write_all (output, string->str, string->len, &bytes_written, NULL, error) || bytes_written != string->len) { g_prefix_error (error, _("Writing levels file failed: ")); g_string_free (string, TRUE); return FALSE; } g_string_free (string, TRUE); return TRUE; }
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 = g_data_input_stream_read_line (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 = g_data_input_stream_read_line (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->low_output[i] = low_output[i] / 255.0; config->high_output[i] = high_output[i] / 255.0; config->gamma[i] = gamma[i]; } 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)); return TRUE; error: g_object_unref (data_input); g_set_error_literal (error, GIMP_CONFIG_ERROR, GIMP_CONFIG_ERROR_PARSE, _("parse error")); return FALSE; }
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); for (channel = GIMP_HISTOGRAM_VALUE; channel <= GIMP_HISTOGRAM_ALPHA; channel++) { GimpCurve *curve = curves->curve[channel]; const gint n_points = gimp_curve_get_n_points (curve); static const gint n = 4; gint point = -1; gdouble gamma = config->gamma[channel]; gdouble delta_in; gdouble delta_out; gdouble x, y; /* clear the points set by default */ gimp_curve_set_point (curve, 0, -1, -1); gimp_curve_set_point (curve, n_points - 1, -1, -1); 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]; point = CLAMP (n_points * x, point + 1, n_points - 1 - n); gimp_curve_set_point (curve, point, x, y); if (delta_out != 0 && gamma != 1.0) { /* The Levels tool performs gamma correction, which is a * power law, while the Curves tool uses cubic Bézier * curves. Here we try to approximate this gamma correction * 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); point = CLAMP (n_points * x, point + 1, n_points - 1 - n + i); gimp_curve_set_point (curve, point, 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); point = CLAMP (n_points * x, point + 1, n_points - 1 - n + i); gimp_curve_set_point (curve, point, x, y); } g_object_unref (config_inv); } } x = config->high_input[channel]; y = config->high_output[channel]; point = CLAMP (n_points * x, point + 1, n_points - 1); gimp_curve_set_point (curve, point, x, y); } return curves; }
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)); }
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)); }
gboolean gimp_levels_config_load_cruft (GimpLevelsConfig *config, gpointer fp, GError **error) { FILE *file = fp; gint low_input[5]; gint high_input[5]; gint low_output[5]; gint high_output[5]; gdouble gamma[5]; gint i; gint fields; gchar buf[50]; gchar *nptr; g_return_val_if_fail (GIMP_IS_LEVELS_CONFIG (config), FALSE); g_return_val_if_fail (file != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); if (! fgets (buf, sizeof (buf), file) || strcmp (buf, "# GIMP Levels File\n") != 0) { g_set_error_literal (error, GIMP_CONFIG_ERROR, GIMP_CONFIG_ERROR_PARSE, _("not a GIMP Levels file")); return FALSE; } for (i = 0; i < 5; i++) { fields = fscanf (file, "%d %d %d %d ", &low_input[i], &high_input[i], &low_output[i], &high_output[i]); if (fields != 4) goto error; if (! fgets (buf, 50, file)) goto error; gamma[i] = g_ascii_strtod (buf, &nptr); if (buf == nptr || errno == ERANGE) goto error; } 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->low_output[i] = low_output[i] / 255.0; config->high_output[i] = high_output[i] / 255.0; config->gamma[i] = gamma[i]; } 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)); return TRUE; error: g_set_error_literal (error, GIMP_CONFIG_ERROR, GIMP_CONFIG_ERROR_PARSE, _("parse error")); return FALSE; }
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) return; input -= config->low_input[channel]; if (input < 0) return; /* Normalize input and lightness */ inten = input / range; out_light = lightness/ range; if (out_light <= 0) return; /* Map selected color to corresponding lightness */ config->gamma[channel] = log (inten) / log (out_light); g_object_notify (G_OBJECT (config), "gamma"); } g_object_thaw_notify (G_OBJECT (config)); }