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