Tizen 2.0 Release
[framework/multimedia/gst-plugins-bad0.10.git] / gst / gaudieffects / gstgaussblur.c
1 #ifdef HAVE_CONFIG_H
2 #  include <config.h>
3 #endif
4
5 #include <math.h>
6 #include <gst/gst.h>
7 #include <gst/controller/gstcontroller.h>
8
9 #include "gstplugin.h"
10 #include "gstgaussblur.h"
11
12 static gboolean gauss_blur_stop (GstBaseTransform * btrans);
13 static gboolean gauss_blur_set_caps (GstBaseTransform * btrans,
14     GstCaps * incaps, GstCaps * outcaps);
15 static GstFlowReturn gauss_blur_process_frame (GstBaseTransform * btrans,
16     GstBuffer * in_buf, GstBuffer * out_buf);
17
18 static void gauss_blur_set_property (GObject * object,
19     guint prop_id, const GValue * value, GParamSpec * pspec);
20 static void gauss_blur_get_property (GObject * object,
21     guint prop_id, GValue * value, GParamSpec * pspec);
22
23 GST_DEBUG_CATEGORY_STATIC (gst_gauss_blur_debug);
24 #define GST_CAT_DEFAULT gst_gauss_blur_debug
25
26 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
27 #define CAPS_STR_RGB GST_VIDEO_CAPS_BGRx ";" GST_VIDEO_CAPS_RGBx
28 #else
29 #define CAPS_STR_RGB GST_VIDEO_CAPS_xRGB ";" GST_VIDEO_CAPS_xBGR
30 #endif
31
32 #define CAPS_STR GST_VIDEO_CAPS_YUV("AYUV")
33
34 /* The capabilities of the inputs and outputs. */
35 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
36     GST_PAD_SINK,
37     GST_PAD_ALWAYS,
38     GST_STATIC_CAPS (CAPS_STR)
39     );
40
41 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
42     GST_PAD_SRC,
43     GST_PAD_ALWAYS,
44     GST_STATIC_CAPS (CAPS_STR)
45     );
46
47 enum
48 {
49   PROP_0,
50   PROP_SIGMA,
51   PROP_LAST
52 };
53
54 static void cleanup (GaussBlur * gb);
55 static gboolean make_gaussian_kernel (GaussBlur * gb, float sigma);
56 static void gaussian_smooth (GaussBlur * gb, guint8 * image,
57     guint8 * out_image);
58
59 GST_BOILERPLATE (GaussBlur, gauss_blur, GstVideoFilter, GST_TYPE_VIDEO_FILTER);
60
61 #define DEFAULT_SIGMA 1.2
62
63 static void
64 gauss_blur_base_init (gpointer gclass)
65 {
66   GstElementClass *element_class = GST_ELEMENT_CLASS (gclass);
67
68   gst_element_class_set_details_simple (element_class,
69       "GaussBlur",
70       "Filter/Effect/Video",
71       "Perform Gaussian blur/sharpen on a video",
72       "Jan Schmidt <thaytan@noraisin.net>");
73
74   gst_element_class_add_static_pad_template (element_class, &src_factory);
75   gst_element_class_add_static_pad_template (element_class, &sink_factory);
76 }
77
78 static void
79 gauss_blur_class_init (GaussBlurClass * klass)
80 {
81   GObjectClass *object_class = (GObjectClass *) klass;
82   GstBaseTransformClass *trans_class = (GstBaseTransformClass *) klass;
83
84   object_class->set_property = gauss_blur_set_property;
85   object_class->get_property = gauss_blur_get_property;
86
87   trans_class->stop = gauss_blur_stop;
88   trans_class->set_caps = gauss_blur_set_caps;
89   trans_class->transform = gauss_blur_process_frame;
90
91   g_object_class_install_property (object_class, PROP_SIGMA,
92       g_param_spec_double ("sigma", "Sigma",
93           "Sigma value for gaussian blur (negative for sharpen)",
94           -20.0, 20.0, DEFAULT_SIGMA,
95           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
96 }
97
98 static void
99 gauss_blur_init (GaussBlur * gb, GaussBlurClass * gclass)
100 {
101   gb->sigma = DEFAULT_SIGMA;
102   gb->cur_sigma = -1.0;
103 }
104
105 static void
106 cleanup (GaussBlur * gb)
107 {
108   g_free (gb->tempim);
109   gb->tempim = NULL;
110
111   g_free (gb->smoothedim);
112   gb->smoothedim = NULL;
113
114   g_free (gb->kernel);
115   gb->kernel = NULL;
116   g_free (gb->kernel_sum);
117   gb->kernel_sum = NULL;
118 }
119
120 static gboolean
121 gauss_blur_stop (GstBaseTransform * btrans)
122 {
123   GaussBlur *gb = GAUSS_BLUR (btrans);
124
125   cleanup (gb);
126
127   return TRUE;
128 }
129
130 static gboolean
131 gauss_blur_set_caps (GstBaseTransform * btrans,
132     GstCaps * incaps, GstCaps * outcaps)
133 {
134   GaussBlur *gb = GAUSS_BLUR (btrans);
135   GstStructure *structure;
136   GstVideoFormat format;
137   guint32 n_elems;
138
139   structure = gst_caps_get_structure (incaps, 0);
140   g_return_val_if_fail (structure != NULL, FALSE);
141
142   if (!gst_video_format_parse_caps (incaps, &format, &gb->width, &gb->height))
143     return FALSE;
144
145   /* get stride */
146   gb->stride = gst_video_format_get_row_stride (format, 0, gb->width);
147
148   n_elems = gb->stride * gb->height;
149
150   gb->tempim = g_malloc (sizeof (gfloat) * n_elems);
151   //gb->smoothedim = g_malloc (sizeof (guint16) * n_elems);
152
153   return TRUE;
154 }
155
156 static GstFlowReturn
157 gauss_blur_process_frame (GstBaseTransform * btrans,
158     GstBuffer * in_buf, GstBuffer * out_buf)
159 {
160   GaussBlur *gb = GAUSS_BLUR (btrans);
161   GstClockTime timestamp;
162   gint64 stream_time;
163   gfloat sigma;
164
165   /* GstController: update the properties */
166   timestamp = GST_BUFFER_TIMESTAMP (in_buf);
167   stream_time =
168       gst_segment_to_stream_time (&btrans->segment, GST_FORMAT_TIME, timestamp);
169   if (GST_CLOCK_TIME_IS_VALID (stream_time))
170     gst_object_sync_values (G_OBJECT (gb), stream_time);
171
172   GST_OBJECT_LOCK (gb);
173   sigma = gb->sigma;
174   GST_OBJECT_UNLOCK (gb);
175
176   if (gb->cur_sigma != sigma) {
177     g_free (gb->kernel);
178     gb->kernel = NULL;
179     g_free (gb->kernel_sum);
180     gb->kernel_sum = NULL;
181     gb->cur_sigma = sigma;
182   }
183   if (gb->kernel == NULL && !make_gaussian_kernel (gb, gb->cur_sigma)) {
184     GST_ELEMENT_ERROR (btrans, RESOURCE, NO_SPACE_LEFT, ("Out of memory"),
185         ("Failed to allocation gaussian kernel"));
186     return GST_FLOW_ERROR;
187   }
188
189   /*
190    * Perform gaussian smoothing on the image using the input standard
191    * deviation.
192    */
193   memcpy (GST_BUFFER_DATA (out_buf), GST_BUFFER_DATA (in_buf),
194       gb->height * gb->stride);
195   gaussian_smooth (gb, GST_BUFFER_DATA (in_buf), GST_BUFFER_DATA (out_buf));
196
197   return GST_FLOW_OK;
198 }
199
200 static void
201 blur_row_x (GaussBlur * gb, guint8 * in_row, gfloat * out_row)
202 {
203   int c, cc, center;
204   float dot[4], sum;
205   int k, kmin, kmax;
206
207   center = gb->windowsize / 2;
208
209   for (c = 0; c < gb->width; c++) {
210     /* Calculate min */
211     cc = center - c;
212     kmin = MAX (0, cc);
213     cc = kmin - cc;
214     /* Calc max */
215     kmax = MIN (gb->windowsize, gb->width - cc);
216     cc *= 4;
217
218     dot[0] = dot[1] = dot[2] = dot[3] = 0.0;
219     /* Calculate sum for range */
220     sum = gb->kernel_sum[kmax - 1];
221     sum -= kmin ? gb->kernel_sum[kmin - 1] : 0.0;
222
223     for (k = kmin; k < kmax; k++) {
224       float coeff = gb->kernel[k];
225       dot[0] += (float) in_row[cc++] * coeff;
226       dot[1] += (float) in_row[cc++] * coeff;
227       dot[2] += (float) in_row[cc++] * coeff;
228       dot[3] += (float) in_row[cc++] * coeff;
229     }
230
231     out_row[c * 4] = dot[0] / sum;
232     out_row[c * 4 + 1] = dot[1] / sum;
233     out_row[c * 4 + 2] = dot[2] / sum;
234     out_row[c * 4 + 3] = dot[3] / sum;
235   }
236 }
237
238 static void
239 gaussian_smooth (GaussBlur * gb, guint8 * image, guint8 * out_image)
240 {
241   int r, c, rr, center;
242   float dot[4], sum;
243   int k, kmin, kmax;
244   guint8 *in_row = image;
245   float *tmp_out_row = gb->tempim;
246   float *tmp_in_pos;
247   gint y_avail = 0;
248   guint8 *out_row;
249
250   /* Apply the gaussian kernel */
251   center = gb->windowsize / 2;
252
253   /* Blur in the y - direction. */
254   for (r = 0; r < gb->height; r++) {
255     /* Calculate input row range */
256     rr = center - r;
257     kmin = MAX (0, rr);
258     rr = kmin - rr;
259     /* Calc max */
260     kmax = MIN (gb->windowsize, gb->height - rr);
261
262     /* Precalculate sum for range */
263     sum = gb->kernel_sum[kmax - 1];
264     sum -= kmin ? gb->kernel_sum[kmin - 1] : 0.0;
265
266     /* Blur more input rows (x direction blur) */
267     while (y_avail <= (r + center) && y_avail < gb->height) {
268       blur_row_x (gb, in_row, tmp_out_row);
269       in_row += gb->stride;
270       tmp_out_row += gb->stride;
271       y_avail++;
272     }
273
274     tmp_in_pos = gb->tempim + (rr * gb->stride);
275     out_row = out_image + r * gb->stride;
276
277     for (c = 0; c < gb->width; c++) {
278       float *tmp = tmp_in_pos;
279
280       dot[0] = dot[1] = dot[2] = dot[3] = 0.0;
281       for (k = kmin; k < kmax; k++, tmp += gb->stride) {
282         float kern = gb->kernel[k];
283         dot[0] += tmp[0] * kern;
284         dot[1] += tmp[1] * kern;
285         dot[2] += tmp[2] * kern;
286         dot[3] += tmp[3] * kern;
287       }
288
289       *out_row++ = (guint8) CLAMP ((dot[0] / sum + 0.5), 0, 255);
290       *out_row++ = (guint8) CLAMP ((dot[1] / sum + 0.5), 0, 255);
291       *out_row++ = (guint8) CLAMP ((dot[2] / sum + 0.5), 0, 255);
292       *out_row++ = (guint8) CLAMP ((dot[3] / sum + 0.5), 0, 255);
293
294       tmp_in_pos += 4;
295     }
296   }
297 }
298
299 /*
300  * Create a one dimensional gaussian kernel.
301  */
302 static gboolean
303 make_gaussian_kernel (GaussBlur * gb, float sigma)
304 {
305   int i, center, left, right;
306   float sum, sum2;
307   const float fe = -0.5 / (sigma * sigma);
308   const float dx = 1.0 / (sigma * sqrt (2 * G_PI));
309
310   center = ceil (2.5 * fabs (sigma));
311   gb->windowsize = (int) (1 + 2 * center);
312
313   gb->kernel = g_new (float, gb->windowsize);
314   gb->kernel_sum = g_new (float, gb->windowsize);
315   if (gb->kernel == NULL || gb->kernel_sum == NULL)
316     return FALSE;
317
318   if (gb->windowsize == 1) {
319     gb->kernel[0] = 1.0;
320     gb->kernel_sum[0] = 1.0;
321     return TRUE;
322   }
323
324   /* Center co-efficient */
325   sum = gb->kernel[center] = dx;
326
327   /* Other coefficients */
328   left = center - 1;
329   right = center + 1;
330   for (i = 1; i <= center; i++, left--, right++) {
331     float fx = dx * pow (G_E, fe * i * i);
332     gb->kernel[right] = gb->kernel[left] = fx;
333     sum += 2 * fx;
334   }
335
336   if (sigma < 0) {
337     sum = -sum;
338     gb->kernel[center] += 2.0 * sum;
339   }
340
341   for (i = 0; i < gb->windowsize; i++)
342     gb->kernel[i] /= sum;
343
344   sum2 = 0.0;
345   for (i = 0; i < gb->windowsize; i++) {
346     sum2 += gb->kernel[i];
347     gb->kernel_sum[i] = sum2;
348   }
349
350 #if 0
351   g_print ("Sigma %f: ", sigma);
352   for (i = 0; i < gb->windowsize; i++)
353     g_print ("%f ", gb->kernel[i]);
354   g_print ("\n");
355   g_print ("sums: ");
356   for (i = 0; i < gb->windowsize; i++)
357     g_print ("%f ", gb->kernel_sum[i]);
358   g_print ("\n");
359   g_print ("sum %f sum2 %f\n", sum, sum2);
360 #endif
361
362   return TRUE;
363 }
364
365 static void
366 gauss_blur_set_property (GObject * object,
367     guint prop_id, const GValue * value, GParamSpec * pspec)
368 {
369   GaussBlur *gb = GAUSS_BLUR (object);
370   switch (prop_id) {
371     case PROP_SIGMA:
372       GST_OBJECT_LOCK (object);
373       gb->sigma = g_value_get_double (value);
374       GST_OBJECT_UNLOCK (object);
375       break;
376     default:
377       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
378       break;
379   }
380 }
381
382 static void
383 gauss_blur_get_property (GObject * object,
384     guint prop_id, GValue * value, GParamSpec * pspec)
385 {
386   GaussBlur *gb = GAUSS_BLUR (object);
387   switch (prop_id) {
388     case PROP_SIGMA:
389       GST_OBJECT_LOCK (gb);
390       g_value_set_double (value, gb->sigma);
391       GST_OBJECT_UNLOCK (gb);
392       break;
393     default:
394       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
395       break;
396   }
397 }
398
399 /* Register the element factories and other features. */
400 gboolean
401 gst_gauss_blur_plugin_init (GstPlugin * plugin)
402 {
403   /* debug category for fltering log messages */
404   GST_DEBUG_CATEGORY_INIT (gst_gauss_blur_debug, "gaussianblur",
405       0, "Gaussian Blur video effect");
406
407   return gst_element_register (plugin, "gaussianblur", GST_RANK_NONE,
408       GST_TYPE_GAUSS_BLUR);
409 }