1 /* This file is an image processing operation for GEGL
2  *
3  * This program is free software: you can redistribute it and/or modify
4  * it under the terms of the GNU General Public License as published by
5  * the Free Software Foundation; either version 3 of the License, or
6  * (at your option) any later version.
7  *
8  * This program 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
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
15  *
16  * Copyright 2001 Spencer Kimball, Bit Specialists, Inc.
17  * Copyright 2011 Hans Lo <hansshulo@gmail.com>
18  */
19 
20 
21 #include "config.h"
22 #include <glib/gi18n-lib.h>
23 
24 #ifdef GEGL_PROPERTIES
25 
26 property_double (mask_radius, _("Mask Radius"), 10.0)
27     value_range (0.0, 50.0)
28 
29 property_double (sharpness, _("Sharpness"), 0.5)
30     value_range (0.0, 1.0)
31 
32 property_double (black, _("Percent Black"), 0.2)
33     value_range (0.0, 1.0)
34 
35 property_double (white, _("Percent White"), 0.2)
36     value_range (0.0, 1.0)
37 
38 #else
39 
40 #define GEGL_OP_FILTER
41 #define GEGL_OP_NAME     photocopy
42 #define GEGL_OP_C_SOURCE photocopy.c
43 
44 #include "gegl-op.h"
45 
46 #define THRESHOLD 0.75
47 
48 static void
49 grey_blur_buffer (GeglBuffer  *input,
50                   gdouble      sharpness,
51                   gdouble      mask_radius,
52                   GeglBuffer **dest1,
53                   GeglBuffer **dest2)
54 {
55   GeglNode *gegl, *image, *write1, *write2, *blur1, *blur2;
56   gdouble radius, std_dev1, std_dev2;
57 
58   gegl = gegl_node_new ();
59   image = gegl_node_new_child (gegl,
60                                "operation", "gegl:buffer-source",
61                                "buffer", input,
62                                NULL);
63 
64   radius   = MAX (1.0, 10 * (1.0 - sharpness));
65   radius   = fabs (radius) + 1.0;
66   std_dev1 = sqrt (-(radius * radius) / (2 * log (1.0 / 255.0)));
67 
68   radius   = fabs (mask_radius) + 1.0;
69   std_dev2 = sqrt (-(radius * radius) / (2 * log (1.0 / 255.0)));
70 
71   blur1 =  gegl_node_new_child (gegl,
72                                 "operation", "gegl:gaussian-blur",
73                                 "std_dev_x", std_dev1,
74                                 "std_dev_y", std_dev1,
75                                 NULL);
76   blur2 =  gegl_node_new_child (gegl,
77                                 "operation", "gegl:gaussian-blur",
78                                 "std_dev_x", std_dev2,
79                                 "std_dev_y", std_dev2,
80                                 NULL);
81 
82   write1 = gegl_node_new_child (gegl,
83                                 "operation", "gegl:buffer-sink",
84                                 "buffer", dest1, NULL);
85 
86   write2 = gegl_node_new_child (gegl,
87                                 "operation", "gegl:buffer-sink",
88                                 "buffer", dest2, NULL);
89 
90   gegl_node_link_many (image, blur1, write1, NULL);
91   gegl_node_process (write1);
92 
93   gegl_node_link_many (image, blur2, write2, NULL);
94   gegl_node_process (write2);
95 
96   g_object_unref (gegl);
97 }
98 
99 static gdouble
100 calculate_threshold (gint    *hist,
101                      gdouble  pct,
102                      gint     count,
103                      gint     under_threshold)
104 {
105   gint    sum;
106   gint    i;
107 
108   if (pct == 0.0 || count == 0)
109     return (under_threshold ? 1.0 : 0.0);
110 
111   sum = 0;
112   for (i = 0; i < 2000; i++)
113     {
114       sum += hist[i];
115       if (((gdouble) sum / (gdouble) count) > pct)
116         {
117           if (under_threshold)
118             return (THRESHOLD - (gdouble) i / 1000.0);
119           else
120             return ((gdouble) i / 1000.0 - THRESHOLD);
121         }
122     }
123 
124   return (under_threshold ? 0.0 : 1.0);
125 }
126 
127 static void
128 compute_ramp (GeglBuffer          *dest1,
129               GeglBuffer          *dest2,
130               const GeglRectangle *roi,
131               gdouble              pct_black,
132               gdouble              pct_white,
133               gboolean             under_threshold,
134               gdouble             *threshold_black,
135               gdouble             *threshold_white)
136 {
137   GeglBufferIterator *iter;
138 
139   gint    hist1[2000];
140   gint    hist2[2000];
141   gdouble diff;
142   gint    count;
143 
144   iter = gegl_buffer_iterator_new (dest1, roi, 0,
145                                    babl_format ("Y float"),
146                                    GEGL_ACCESS_READ,
147                                    GEGL_ABYSS_NONE, 2);
148   gegl_buffer_iterator_add (iter, dest2, roi, 0,
149                             babl_format ("Y float"),
150                             GEGL_ACCESS_READ,
151                             GEGL_ABYSS_NONE);
152 
153   memset (hist1, 0, sizeof (int) * 2000);
154   memset (hist2, 0, sizeof (int) * 2000);
155   count = 0;
156 
157   while (gegl_buffer_iterator_next (iter))
158   {
159     gint n_pixels = iter->length;
160     gfloat *ptr1  = iter->items[0].data;
161     gfloat *ptr2  = iter->items[1].data;
162 
163     while (n_pixels--)
164       {
165         diff = *ptr1 / *ptr2;
166         ptr1++;
167         ptr2++;
168 
169         if (under_threshold)
170           {
171             if (diff < THRESHOLD && diff >= 0.0f)
172               {
173                 hist2[(int) (diff * 1000)] ++;
174                 count ++;
175               }
176           }
177         else
178           {
179             if (diff >= THRESHOLD && diff < 2.0)
180               {
181                 hist1[(int) (diff * 1000)] ++;
182                 count ++;
183               }
184           }
185 
186       }
187   }
188 
189   *threshold_black = calculate_threshold (hist1, pct_black, count, 0);
190   *threshold_white = calculate_threshold (hist2, pct_white, count, 1);
191 }
192 
193 static void
194 prepare (GeglOperation *operation)
195 {
196   gegl_operation_set_format (operation, "input",
197                              babl_format ("Y float"));
198   gegl_operation_set_format (operation, "output",
199                              babl_format ("Y float"));
200 }
201 
202 static GeglRectangle
203 get_required_for_output (GeglOperation       *operation,
204                          const gchar         *input_pad,
205                          const GeglRectangle *roi)
206 {
207   GeglRectangle result = *gegl_operation_source_get_bounding_box (operation, "input");
208 
209   /* Don't request an infinite plane */
210   if (gegl_rectangle_is_infinite_plane (&result))
211     return *roi;
212 
213   return result;
214 }
215 
216 static GeglRectangle
217 get_cached_region (GeglOperation       *operation,
218                    const GeglRectangle *roi)
219 {
220   GeglRectangle result = *gegl_operation_source_get_bounding_box (operation, "input");
221 
222   if (gegl_rectangle_is_infinite_plane (&result))
223     return *roi;
224 
225   return result;
226 }
227 
228 static gboolean
229 process (GeglOperation       *operation,
230          GeglBuffer          *input,
231          GeglBuffer          *output,
232          const GeglRectangle *result,
233          gint                 level)
234 {
235   GeglProperties *o = GEGL_PROPERTIES (operation);
236 
237   GeglBufferIterator *iter;
238 
239   GeglBuffer *dest1;
240   GeglBuffer *dest2;
241 
242   gdouble diff;
243   gdouble ramp_down;
244   gdouble ramp_up;
245   gdouble mult;
246 
247   grey_blur_buffer (input, o->sharpness, o->mask_radius, &dest1, &dest2);
248 
249   compute_ramp (dest1, dest2, result,
250                 o->black, o->white, TRUE,
251                 &ramp_down, &ramp_up);
252 
253   iter = gegl_buffer_iterator_new (dest1, result, 0,
254                                    babl_format ("Y float"),
255                                    GEGL_ACCESS_READ,
256                                    GEGL_ABYSS_NONE, 4);
257   gegl_buffer_iterator_add (iter, dest2, result, 0,
258                             babl_format ("Y float"),
259                             GEGL_ACCESS_READ,
260                             GEGL_ABYSS_NONE);
261   gegl_buffer_iterator_add (iter, output, result, 0,
262                             babl_format ("Y float"),
263                             GEGL_ACCESS_WRITE,
264                             GEGL_ABYSS_NONE);
265 
266   while (gegl_buffer_iterator_next (iter))
267     {
268       gint    n_pixels  = iter->length;
269       gfloat *ptr1      = iter->items[0].data;
270       gfloat *ptr2      = iter->items[1].data;
271       gfloat *out_pixel = iter->items[2].data;
272 
273       while (n_pixels--)
274         {
275           diff = *ptr1 / *ptr2;
276           if (diff < THRESHOLD)
277             {
278               if (ramp_down == 0.0)
279                 *out_pixel = 0.0;
280               else
281                 {
282                   mult = (ramp_down - MIN (ramp_down,
283                                            (THRESHOLD - diff))) / ramp_down;
284                   *out_pixel = *ptr1 * mult;
285                 }
286             }
287           else
288             {
289               if (ramp_up == 0.0)
290                 mult = 1.0;
291               else
292                 mult = MIN (ramp_up,
293                             (diff - THRESHOLD)) / ramp_up;
294 
295               *out_pixel = mult + *ptr1 - mult * *ptr1;
296             }
297 
298           ptr1++;
299           ptr2++;
300           out_pixel++;
301         }
302     }
303 
304   g_object_unref (dest1);
305   g_object_unref (dest2);
306 
307   return TRUE;
308 }
309 
310 static void
311 gegl_op_class_init (GeglOpClass *klass)
312 {
313   GeglOperationClass       *operation_class;
314   GeglOperationFilterClass *filter_class;
315 
316   operation_class = GEGL_OPERATION_CLASS (klass);
317   filter_class    = GEGL_OPERATION_FILTER_CLASS (klass);
318 
319   operation_class->prepare                 = prepare;
320   operation_class->get_required_for_output = get_required_for_output;
321   operation_class->get_cached_region       = get_cached_region;
322   operation_class->threaded                = FALSE;
323   filter_class->process                    = process;
324 
325   gegl_operation_class_set_keys (operation_class,
326     "name",          "gegl:photocopy",
327     "categories",    "artistic",
328     "license",       "GPL3+",
329     "title",         _("Photocopy"),
330     "reference-hash", "d2f210ce9e61b81ebd58a5eb7dfe9dd7",
331     "reference-hashB", "53a9c82b6983adb663c5af8170a64b3d",
332     "description", _("Simulate color distortion produced by a copy machine"),
333     NULL);
334 }
335 #endif
336