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 2012,2013 Felix Ulber <felix.ulber@gmx.de>
17  *           2013 Øyvind Kolås <pippin@gimp.org>
18  *           2017 Red Hat, Inc.
19  */
20 
21 #include "config.h"
22 #include <glib/gi18n-lib.h>
23 
24 #ifdef GEGL_PROPERTIES
25 
26 property_double (black_level, _("Black level"), 0.0)
27     description (_("Adjust the black level"))
28     value_range (-0.1, 0.1)
29 
30 property_double (exposure, _("Exposure"), 0.0)
31     description (_("Relative brightness change in stops"))
32     ui_range    (-10.0, 10.0)
33 
34 #else
35 
36 #define GEGL_OP_POINT_FILTER
37 #define GEGL_OP_NAME     exposure
38 #define GEGL_OP_C_SOURCE exposure.c
39 
40 #include "gegl-op.h"
41 #include "opencl/gegl-cl.h"
42 
43 #ifdef _MSC_VER
44 #define exp2f (b) ((gfloat) pow (2.0, b))
45 #endif
46 
47 typedef void (*ProcessFunc) (GeglOperation       *operation,
48                              void                *in_buf,
49                              void                *out_buf,
50                              glong                n_pixels,
51                              const GeglRectangle *roi,
52                              gint                 level);
53 
54 typedef struct
55 {
56   GeglClRunData **cl_data_ptr;
57   ProcessFunc process;
58   const char *kernel_name;
59   const char *kernel_source;
60 } EParamsType;
61 
62 static GeglClRunData *cl_data_rgb = NULL;
63 static GeglClRunData *cl_data_rgba = NULL;
64 static GeglClRunData *cl_data_y = NULL;
65 static GeglClRunData *cl_data_ya = NULL;
66 
67 static const char* kernel_source_rgb =
68 "__kernel void kernel_exposure_rgb(__global const float *in,           \n"
69 "                                  __global       float *out,          \n"
70 "                                  float                 black_level,  \n"
71 "                                  float                 gain)         \n"
72 "{                                                                     \n"
73 "  int gid = get_global_id(0);                                         \n"
74 "  int offset  = 3 * gid;                                              \n"
75 "  float3 in_v = (float3) (in[offset], in[offset + 1], in[offset+2]);  \n"
76 "  float3 out_v;                                                       \n"
77 "  out_v.xyz =  ((in_v.xyz - black_level) * gain);                     \n"
78 "  out[offset]     = out_v.x;                                          \n"
79 "  out[offset + 1] = out_v.y;                                          \n"
80 "  out[offset + 2] = out_v.z;                                          \n"
81 "}                                                                     \n";
82 
83 static const char* kernel_source_rgba =
84 "__kernel void kernel_exposure_rgba(__global const float4 *in,          \n"
85 "                                   __global       float4 *out,         \n"
86 "                                   float                  black_level, \n"
87 "                                   float                  gain)        \n"
88 "{                                                                      \n"
89 "  int gid = get_global_id(0);                                          \n"
90 "  float4 in_v  = in[gid];                                              \n"
91 "  float4 out_v;                                                        \n"
92 "  out_v.xyz =  ((in_v.xyz - black_level) * gain);                      \n"
93 "  out_v.w   =  in_v.w;                                                 \n"
94 "  out[gid]  =  out_v;                                                  \n"
95 "}                                                                      \n";
96 
97 static const char* kernel_source_y =
98 "__kernel void kernel_exposure_y(__global const float *in,             \n"
99 "                                __global       float *out,            \n"
100 "                                float                 black_level,    \n"
101 "                                float                 gain)           \n"
102 "{                                                                     \n"
103 "  int gid = get_global_id(0);                                         \n"
104 "  float in_v  = in[gid];                                              \n"
105 "  float out_v;                                                        \n"
106 "  out_v     =  ((in_v - black_level) * gain);                         \n"
107 "  out[gid]  =  out_v;                                                 \n"
108 "}                                                                     \n";
109 
110 static const char* kernel_source_ya =
111 "__kernel void kernel_exposure_ya(__global const float2 *in,             \n"
112 "                                 __global       float2 *out,            \n"
113 "                                 float                  black_level,    \n"
114 "                                 float                  gain)           \n"
115 "{                                                                       \n"
116 "  int gid = get_global_id(0);                                           \n"
117 "  float2 in_v  = in[gid];                                               \n"
118 "  float2 out_v;                                                         \n"
119 "  out_v.x   =  ((in_v.x - black_level) * gain);                         \n"
120 "  out_v.y   =  in_v.y;                                                  \n"
121 "  out[gid]  =  out_v;                                                   \n"
122 "}                                                                       \n";
123 
124 static void
125 process_rgb (GeglOperation       *op,
126              void                *in_buf,
127              void                *out_buf,
128              glong                n_pixels,
129              const GeglRectangle *roi,
130              gint                 level)
131 {
132   GeglProperties *o = GEGL_PROPERTIES (op);
133   gfloat     *in_pixel;
134   gfloat     *out_pixel;
135   gfloat      black_level = (gfloat) o->black_level;
136   gfloat      diff;
137   gfloat      exposure_negated = (gfloat) -o->exposure;
138   gfloat      gain;
139   gfloat      white;
140 
141   glong       i;
142 
143   in_pixel = in_buf;
144   out_pixel = out_buf;
145 
146   white = exp2f (exposure_negated);
147   diff = MAX (white - black_level, 0.000001);
148   gain = 1.0f / diff;
149 
150   for (i=0; i<n_pixels; i++)
151     {
152       out_pixel[0] = (in_pixel[0] - black_level) * gain;
153       out_pixel[1] = (in_pixel[1] - black_level) * gain;
154       out_pixel[2] = (in_pixel[2] - black_level) * gain;
155 
156       out_pixel += 3;
157       in_pixel  += 3;
158     }
159 }
160 
161 static void
162 process_rgba (GeglOperation       *op,
163               void                *in_buf,
164               void                *out_buf,
165               glong                n_pixels,
166               const GeglRectangle *roi,
167               gint                 level)
168 {
169   GeglProperties *o = GEGL_PROPERTIES (op);
170   gfloat     *in_pixel;
171   gfloat     *out_pixel;
172   gfloat      black_level = (gfloat) o->black_level;
173   gfloat      diff;
174   gfloat      exposure_negated = (gfloat) -o->exposure;
175   gfloat      gain;
176   gfloat      white;
177 
178   glong       i;
179 
180   in_pixel = in_buf;
181   out_pixel = out_buf;
182 
183   white = exp2f (exposure_negated);
184   diff = MAX (white - black_level, 0.000001);
185   gain = 1.0f / diff;
186 
187   for (i=0; i<n_pixels; i++)
188     {
189       out_pixel[0] = (in_pixel[0] - black_level) * gain;
190       out_pixel[1] = (in_pixel[1] - black_level) * gain;
191       out_pixel[2] = (in_pixel[2] - black_level) * gain;
192       out_pixel[3] = in_pixel[3];
193 
194       out_pixel += 4;
195       in_pixel  += 4;
196     }
197 }
198 
199 static void
200 process_y (GeglOperation       *op,
201            void                *in_buf,
202            void                *out_buf,
203            glong                n_pixels,
204            const GeglRectangle *roi,
205            gint                 level)
206 {
207   GeglProperties *o = GEGL_PROPERTIES (op);
208   gfloat     *in_pixel;
209   gfloat     *out_pixel;
210   gfloat      black_level = (gfloat) o->black_level;
211   gfloat      diff;
212   gfloat      exposure_negated = (gfloat) -o->exposure;
213   gfloat      gain;
214   gfloat      white;
215 
216   glong       i;
217 
218   in_pixel = in_buf;
219   out_pixel = out_buf;
220 
221   white = exp2f (exposure_negated);
222   diff = MAX (white - black_level, 0.000001);
223   gain = 1.0f / diff;
224 
225   for (i=0; i<n_pixels; i++)
226     {
227       out_pixel[0] = (in_pixel[0] - black_level) * gain;
228 
229       out_pixel++;
230       in_pixel++;
231     }
232 }
233 
234 static void
235 process_ya (GeglOperation       *op,
236             void                *in_buf,
237             void                *out_buf,
238             glong                n_pixels,
239             const GeglRectangle *roi,
240             gint                 level)
241 {
242   GeglProperties *o = GEGL_PROPERTIES (op);
243   gfloat     *in_pixel;
244   gfloat     *out_pixel;
245   gfloat      black_level = (gfloat) o->black_level;
246   gfloat      diff;
247   gfloat      exposure_negated = (gfloat) -o->exposure;
248   gfloat      gain;
249   gfloat      white;
250 
251   glong       i;
252 
253   in_pixel = in_buf;
254   out_pixel = out_buf;
255 
256   white = exp2f (exposure_negated);
257   diff = MAX (white - black_level, 0.000001);
258   gain = 1.0f / diff;
259 
260   for (i=0; i<n_pixels; i++)
261     {
262       out_pixel[0] = (in_pixel[0] - black_level) * gain;
263       out_pixel[1] = in_pixel[1];
264 
265       out_pixel += 2;
266       in_pixel  += 2;
267     }
268 }
269 
270 static void
271 prepare (GeglOperation *operation)
272 {
273   GeglProperties *o = GEGL_PROPERTIES (operation);
274   const Babl *space = gegl_operation_get_source_space (operation, "input");
275   EParamsType *params;
276   const Babl *format;
277   const Babl *input_format;
278   const Babl *input_model;
279   const Babl *y_model;
280 
281   if (o->user_data == NULL)
282     o->user_data = g_slice_new0 (EParamsType);
283 
284   params = (EParamsType *) o->user_data;
285 
286   input_format = gegl_operation_get_source_format (operation, "input");
287   if (input_format == NULL)
288     {
289       format = babl_format ("RGBA float");
290 
291       params->process = process_rgba;
292 
293       params->cl_data_ptr = &cl_data_rgba;
294       params->kernel_name = "kernel_exposure_rgba";
295       params->kernel_source = kernel_source_rgba;
296       goto out;
297     }
298 
299   input_model = babl_format_get_model (input_format);
300 
301   if (babl_format_has_alpha (input_format))
302     {
303       y_model = babl_model_with_space ("YA", space);
304       if (input_model == y_model)
305         {
306           format = babl_format_with_space ("YA float", space);
307 
308           params->process = process_ya;
309 
310           params->cl_data_ptr = &cl_data_ya;
311           params->kernel_name = "kernel_exposure_ya";
312           params->kernel_source = kernel_source_ya;
313         }
314       else
315         {
316           format = babl_format_with_space ("RGBA float", space);
317 
318           params->process = process_rgba;
319 
320           params->cl_data_ptr = &cl_data_rgba;
321           params->kernel_name = "kernel_exposure_rgba";
322           params->kernel_source = kernel_source_rgba;
323         }
324     }
325   else
326     {
327       y_model = babl_model_with_space ("Y", space);
328       if (input_model == y_model)
329         {
330           format = babl_format_with_space ("Y float", space);
331 
332           params->process = process_y;
333 
334           params->cl_data_ptr = &cl_data_y;
335           params->kernel_name = "kernel_exposure_y";
336           params->kernel_source = kernel_source_y;
337         }
338       else
339         {
340           format = babl_format_with_space ("RGB float", space);
341 
342           params->process = process_rgb;
343 
344           params->cl_data_ptr = &cl_data_rgb;
345           params->kernel_name = "kernel_exposure_rgb";
346           params->kernel_source = kernel_source_rgb;
347         }
348     }
349 
350  out:
351   gegl_operation_set_format (operation, "input", format);
352   gegl_operation_set_format (operation, "output", format);
353 }
354 
355 /* GeglOperationPointFilter gives us a linear buffer to operate on
356  * in our requested pixel format
357  */
358 static gboolean
359 process (GeglOperation       *operation,
360          void                *in_buf,
361          void                *out_buf,
362          glong                n_pixels,
363          const GeglRectangle *roi,
364          gint                 level)
365 {
366   GeglProperties *o = GEGL_PROPERTIES (operation);
367   EParamsType *params = (EParamsType *) o->user_data;
368 
369   params->process (operation, in_buf, out_buf, n_pixels, roi, level);
370   return TRUE;
371 }
372 
373 /* OpenCL processing function */
374 static cl_int
375 cl_process (GeglOperation       *op,
376             cl_mem               in_tex,
377             cl_mem               out_tex,
378             size_t               global_worksize,
379             const GeglRectangle *roi,
380             gint                 level)
381 {
382   /* Retrieve a pointer to GeglProperties structure which contains all the
383    * chanted properties
384    */
385 
386   GeglProperties *o = GEGL_PROPERTIES (op);
387   EParamsType *params = (EParamsType *) o->user_data;
388 
389   gfloat      black_level = (gfloat) o->black_level;
390   gfloat      diff;
391   gfloat      exposure_negated = (gfloat) -o->exposure;
392   gfloat      gain;
393   gfloat      white;
394 
395   GeglClRunData *cl_data_local;
396   cl_int cl_err = 0;
397 
398   if (*params->cl_data_ptr == NULL)
399     {
400       const char *kernel_name[] = {NULL, NULL};
401 
402       kernel_name[0] = params->kernel_name;
403       *params->cl_data_ptr = gegl_cl_compile_and_build (params->kernel_source, kernel_name);
404     }
405   if (*params->cl_data_ptr == NULL) return 1;
406 
407   cl_data_local = *params->cl_data_ptr;
408 
409   white = exp2f (exposure_negated);
410   diff = MAX (white - black_level, 0.000001);
411   gain = 1.0f / diff;
412 
413   cl_err |= gegl_clSetKernelArg(cl_data_local->kernel[0], 0, sizeof(cl_mem),   (void*)&in_tex);
414   cl_err |= gegl_clSetKernelArg(cl_data_local->kernel[0], 1, sizeof(cl_mem),   (void*)&out_tex);
415   cl_err |= gegl_clSetKernelArg(cl_data_local->kernel[0], 2, sizeof(cl_float), (void*)&black_level);
416   cl_err |= gegl_clSetKernelArg(cl_data_local->kernel[0], 3, sizeof(cl_float), (void*)&gain);
417   if (cl_err != CL_SUCCESS) return cl_err;
418 
419   cl_err = gegl_clEnqueueNDRangeKernel(gegl_cl_get_command_queue (),
420                                         cl_data_local->kernel[0], 1,
421                                         NULL, &global_worksize, NULL,
422                                         0, NULL, NULL);
423   if (cl_err != CL_SUCCESS) return cl_err;
424 
425   return cl_err;
426 }
427 
428 static void
429 finalize (GObject *object)
430 {
431   GeglOperation *op = GEGL_OPERATION (object);
432   GeglProperties *o = GEGL_PROPERTIES (op);
433 
434   if (o->user_data)
435     g_slice_free (EParamsType, o->user_data);
436 
437   G_OBJECT_CLASS (gegl_op_parent_class)->finalize (object);
438 }
439 
440 static void
441 gegl_op_class_init (GeglOpClass *klass)
442 {
443   GObjectClass                  *object_class;
444   GeglOperationClass            *operation_class;
445   GeglOperationPointFilterClass *point_filter_class;
446   gchar                         *composition =
447     "<?xml version='1.0' encoding='UTF-8'?>"
448     "<gegl>"
449     "  <node operation='gegl:crop' width='200' height='200'/>"
450     "  <node operation='gegl:over'>"
451     "    <node operation='gegl:exposure'>"
452     "      <params>"
453     "        <param name='exposure'>1.5</param>"
454     "      </params>"
455     "    </node>"
456     "    <node operation='gegl:load' path='standard-input.png'/>"
457     "  </node>"
458     "  <node operation='gegl:checkerboard'>"
459     "    <params>"
460     "      <param name='color1'>rgb(0.25,0.25,0.25)</param>"
461     "      <param name='color2'>rgb(0.75,0.75,0.75)</param>"
462     "    </params>"
463     "  </node>"
464     "</gegl>";
465 
466   object_class       = G_OBJECT_CLASS (klass);
467   operation_class    = GEGL_OPERATION_CLASS (klass);
468   point_filter_class = GEGL_OPERATION_POINT_FILTER_CLASS (klass);
469 
470   object_class->finalize = finalize;
471 
472   operation_class->opencl_support = TRUE;
473   operation_class->prepare        = prepare;
474 
475   point_filter_class->process    = process;
476   point_filter_class->cl_process = cl_process;
477 
478   gegl_operation_class_set_keys (operation_class,
479     "name",        "gegl:exposure",
480     "title",       _("Exposure"),
481     "categories",  "color",
482     "reference-hash", "a4ae5d7f933046aa462e0f7659bd1261",
483     "reference-composition", composition,
484     "description", _("Change exposure of an image in shutter speed stops"),
485     "op-version",  "1:0",
486     NULL);
487 }
488 
489 #endif
490