Tizen 2.0 Release
[framework/multimedia/gst-plugins-bad0.10.git] / ext / rsvg / gstrsvgdec.c
1 /* GStreamer
2  * Copyright (C) <2009> Sebastian Dröge <sebastian.droege@collabora.co.uk>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19 /**
20  * SECTION:element-rsvgdec
21  *
22  * This elements renders SVG graphics.
23  *
24  * <refsect2>
25  * <title>Example launch lines</title>
26  * |[
27  * gst-launch filesrc location=image.svg ! rsvgdec ! imagefreeze ! ffmpegcolorspace ! autovideosink
28  * ]| render and show a svg image.
29  * </refsect2>
30  */
31
32 #ifdef HAVE_CONFIG_H
33 #include "config.h"
34 #endif
35
36 #include "gstrsvgdec.h"
37
38 #include <string.h>
39
40 GST_DEBUG_CATEGORY_STATIC (rsvgdec_debug);
41 #define GST_CAT_DEFAULT rsvgdec_debug
42
43 static GstStaticPadTemplate sink_factory =
44     GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
45     GST_STATIC_CAPS ("image/svg+xml; image/svg"));
46
47 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
48 #define GST_RSVG_VIDEO_CAPS GST_VIDEO_CAPS_BGRA
49 #else
50 #define GST_RSVG_VIDEO_CAPS GST_VIDEO_CAPS_ARGB
51 #endif
52
53 static GstStaticPadTemplate src_factory =
54 GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
55     GST_STATIC_CAPS (GST_RSVG_VIDEO_CAPS));
56
57 GST_BOILERPLATE (GstRsvgDec, gst_rsvg_dec, GstElement, GST_TYPE_ELEMENT);
58
59 static void gst_rsvg_dec_reset (GstRsvgDec * rsvg);
60
61 static GstFlowReturn gst_rsvg_dec_chain (GstPad * pad, GstBuffer * buffer);
62 static gboolean gst_rsvg_dec_sink_set_caps (GstPad * pad, GstCaps * caps);
63 static gboolean gst_rsvg_dec_sink_event (GstPad * pad, GstEvent * event);
64
65 static gboolean gst_rsvg_dec_src_event (GstPad * pad, GstEvent * event);
66 static gboolean gst_rsvg_dec_src_query (GstPad * pad, GstQuery * query);
67 static const GstQueryType *gst_rsvg_dec_src_query_type (GstPad * pad);
68 static gboolean gst_rsvg_dec_src_set_caps (GstPad * pad, GstCaps * caps);
69
70 static GstStateChangeReturn gst_rsvg_dec_change_state (GstElement * element,
71     GstStateChange transition);
72
73 static void gst_rsvg_dec_finalize (GObject * object);
74
75 static void
76 gst_rsvg_dec_base_init (gpointer g_class)
77 {
78   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
79
80   gst_element_class_set_details_simple (element_class,
81       "SVG image decoder", "Codec/Decoder/Image",
82       "Uses librsvg to decode SVG images",
83       "Sebastian Dröge <sebastian.droege@collabora.co.uk>");
84
85   gst_element_class_add_static_pad_template (element_class, &sink_factory);
86   gst_element_class_add_static_pad_template (element_class, &src_factory);
87 }
88
89 static void
90 gst_rsvg_dec_class_init (GstRsvgDecClass * klass)
91 {
92   GstElementClass *element_class = (GstElementClass *) klass;
93   GObjectClass *gobject_class = (GObjectClass *) klass;
94
95   GST_DEBUG_CATEGORY_INIT (rsvgdec_debug, "rsvgdec", 0, "RSVG decoder");
96
97   gobject_class->finalize = gst_rsvg_dec_finalize;
98   element_class->change_state = GST_DEBUG_FUNCPTR (gst_rsvg_dec_change_state);
99 }
100
101 static void
102 gst_rsvg_dec_init (GstRsvgDec * rsvg, GstRsvgDecClass * klass)
103 {
104   rsvg->sinkpad = gst_pad_new_from_static_template (&sink_factory, "sink");
105   gst_pad_set_setcaps_function (rsvg->sinkpad, gst_rsvg_dec_sink_set_caps);
106   gst_pad_set_event_function (rsvg->sinkpad, gst_rsvg_dec_sink_event);
107   gst_pad_set_chain_function (rsvg->sinkpad, gst_rsvg_dec_chain);
108   gst_element_add_pad (GST_ELEMENT (rsvg), rsvg->sinkpad);
109
110   rsvg->srcpad = gst_pad_new_from_static_template (&src_factory, "src");
111   gst_pad_set_event_function (rsvg->srcpad, gst_rsvg_dec_src_event);
112   gst_pad_set_query_function (rsvg->srcpad, gst_rsvg_dec_src_query);
113   gst_pad_set_query_type_function (rsvg->srcpad, gst_rsvg_dec_src_query_type);
114   gst_pad_set_setcaps_function (rsvg->srcpad, gst_rsvg_dec_src_set_caps);
115   gst_element_add_pad (GST_ELEMENT (rsvg), rsvg->srcpad);
116
117   rsvg->adapter = gst_adapter_new ();
118
119   gst_rsvg_dec_reset (rsvg);
120 }
121
122 static void
123 gst_rsvg_dec_finalize (GObject * object)
124 {
125   GstRsvgDec *rsvg = GST_RSVG_DEC (object);
126
127   if (rsvg->adapter) {
128     g_object_unref (rsvg->adapter);
129     rsvg->adapter = NULL;
130   }
131
132   G_OBJECT_CLASS (parent_class)->finalize (object);
133 }
134
135 static void
136 gst_rsvg_dec_reset (GstRsvgDec * dec)
137 {
138   gst_adapter_clear (dec->adapter);
139   dec->width = dec->height = 0;
140   dec->fps_n = 0;
141   dec->fps_d = 1;
142   dec->first_timestamp = GST_CLOCK_TIME_NONE;
143   dec->frame_count = 0;
144
145   gst_segment_init (&dec->segment, GST_FORMAT_UNDEFINED);
146   dec->need_newsegment = TRUE;
147
148   g_list_foreach (dec->pending_events, (GFunc) gst_mini_object_unref, NULL);
149   g_list_free (dec->pending_events);
150   dec->pending_events = NULL;
151
152   if (dec->pending_tags) {
153     gst_tag_list_free (dec->pending_tags);
154     dec->pending_tags = NULL;
155   }
156 }
157
158 #define CAIRO_UNPREMULTIPLY(a,r,g,b) G_STMT_START { \
159   b = (a > 0) ? MIN ((b * 255 + a / 2) / a, 255) : 0; \
160   g = (a > 0) ? MIN ((g * 255 + a / 2) / a, 255) : 0; \
161   r = (a > 0) ? MIN ((r * 255 + a / 2) / a, 255) : 0; \
162 } G_STMT_END
163
164 static void
165 gst_rsvg_decode_unpremultiply (guint8 * data, gint width, gint height)
166 {
167   gint i, j;
168   guint a;
169
170   for (i = 0; i < height; i++) {
171     for (j = 0; j < width; j++) {
172 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
173       a = data[3];
174       data[0] = (a > 0) ? MIN ((data[0] * 255 + a / 2) / a, 255) : 0;
175       data[1] = (a > 0) ? MIN ((data[1] * 255 + a / 2) / a, 255) : 0;
176       data[2] = (a > 0) ? MIN ((data[2] * 255 + a / 2) / a, 255) : 0;
177 #else
178       a = data[0];
179       data[1] = (a > 0) ? MIN ((data[1] * 255 + a / 2) / a, 255) : 0;
180       data[2] = (a > 0) ? MIN ((data[2] * 255 + a / 2) / a, 255) : 0;
181       data[3] = (a > 0) ? MIN ((data[3] * 255 + a / 2) / a, 255) : 0;
182 #endif
183       data += 4;
184     }
185   }
186 }
187
188 static GstFlowReturn
189 gst_rsvg_decode_image (GstRsvgDec * rsvg, const guint8 * data, guint size,
190     GstBuffer ** buffer)
191 {
192   GstFlowReturn ret = GST_FLOW_OK;
193   cairo_t *cr;
194   cairo_surface_t *surface;
195   RsvgHandle *handle;
196   GError *error = NULL;
197   RsvgDimensionData dimension;
198   gdouble scalex, scaley;
199   const gchar *title = NULL, *comment = NULL;
200
201   GST_LOG_OBJECT (rsvg, "parsing svg");
202
203   handle = rsvg_handle_new_from_data (data, size, &error);
204   if (!handle) {
205     GST_ERROR_OBJECT (rsvg, "Failed to parse SVG image: %s", error->message);
206     g_error_free (error);
207     return GST_FLOW_ERROR;
208   }
209
210   title = rsvg_handle_get_title (handle);
211   comment = rsvg_handle_get_desc (handle);
212
213   if (title || comment) {
214     GST_LOG_OBJECT (rsvg, "adding tags");
215
216     if (!rsvg->pending_tags)
217       rsvg->pending_tags = gst_tag_list_new ();
218
219     if (title && *title)
220       gst_tag_list_add (rsvg->pending_tags, GST_TAG_MERGE_REPLACE_ALL,
221           GST_TAG_TITLE, title, NULL);
222     if (comment && *comment)
223       gst_tag_list_add (rsvg->pending_tags, GST_TAG_MERGE_REPLACE_ALL,
224           GST_TAG_COMMENT, comment, NULL);
225   }
226
227   rsvg_handle_get_dimensions (handle, &dimension);
228   if (rsvg->width != dimension.width || rsvg->height != dimension.height) {
229     GstCaps *caps1, *caps2, *caps3;
230     GstStructure *s;
231
232     GST_LOG_OBJECT (rsvg, "resolution changed, updating caps");
233
234     caps1 = gst_caps_copy (gst_pad_get_pad_template_caps (rsvg->srcpad));
235     caps2 = gst_pad_peer_get_caps (rsvg->srcpad);
236     if (caps2) {
237       caps3 = gst_caps_intersect (caps1, caps2);
238       gst_caps_unref (caps1);
239       gst_caps_unref (caps2);
240       caps1 = caps3;
241       caps3 = NULL;
242     }
243
244     if (gst_caps_is_empty (caps1)) {
245       GST_ERROR_OBJECT (rsvg, "Unable to negotiate a format");
246       gst_caps_unref (caps1);
247       g_object_unref (handle);
248       return GST_FLOW_NOT_NEGOTIATED;
249     }
250
251     caps2 = gst_caps_copy (gst_pad_get_pad_template_caps (rsvg->srcpad));
252     s = gst_caps_get_structure (caps2, 0);
253     gst_structure_set (s, "width", G_TYPE_INT, dimension.width, "height",
254         G_TYPE_INT, dimension.height, "framerate", GST_TYPE_FRACTION, 0, 1,
255         NULL);
256     caps3 = gst_caps_intersect (caps1, caps2);
257     if (!gst_caps_is_empty (caps3)) {
258       gst_caps_truncate (caps3);
259       gst_pad_set_caps (rsvg->srcpad, caps3);
260       gst_caps_unref (caps1);
261       gst_caps_unref (caps2);
262       gst_caps_unref (caps3);
263       rsvg->width = dimension.width;
264       rsvg->height = dimension.height;
265     } else {
266       gst_caps_unref (caps2);
267       gst_caps_unref (caps3);
268       gst_caps_truncate (caps1);
269
270       s = gst_caps_get_structure (caps1, 0);
271       gst_structure_set (s, "framerate", GST_TYPE_FRACTION, 0, 1, NULL);
272
273       if (!gst_caps_is_fixed (caps1)
274           && (!gst_structure_fixate_field_nearest_int (s, "width",
275                   dimension.width)
276               || !gst_structure_fixate_field_nearest_int (s, "height",
277                   dimension.height))) {
278         g_object_unref (handle);
279         GST_ERROR_OBJECT (rsvg, "Failed to fixate caps");
280         return GST_FLOW_NOT_NEGOTIATED;
281       }
282       gst_pad_set_caps (rsvg->srcpad, caps1);
283       gst_structure_get_int (s, "width", &rsvg->width);
284       gst_structure_get_int (s, "height", &rsvg->height);
285       gst_caps_unref (caps1);
286     }
287   }
288
289   if ((ret = gst_pad_alloc_buffer_and_set_caps (rsvg->srcpad,
290               GST_BUFFER_OFFSET_NONE,
291               rsvg->width * rsvg->height * 4,
292               GST_PAD_CAPS (rsvg->srcpad), buffer)) != GST_FLOW_OK) {
293     g_object_unref (handle);
294     GST_ERROR_OBJECT (rsvg, "Buffer allocation failed %s",
295         gst_flow_get_name (ret));
296     return ret;
297   }
298
299   GST_LOG_OBJECT (rsvg, "render image at %d x %d", rsvg->height, rsvg->width);
300
301   surface =
302       cairo_image_surface_create_for_data (GST_BUFFER_DATA (*buffer),
303       CAIRO_FORMAT_ARGB32, rsvg->width, rsvg->height, rsvg->width * 4);
304
305   cr = cairo_create (surface);
306   cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
307   cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.0);
308   cairo_paint (cr);
309   cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
310   cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0);
311
312   scalex = scaley = 1.0;
313   if (rsvg->width != dimension.width) {
314     scalex = ((gdouble) rsvg->width) / ((gdouble) dimension.width);
315   }
316   if (rsvg->height != dimension.height) {
317     scaley = ((gdouble) rsvg->height) / ((gdouble) dimension.height);
318   }
319   cairo_scale (cr, scalex, scaley);
320   rsvg_handle_render_cairo (handle, cr);
321
322   g_object_unref (handle);
323   cairo_destroy (cr);
324   cairo_surface_destroy (surface);
325
326   /* Now unpremultiply Cairo's ARGB to match GStreamer's */
327   gst_rsvg_decode_unpremultiply (GST_BUFFER_DATA (*buffer), rsvg->width,
328       rsvg->height);
329
330   return ret;
331 }
332
333 static GstFlowReturn
334 gst_rsvg_dec_chain (GstPad * pad, GstBuffer * buffer)
335 {
336   GstRsvgDec *rsvg = GST_RSVG_DEC (GST_PAD_PARENT (pad));
337   gboolean completed = FALSE;
338   const guint8 *data;
339   guint size;
340   gboolean ret = GST_FLOW_OK;
341
342   /* first_timestamp is used slightly differently where a framerate
343      is given or not.
344      If there is a frame rate, it will be used as a base.
345      If there is not, it will be used to keep track of the timestamp
346      of the first buffer, to be used as the timestamp of the output
347      buffer. When a buffer is output, first timestamp will resync to
348      the next buffer's timestamp. */
349   if (rsvg->first_timestamp == GST_CLOCK_TIME_NONE) {
350     if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
351       rsvg->first_timestamp = GST_BUFFER_TIMESTAMP (buffer);
352     else if (rsvg->fps_n != 0)
353       rsvg->first_timestamp = 0;
354   }
355
356   gst_adapter_push (rsvg->adapter, buffer);
357
358   size = gst_adapter_available (rsvg->adapter);
359
360   /* "<svg></svg>" */
361   while (size >= 5 + 6 && ret == GST_FLOW_OK) {
362     guint i;
363
364     data = gst_adapter_peek (rsvg->adapter, size);
365     for (i = size - 6; i >= 5; i--) {
366       if (memcmp (data + i, "</svg>", 6) == 0) {
367         completed = TRUE;
368         size = i + 6;
369         break;
370       }
371     }
372
373     if (completed) {
374       GstBuffer *outbuf = NULL;
375
376       GST_LOG_OBJECT (rsvg, "have complete svg of %u bytes", size);
377
378       data = gst_adapter_peek (rsvg->adapter, size);
379
380       ret = gst_rsvg_decode_image (rsvg, data, size, &outbuf);
381       if (ret != GST_FLOW_OK)
382         break;
383
384
385       if (rsvg->first_timestamp != GST_CLOCK_TIME_NONE) {
386         GST_BUFFER_TIMESTAMP (outbuf) = rsvg->first_timestamp;
387         GST_BUFFER_DURATION (outbuf) = GST_CLOCK_TIME_NONE;
388         if (GST_BUFFER_DURATION_IS_VALID (buffer)) {
389           GstClockTime end =
390               GST_BUFFER_TIMESTAMP_IS_VALID (buffer) ?
391               GST_BUFFER_TIMESTAMP (buffer) : rsvg->first_timestamp;
392           end += GST_BUFFER_DURATION (buffer);
393           GST_BUFFER_DURATION (outbuf) = end - GST_BUFFER_TIMESTAMP (outbuf);
394         }
395         if (rsvg->fps_n == 0) {
396           rsvg->first_timestamp = GST_CLOCK_TIME_NONE;
397         } else {
398           GST_BUFFER_DURATION (outbuf) =
399               gst_util_uint64_scale (rsvg->frame_count, rsvg->fps_d,
400               rsvg->fps_n * GST_SECOND);
401         }
402       } else if (rsvg->fps_n != 0) {
403         GST_BUFFER_TIMESTAMP (outbuf) =
404             rsvg->first_timestamp + gst_util_uint64_scale (rsvg->frame_count,
405             rsvg->fps_d, rsvg->fps_n * GST_SECOND);
406         GST_BUFFER_DURATION (outbuf) =
407             gst_util_uint64_scale (rsvg->frame_count, rsvg->fps_d,
408             rsvg->fps_n * GST_SECOND);
409       } else {
410         GST_BUFFER_TIMESTAMP (outbuf) = rsvg->first_timestamp;
411         GST_BUFFER_DURATION (outbuf) = GST_CLOCK_TIME_NONE;
412       }
413       rsvg->frame_count++;
414
415       if (rsvg->need_newsegment) {
416         gst_pad_push_event (rsvg->srcpad,
417             gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME, 0, -1, 0));
418         rsvg->need_newsegment = FALSE;
419       }
420
421       if (rsvg->pending_events) {
422         GList *l;
423
424         for (l = rsvg->pending_events; l; l = l->next)
425           gst_pad_push_event (rsvg->srcpad, l->data);
426         g_list_free (rsvg->pending_events);
427         rsvg->pending_events = NULL;
428       }
429
430       if (rsvg->pending_tags) {
431         gst_element_found_tags (GST_ELEMENT_CAST (rsvg), rsvg->pending_tags);
432         rsvg->pending_tags = NULL;
433       }
434
435       GST_LOG_OBJECT (rsvg, "image rendered okay");
436
437       ret = gst_pad_push (rsvg->srcpad, outbuf);
438       if (ret != GST_FLOW_OK)
439         break;
440
441       gst_adapter_flush (rsvg->adapter, size);
442       size = gst_adapter_available (rsvg->adapter);
443       continue;
444     } else {
445       break;
446     }
447   }
448
449   return GST_FLOW_OK;
450 }
451
452 static gboolean
453 gst_rsvg_dec_sink_set_caps (GstPad * pad, GstCaps * caps)
454 {
455   GstRsvgDec *rsvg = GST_RSVG_DEC (gst_pad_get_parent (pad));
456   gboolean ret = TRUE;
457   GstStructure *s = gst_caps_get_structure (caps, 0);
458
459   gst_structure_get_fraction (s, "framerate", &rsvg->fps_n, &rsvg->fps_d);
460
461   gst_object_unref (rsvg);
462
463   return ret;
464 }
465
466 static gboolean
467 gst_rsvg_dec_sink_event (GstPad * pad, GstEvent * event)
468 {
469   GstRsvgDec *rsvg = GST_RSVG_DEC (gst_pad_get_parent (pad));
470   gboolean res = FALSE;
471
472   switch (GST_EVENT_TYPE (event)) {
473     case GST_EVENT_NEWSEGMENT:{
474       gdouble rate, arate;
475       gboolean update;
476       gint64 start, stop, position;
477       GstFormat fmt;
478
479       gst_event_parse_new_segment_full (event, &update, &rate, &arate, &fmt,
480           &start, &stop, &position);
481
482       gst_segment_set_newsegment_full (&rsvg->segment, update, rate, arate,
483           fmt, start, stop, position);
484
485       if (fmt == GST_FORMAT_TIME) {
486         rsvg->need_newsegment = FALSE;
487         res = gst_pad_push_event (rsvg->srcpad, event);
488       } else {
489         gst_event_unref (event);
490         res = TRUE;
491       }
492       break;
493     }
494     case GST_EVENT_EOS:
495     case GST_EVENT_FLUSH_STOP:
496       gst_adapter_clear (rsvg->adapter);
497       /* fall through */
498     case GST_EVENT_FLUSH_START:
499       res = gst_pad_push_event (rsvg->srcpad, event);
500       break;
501     default:
502       if (GST_PAD_CAPS (rsvg->srcpad)) {
503         res = gst_pad_push_event (rsvg->srcpad, event);
504       } else {
505         res = TRUE;
506         rsvg->pending_events = g_list_append (rsvg->pending_events, event);
507       }
508       break;
509   }
510
511   gst_object_unref (rsvg);
512
513   return res;
514 }
515
516 static gboolean
517 gst_rsvg_dec_src_event (GstPad * pad, GstEvent * event)
518 {
519   GstRsvgDec *rsvg = GST_RSVG_DEC (gst_pad_get_parent (pad));
520   gboolean res = FALSE;
521
522   switch (GST_EVENT_TYPE (event)) {
523     default:
524       res = gst_pad_push_event (rsvg->sinkpad, event);
525       break;
526   }
527
528   gst_object_unref (rsvg);
529
530   return res;
531 }
532
533 static const GstQueryType *
534 gst_rsvg_dec_src_query_type (GstPad * pad)
535 {
536   static const GstQueryType query_types[] = {
537     (GstQueryType) 0
538   };
539
540   return query_types;
541 }
542
543 static gboolean
544 gst_rsvg_dec_src_query (GstPad * pad, GstQuery * query)
545 {
546   GstRsvgDec *rsvg = GST_RSVG_DEC (gst_pad_get_parent (pad));
547   gboolean res = TRUE;
548
549   switch (GST_QUERY_TYPE (query)) {
550     default:
551       res = gst_pad_query_default (pad, query);
552       break;
553   }
554
555   gst_object_unref (rsvg);
556
557   return res;
558 }
559
560 static gboolean
561 gst_rsvg_dec_src_set_caps (GstPad * pad, GstCaps * caps)
562 {
563   GstRsvgDec *rsvg = GST_RSVG_DEC (gst_pad_get_parent (pad));
564   gboolean ret = TRUE;
565   GstStructure *s = gst_caps_get_structure (caps, 0);
566
567   ret &= gst_structure_get_int (s, "width", &rsvg->width);
568   ret &= gst_structure_get_int (s, "height", &rsvg->height);
569
570   gst_object_unref (rsvg);
571
572   return ret;
573 }
574
575 static GstStateChangeReturn
576 gst_rsvg_dec_change_state (GstElement * element, GstStateChange transition)
577 {
578   GstStateChangeReturn res;
579   GstRsvgDec *dec = GST_RSVG_DEC (element);
580
581   switch (transition) {
582     case GST_STATE_CHANGE_READY_TO_PAUSED:
583       break;
584     default:
585       break;
586   }
587
588   res = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
589   if (res == GST_STATE_CHANGE_FAILURE)
590     return res;
591
592   switch (transition) {
593     case GST_STATE_CHANGE_PAUSED_TO_READY:
594       gst_rsvg_dec_reset (dec);
595       break;
596     default:
597       break;
598   }
599
600   return res;
601 }