1 /* GStreamer
2 * Copyright (C) <2009> Sebastian Dröge <sebastian.droege@collabora.co.uk>
3 * Copyright (C) <2013> Luciana Fujii <luciana.fujii@collabora.co.uk>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20 /**
21 * SECTION:element-rsvgdec
22 * @title: rsvgdec
23 *
24 * This elements renders SVG graphics.
25 *
26 * ## Example launch lines
27 * |[
28 * gst-launch-1.0 filesrc location=image.svg ! rsvgdec ! imagefreeze ! videoconvert ! autovideosink
29 * ]| render and show a svg image.
30 *
31 */
32
33 #ifdef HAVE_CONFIG_H
34 #include "config.h"
35 #endif
36
37 #include "gstrsvgdec.h"
38
39 #include <string.h>
40
41 GST_DEBUG_CATEGORY_STATIC (rsvgdec_debug);
42 #define GST_CAT_DEFAULT rsvgdec_debug
43
44 static GstStaticPadTemplate sink_factory =
45 GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
46 GST_STATIC_CAPS ("image/svg+xml; image/svg"));
47
48 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
49 #define GST_RSVG_VIDEO_CAPS GST_VIDEO_CAPS_MAKE ("BGRA")
50 #define GST_RSVG_VIDEO_FORMAT GST_VIDEO_FORMAT_BGRA
51 #else
52 #define GST_RSVG_VIDEO_CAPS GST_VIDEO_CAPS_MAKE ("ARGB")
53 #define GST_RSVG_VIDEO_FORMAT GST_VIDEO_FORMAT_ARGB
54 #endif
55
56 static GstStaticPadTemplate src_factory =
57 GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
58 GST_STATIC_CAPS (GST_RSVG_VIDEO_CAPS));
59
60 #define gst_rsv_dec_parent_class parent_class
61 G_DEFINE_TYPE (GstRsvgDec, gst_rsvg_dec, GST_TYPE_VIDEO_DECODER);
62
63 static gboolean gst_rsvg_dec_stop (GstVideoDecoder * decoder);
64 static gboolean gst_rsvg_dec_set_format (GstVideoDecoder * decoder,
65 GstVideoCodecState * state);
66 static GstFlowReturn gst_rsvg_dec_parse (GstVideoDecoder * decoder,
67 GstVideoCodecFrame * frame, GstAdapter * adapter, gboolean at_eos);
68 static GstFlowReturn gst_rsvg_dec_handle_frame (GstVideoDecoder * decoder,
69 GstVideoCodecFrame * frame);
70 static GstFlowReturn gst_rsvg_decode_image (GstRsvgDec * rsvg,
71 GstBuffer * buffer, GstVideoCodecFrame * frame);
72
73 static void gst_rsvg_dec_finalize (GObject * object);
74
75 static void
gst_rsvg_dec_class_init(GstRsvgDecClass * klass)76 gst_rsvg_dec_class_init (GstRsvgDecClass * klass)
77 {
78 GstVideoDecoderClass *video_decoder_class = GST_VIDEO_DECODER_CLASS (klass);
79 GObjectClass *gobject_class = (GObjectClass *) klass;
80 GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
81
82 GST_DEBUG_CATEGORY_INIT (rsvgdec_debug, "rsvgdec", 0, "RSVG decoder");
83
84 gst_element_class_set_static_metadata (element_class,
85 "SVG image decoder", "Codec/Decoder/Image",
86 "Uses librsvg to decode SVG images",
87 "Sebastian Dröge <sebastian.droege@collabora.co.uk>");
88
89 gst_element_class_add_static_pad_template (element_class, &sink_factory);
90 gst_element_class_add_static_pad_template (element_class, &src_factory);
91
92 gobject_class->finalize = gst_rsvg_dec_finalize;
93 video_decoder_class->stop = GST_DEBUG_FUNCPTR (gst_rsvg_dec_stop);
94 video_decoder_class->set_format = GST_DEBUG_FUNCPTR (gst_rsvg_dec_set_format);
95 video_decoder_class->parse = GST_DEBUG_FUNCPTR (gst_rsvg_dec_parse);
96 video_decoder_class->handle_frame =
97 GST_DEBUG_FUNCPTR (gst_rsvg_dec_handle_frame);
98 }
99
100 static void
gst_rsvg_dec_init(GstRsvgDec * rsvg)101 gst_rsvg_dec_init (GstRsvgDec * rsvg)
102 {
103 GstVideoDecoder *decoder = GST_VIDEO_DECODER (rsvg);
104 gst_video_decoder_set_packetized (decoder, FALSE);
105 gst_video_decoder_set_use_default_pad_acceptcaps (GST_VIDEO_DECODER_CAST
106 (rsvg), TRUE);
107 GST_PAD_SET_ACCEPT_TEMPLATE (GST_VIDEO_DECODER_SINK_PAD (rsvg));
108 }
109
110 static void
gst_rsvg_dec_finalize(GObject * object)111 gst_rsvg_dec_finalize (GObject * object)
112 {
113 G_OBJECT_CLASS (gst_rsvg_dec_parent_class)->finalize (object);
114 }
115
116
117 #define CAIRO_UNPREMULTIPLY(a,r,g,b) G_STMT_START { \
118 b = (a > 0) ? MIN ((b * 255 + a / 2) / a, 255) : 0; \
119 g = (a > 0) ? MIN ((g * 255 + a / 2) / a, 255) : 0; \
120 r = (a > 0) ? MIN ((r * 255 + a / 2) / a, 255) : 0; \
121 } G_STMT_END
122
123 static void
gst_rsvg_decode_unpremultiply(guint8 * data,gint width,gint height)124 gst_rsvg_decode_unpremultiply (guint8 * data, gint width, gint height)
125 {
126 gint i, j;
127 guint a;
128
129 for (i = 0; i < height; i++) {
130 for (j = 0; j < width; j++) {
131 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
132 a = data[3];
133 data[0] = (a > 0) ? MIN ((data[0] * 255 + a / 2) / a, 255) : 0;
134 data[1] = (a > 0) ? MIN ((data[1] * 255 + a / 2) / a, 255) : 0;
135 data[2] = (a > 0) ? MIN ((data[2] * 255 + a / 2) / a, 255) : 0;
136 #else
137 a = data[0];
138 data[1] = (a > 0) ? MIN ((data[1] * 255 + a / 2) / a, 255) : 0;
139 data[2] = (a > 0) ? MIN ((data[2] * 255 + a / 2) / a, 255) : 0;
140 data[3] = (a > 0) ? MIN ((data[3] * 255 + a / 2) / a, 255) : 0;
141 #endif
142 data += 4;
143 }
144 }
145 }
146
147 static GstFlowReturn
gst_rsvg_decode_image(GstRsvgDec * rsvg,GstBuffer * buffer,GstVideoCodecFrame * frame)148 gst_rsvg_decode_image (GstRsvgDec * rsvg, GstBuffer * buffer,
149 GstVideoCodecFrame * frame)
150 {
151 GstVideoDecoder *decoder = GST_VIDEO_DECODER (rsvg);
152 GstFlowReturn ret = GST_FLOW_OK;
153 cairo_t *cr;
154 cairo_surface_t *surface;
155 RsvgHandle *handle;
156 GError *error = NULL;
157 RsvgDimensionData dimension;
158 gdouble scalex, scaley;
159 GstMapInfo minfo;
160 GstVideoFrame vframe;
161 GstVideoCodecState *output_state;
162
163 GST_LOG_OBJECT (rsvg, "parsing svg");
164
165 if (!gst_buffer_map (buffer, &minfo, GST_MAP_READ)) {
166 GST_ERROR_OBJECT (rsvg, "Failed to get SVG image");
167 return GST_FLOW_ERROR;
168 }
169 handle = rsvg_handle_new_from_data (minfo.data, minfo.size, &error);
170 if (!handle) {
171 GST_ERROR_OBJECT (rsvg, "Failed to parse SVG image: %s", error->message);
172 g_error_free (error);
173 return GST_FLOW_ERROR;
174 }
175
176 rsvg_handle_get_dimensions (handle, &dimension);
177
178 output_state = gst_video_decoder_get_output_state (decoder);
179 if ((output_state == NULL)
180 || GST_VIDEO_INFO_WIDTH (&output_state->info) != dimension.width
181 || GST_VIDEO_INFO_HEIGHT (&output_state->info) != dimension.height) {
182
183 /* Create the output state */
184 if (output_state)
185 gst_video_codec_state_unref (output_state);
186 output_state =
187 gst_video_decoder_set_output_state (decoder, GST_RSVG_VIDEO_FORMAT,
188 dimension.width, dimension.height, rsvg->input_state);
189 }
190
191 ret = gst_video_decoder_allocate_output_frame (decoder, frame);
192
193 if (ret != GST_FLOW_OK) {
194 g_object_unref (handle);
195 gst_video_codec_state_unref (output_state);
196 GST_ERROR_OBJECT (rsvg, "Buffer allocation failed %s",
197 gst_flow_get_name (ret));
198 return ret;
199 }
200
201 GST_LOG_OBJECT (rsvg, "render image at %d x %d",
202 GST_VIDEO_INFO_HEIGHT (&output_state->info),
203 GST_VIDEO_INFO_WIDTH (&output_state->info));
204
205
206 if (!gst_video_frame_map (&vframe,
207 &output_state->info, frame->output_buffer, GST_MAP_READWRITE)) {
208 GST_ERROR_OBJECT (rsvg, "Failed to get SVG image");
209 g_object_unref (handle);
210 gst_video_codec_state_unref (output_state);
211 return GST_FLOW_ERROR;
212 }
213 surface =
214 cairo_image_surface_create_for_data (GST_VIDEO_FRAME_PLANE_DATA (&vframe,
215 0), CAIRO_FORMAT_ARGB32, GST_VIDEO_FRAME_WIDTH (&vframe),
216 GST_VIDEO_FRAME_HEIGHT (&vframe), GST_VIDEO_FRAME_PLANE_STRIDE (&vframe,
217 0));
218
219 cr = cairo_create (surface);
220 cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
221 cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.0);
222 cairo_paint (cr);
223 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
224 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0);
225
226 scalex = scaley = 1.0;
227 if (GST_VIDEO_INFO_WIDTH (&output_state->info) != dimension.width) {
228 scalex =
229 ((gdouble) GST_VIDEO_INFO_WIDTH (&output_state->info)) /
230 ((gdouble) dimension.width);
231 }
232 if (GST_VIDEO_INFO_HEIGHT (&output_state->info) != dimension.height) {
233 scaley =
234 ((gdouble) GST_VIDEO_INFO_HEIGHT (&output_state->info)) /
235 ((gdouble) dimension.height);
236 }
237 cairo_scale (cr, scalex, scaley);
238 rsvg_handle_render_cairo (handle, cr);
239
240 g_object_unref (handle);
241 cairo_destroy (cr);
242 cairo_surface_destroy (surface);
243
244 /* Now unpremultiply Cairo's ARGB to match GStreamer's */
245 gst_rsvg_decode_unpremultiply (GST_VIDEO_FRAME_PLANE_DATA (&vframe, 0),
246 GST_VIDEO_FRAME_WIDTH (&vframe), GST_VIDEO_FRAME_HEIGHT (&vframe));
247
248 gst_video_codec_state_unref (output_state);
249 gst_buffer_unmap (buffer, &minfo);
250 gst_video_frame_unmap (&vframe);
251
252 return ret;
253 }
254
255
256 static gboolean
gst_rsvg_dec_set_format(GstVideoDecoder * decoder,GstVideoCodecState * state)257 gst_rsvg_dec_set_format (GstVideoDecoder * decoder, GstVideoCodecState * state)
258 {
259 GstRsvgDec *rsvg = GST_RSVG_DEC (decoder);
260 GstVideoInfo *info = &state->info;
261
262 if (rsvg->input_state)
263 gst_video_codec_state_unref (rsvg->input_state);
264 rsvg->input_state = gst_video_codec_state_ref (state);
265
266 /* Create the output state */
267 state = gst_video_decoder_set_output_state (decoder, GST_RSVG_VIDEO_FORMAT,
268 GST_VIDEO_INFO_WIDTH (info), GST_VIDEO_INFO_HEIGHT (info),
269 rsvg->input_state);
270 gst_video_codec_state_unref (state);
271
272 return TRUE;
273 }
274
275 static GstFlowReturn
gst_rsvg_dec_parse(GstVideoDecoder * decoder,GstVideoCodecFrame * frame,GstAdapter * adapter,gboolean at_eos)276 gst_rsvg_dec_parse (GstVideoDecoder * decoder, GstVideoCodecFrame * frame,
277 GstAdapter * adapter, gboolean at_eos)
278 {
279 gboolean completed = FALSE;
280 const guint8 *data;
281 guint size;
282 guint i;
283
284 GST_LOG_OBJECT (decoder, "parse start");
285 size = gst_adapter_available (adapter);
286
287 /* "<svg></svg>" */
288 if (size < 5 + 6)
289 return GST_VIDEO_DECODER_FLOW_NEED_DATA;
290
291 data = gst_adapter_map (adapter, size);
292 if (data == NULL) {
293 GST_ERROR_OBJECT (decoder, "Unable to map memory");
294 return GST_FLOW_ERROR;
295 }
296 for (i = 0; i < size - 4; i++) {
297 if (memcmp (data + i, "<svg", 4) == 0) {
298 gst_adapter_flush (adapter, i);
299
300 size = gst_adapter_available (adapter);
301 if (size < 5 + 6)
302 return GST_VIDEO_DECODER_FLOW_NEED_DATA;
303 data = gst_adapter_map (adapter, size);
304 if (data == NULL) {
305 GST_ERROR_OBJECT (decoder, "Unable to map memory");
306 return GST_FLOW_ERROR;
307 }
308 break;
309 }
310 }
311 /* If start wasn't found: */
312 if (i == size - 4) {
313 gst_adapter_flush (adapter, size - 4);
314 return GST_VIDEO_DECODER_FLOW_NEED_DATA;
315 }
316
317 for (i = size - 6; i >= 5; i--) {
318 if (memcmp (data + i, "</svg>", 6) == 0) {
319 completed = TRUE;
320 size = i + 6;
321 break;
322 }
323 if (memcmp (data + i, "</svg:svg>", 10) == 0) {
324 completed = TRUE;
325 size = i + 10;
326 break;
327 }
328 }
329
330 if (completed) {
331
332 GST_LOG_OBJECT (decoder, "have complete svg of %u bytes", size);
333
334 gst_video_decoder_add_to_frame (decoder, size);
335 return gst_video_decoder_have_frame (decoder);
336 }
337 return GST_VIDEO_DECODER_FLOW_NEED_DATA;
338 }
339
340 static GstFlowReturn
gst_rsvg_dec_handle_frame(GstVideoDecoder * decoder,GstVideoCodecFrame * frame)341 gst_rsvg_dec_handle_frame (GstVideoDecoder * decoder,
342 GstVideoCodecFrame * frame)
343 {
344 GstRsvgDec *rsvg = GST_RSVG_DEC (decoder);
345 gboolean ret;
346
347 ret = gst_rsvg_decode_image (rsvg, frame->input_buffer, frame);
348 switch (ret) {
349 case GST_FLOW_OK:
350 ret = gst_video_decoder_finish_frame (decoder, frame);
351 break;
352 default:
353 gst_video_codec_frame_unref (frame);
354 break;
355 }
356
357 GST_LOG_OBJECT (rsvg, "Handle frame done");
358 return ret;
359 }
360
361 static gboolean
gst_rsvg_dec_stop(GstVideoDecoder * decoder)362 gst_rsvg_dec_stop (GstVideoDecoder * decoder)
363 {
364 GstRsvgDec *rsvg = GST_RSVG_DEC (decoder);
365
366 if (rsvg->input_state) {
367 gst_video_codec_state_unref (rsvg->input_state);
368 rsvg->input_state = NULL;
369 }
370
371 return TRUE;
372 }
373