1 /* This file is an image processing operation for GEGL
2 *
3 * GEGL is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU Lesser General Public
5 * License as published by the Free Software Foundation; either
6 * version 3 of the License, or (at your option) any later version.
7 *
8 * GEGL is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with GEGL; if not, see <https://www.gnu.org/licenses/>.
15 *
16 * Copyright 1996 Federico Mena Quintero
17 * Copyright 1997 Scott Goehring
18 * Copyright 2006 Øyvind Kolås <pippin@gimp.org>
19 * Copyright 2013 Carlos Zubieta <czubieta.dev@gmail.com>
20 *
21 */
22
23 #include "config.h"
24 #include <glib/gi18n-lib.h>
25
26 #ifdef GEGL_PROPERTIES
27
28 #else
29
30 #define GEGL_OP_FILTER
31 #define GEGL_OP_NAME stretch_contrast_hsv
32 #define GEGL_OP_C_SOURCE stretch-contrast-hsv.c
33
34 #include "gegl-op.h"
35
36 typedef struct {
37 gfloat slo;
38 gfloat sdiff;
39 gfloat vlo;
40 gfloat vdiff;
41 } AutostretchData;
42
43 static void
buffer_get_auto_stretch_data(GeglOperation * operation,GeglBuffer * buffer,const GeglRectangle * result,AutostretchData * data,const Babl * space)44 buffer_get_auto_stretch_data (GeglOperation *operation,
45 GeglBuffer *buffer,
46 const GeglRectangle *result,
47 AutostretchData *data,
48 const Babl *space)
49 {
50 gfloat smin = G_MAXFLOAT;
51 gfloat smax = -G_MAXFLOAT;
52 gfloat vmin = G_MAXFLOAT;
53 gfloat vmax = -G_MAXFLOAT;
54
55 GeglBufferIterator *gi;
56 gint done_pixels = 0;
57
58 gegl_operation_progress (operation, 0.0, "");
59
60 gi = gegl_buffer_iterator_new (buffer, result, 0, babl_format_with_space ("HSVA float", space),
61 GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 1);
62
63 while (gegl_buffer_iterator_next (gi))
64 {
65 gfloat *buf = gi->items[0].data;
66 gint i;
67
68 for (i = 0; i < gi->length; i++)
69 {
70 gfloat sval = buf[1];
71 gfloat vval = buf[2];
72
73 smin = MIN (sval, smin);
74 smax = MAX (sval, smax);
75 vmin = MIN (vval, vmin);
76 vmax = MAX (vval, vmax);
77
78 buf += 4;
79 }
80
81 done_pixels += gi->length;
82
83 gegl_operation_progress (operation,
84 (gdouble) 0.5 * done_pixels /
85 (gdouble) (result->width * result->height),
86 "");
87 }
88
89 if (data)
90 {
91 data->slo = smin;
92 data->sdiff = smax - smin;
93 data->vlo = vmin;
94 data->vdiff = vmax - vmin;
95 }
96
97 gegl_operation_progress (operation, 0.5, "");
98 }
99
100 static void
clean_autostretch_data(AutostretchData * data)101 clean_autostretch_data (AutostretchData *data)
102 {
103 if (data->sdiff < GEGL_FLOAT_EPSILON)
104 {
105 data->sdiff = 1.0;
106 data->slo = 0.0;
107 }
108
109 if (data->vdiff < GEGL_FLOAT_EPSILON)
110 {
111 data->vdiff = 1.0;
112 data->vlo = 0.0;
113 }
114 }
115
116 static void
prepare(GeglOperation * operation)117 prepare (GeglOperation *operation)
118 {
119 const Babl *space = gegl_operation_get_source_space (operation, "input");
120 gegl_operation_set_format (operation, "input", babl_format_with_space ("HSVA float", space));
121 gegl_operation_set_format (operation, "output", babl_format_with_space ("HSVA float", space));
122 }
123
124 static GeglRectangle
get_required_for_output(GeglOperation * operation,const gchar * input_pad,const GeglRectangle * roi)125 get_required_for_output (GeglOperation *operation,
126 const gchar *input_pad,
127 const GeglRectangle *roi)
128 {
129 GeglRectangle result = *gegl_operation_source_get_bounding_box (operation, "input");
130
131 /* Don't request an infinite plane */
132 if (gegl_rectangle_is_infinite_plane (&result))
133 return *roi;
134
135 return result;
136 }
137
138 static GeglRectangle
get_cached_region(GeglOperation * operation,const GeglRectangle * roi)139 get_cached_region (GeglOperation *operation,
140 const GeglRectangle *roi)
141 {
142 GeglRectangle result = *gegl_operation_source_get_bounding_box (operation, "input");
143
144 if (gegl_rectangle_is_infinite_plane (&result))
145 return *roi;
146
147 return result;
148 }
149
150 static gboolean
process(GeglOperation * operation,GeglBuffer * input,GeglBuffer * output,const GeglRectangle * result,gint level)151 process (GeglOperation *operation,
152 GeglBuffer *input,
153 GeglBuffer *output,
154 const GeglRectangle *result,
155 gint level)
156 {
157 const Babl *space = gegl_operation_get_format (operation, "output");
158 AutostretchData data;
159 GeglBufferIterator *gi;
160 gint done_pixels = 0;
161
162 buffer_get_auto_stretch_data (operation, input, result, &data, space);
163 clean_autostretch_data (&data);
164
165 gegl_operation_progress (operation, 0.5, "");
166
167 gi = gegl_buffer_iterator_new (input, result, 0, babl_format_with_space ("HSVA float", space),
168 GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 2);
169
170 gegl_buffer_iterator_add (gi, output, result, 0, babl_format_with_space ("HSVA float", space),
171 GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE);
172
173 while (gegl_buffer_iterator_next (gi))
174 {
175 gfloat *in = gi->items[0].data;
176 gfloat *out = gi->items[1].data;
177 gint i;
178
179 for (i = 0; i < gi->length; i++)
180 {
181 out[0] = in[0]; /* Keep hue */
182 out[1] = (in[1] - data.slo) / data.sdiff;
183 out[2] = (in[2] - data.vlo) / data.vdiff;
184 out[3] = in[3]; /* Keep alpha */
185
186 in += 4;
187 out += 4;
188 }
189
190 done_pixels += gi->length;
191
192 gegl_operation_progress (operation,
193 0.5 +
194 (gdouble) 0.5 * done_pixels /
195 (gdouble) (result->width * result->height),
196 "");
197 }
198
199 gegl_operation_progress (operation, 1.0, "");
200
201 return TRUE;
202 }
203
204 /* Pass-through when trying to perform a reduction on an infinite plane
205 */
206 static gboolean
operation_process(GeglOperation * operation,GeglOperationContext * context,const gchar * output_prop,const GeglRectangle * result,gint level)207 operation_process (GeglOperation *operation,
208 GeglOperationContext *context,
209 const gchar *output_prop,
210 const GeglRectangle *result,
211 gint level)
212 {
213 GeglOperationClass *operation_class;
214
215 const GeglRectangle *in_rect =
216 gegl_operation_source_get_bounding_box (operation, "input");
217
218 operation_class = GEGL_OPERATION_CLASS (gegl_op_parent_class);
219
220 if (in_rect && gegl_rectangle_is_infinite_plane (in_rect))
221 {
222 gpointer in = gegl_operation_context_get_object (context, "input");
223 gegl_operation_context_take_object (context, "output",
224 g_object_ref (G_OBJECT (in)));
225 return TRUE;
226 }
227
228 /* chain up, which will create the needed buffers for our actual
229 * process function
230 */
231 return operation_class->process (operation, context, output_prop, result,
232 gegl_operation_context_get_level (context));
233 }
234
235 static void
gegl_op_class_init(GeglOpClass * klass)236 gegl_op_class_init (GeglOpClass *klass)
237 {
238 GeglOperationClass *operation_class;
239 GeglOperationFilterClass *filter_class;
240
241 operation_class = GEGL_OPERATION_CLASS (klass);
242 filter_class = GEGL_OPERATION_FILTER_CLASS (klass);
243
244 filter_class->process = process;
245 operation_class->prepare = prepare;
246 operation_class->process = operation_process;
247 operation_class->threaded = FALSE;
248 operation_class->get_required_for_output = get_required_for_output;
249 operation_class->get_cached_region = get_cached_region;
250
251 gegl_operation_class_set_keys (operation_class,
252 "name", "gegl:stretch-contrast-hsv",
253 "title", _("Stretch Contrast HSV"),
254 "categories", "color:enhance",
255 "reference-hash", "c7802207f601127c78bf11314af1fc16",
256 "description",
257 _("Scales the components of the buffer to be in the 0.0-1.0 range. "
258 "This improves images that make poor use of the available contrast "
259 "(little contrast, very dark, or very bright images). "
260 "This version differs from Contrast Autostretch in that it works "
261 "in HSV space, and preserves hue."),
262 NULL);
263 }
264
265 #endif
266