Use new gst_element_class_set_static_metadata()
[platform/upstream/gstreamer.git] / ext / libpng / gstpngenc.c
1 /* GStreamer
2  * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3  *
4  * Filter:
5  * Copyright (C) 2000 Donald A. Graft
6  *
7  * This library is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
10  * Library General Public License for more details.
11  *
12  * You should have received a copy of the GNU Library General Public
13  * License along with this library; if not, write to the
14  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
15  * Boston, MA 02111-1307, USA.
16  *
17  */
18 /**
19  * SECTION:element-pngenc
20  *
21  * Encodes png images.
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27 #include <string.h>
28 #include <gst/gst.h>
29 #include "gstpngenc.h"
30 #include <zlib.h>
31
32 GST_DEBUG_CATEGORY_STATIC (pngenc_debug);
33 #define GST_CAT_DEFAULT pngenc_debug
34
35 /* Filter signals and args */
36 enum
37 {
38   /* FILL ME */
39   LAST_SIGNAL
40 };
41
42 #define DEFAULT_SNAPSHOT                FALSE
43 /* #define DEFAULT_NEWMEDIA             FALSE */
44 #define DEFAULT_COMPRESSION_LEVEL       6
45
46 enum
47 {
48   ARG_0,
49   ARG_SNAPSHOT,
50 /*   ARG_NEWMEDIA, */
51   ARG_COMPRESSION_LEVEL
52 };
53
54 static GstStaticPadTemplate pngenc_src_template =
55 GST_STATIC_PAD_TEMPLATE ("src",
56     GST_PAD_SRC,
57     GST_PAD_ALWAYS,
58     GST_STATIC_CAPS ("image/png, "
59         "width = (int) [ 16, 1000000 ], "
60         "height = (int) [ 16, 1000000 ], " "framerate = " GST_VIDEO_FPS_RANGE)
61     );
62
63 static GstStaticPadTemplate pngenc_sink_template =
64 GST_STATIC_PAD_TEMPLATE ("sink",
65     GST_PAD_SINK,
66     GST_PAD_ALWAYS,
67     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ RGBA, RGB, GRAY8, GRAY16_BE }"))
68     );
69
70 /* static GstElementClass *parent_class = NULL; */
71
72 G_DEFINE_TYPE (GstPngEnc, gst_pngenc, GST_TYPE_ELEMENT);
73
74 static void gst_pngenc_set_property (GObject * object,
75     guint prop_id, const GValue * value, GParamSpec * pspec);
76 static void gst_pngenc_get_property (GObject * object,
77     guint prop_id, GValue * value, GParamSpec * pspec);
78
79 static GstFlowReturn gst_pngenc_chain (GstPad * pad, GstObject * parent,
80     GstBuffer * data);
81 static gboolean gst_pngenc_sink_event (GstPad * pad, GstObject * parent,
82     GstEvent * event);
83
84 static void
85 user_error_fn (png_structp png_ptr, png_const_charp error_msg)
86 {
87   g_warning ("%s", error_msg);
88 }
89
90 static void
91 user_warning_fn (png_structp png_ptr, png_const_charp warning_msg)
92 {
93   g_warning ("%s", warning_msg);
94 }
95
96 static void
97 gst_pngenc_class_init (GstPngEncClass * klass)
98 {
99   GObjectClass *gobject_class;
100   GstElementClass *element_class;
101
102   gobject_class = (GObjectClass *) klass;
103   element_class = (GstElementClass *) klass;
104
105   gobject_class->get_property = gst_pngenc_get_property;
106   gobject_class->set_property = gst_pngenc_set_property;
107
108   g_object_class_install_property (gobject_class, ARG_SNAPSHOT,
109       g_param_spec_boolean ("snapshot", "Snapshot",
110           "Send EOS after encoding a frame, useful for snapshots",
111           DEFAULT_SNAPSHOT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
112
113 /*   g_object_class_install_property (gobject_class, ARG_NEWMEDIA, */
114 /*       g_param_spec_boolean ("newmedia", "newmedia", */
115 /*           "Send new media discontinuity after encoding each frame", */
116 /*           DEFAULT_NEWMEDIA, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); */
117
118   g_object_class_install_property (gobject_class, ARG_COMPRESSION_LEVEL,
119       g_param_spec_uint ("compression-level", "compression-level",
120           "PNG compression level",
121           Z_NO_COMPRESSION, Z_BEST_COMPRESSION,
122           DEFAULT_COMPRESSION_LEVEL,
123           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
124
125   gst_element_class_add_pad_template
126       (element_class, gst_static_pad_template_get (&pngenc_sink_template));
127   gst_element_class_add_pad_template
128       (element_class, gst_static_pad_template_get (&pngenc_src_template));
129   gst_element_class_set_static_metadata (element_class, "PNG image encoder",
130       "Codec/Encoder/Image",
131       "Encode a video frame to a .png image",
132       "Jeremy SIMON <jsimon13@yahoo.fr>");
133
134   GST_DEBUG_CATEGORY_INIT (pngenc_debug, "pngenc", 0, "PNG image encoder");
135 }
136
137
138 static gboolean
139 gst_pngenc_setcaps (GstPngEnc * pngenc, GstCaps * caps)
140 {
141   int fps_n, fps_d;
142   GstCaps *pcaps;
143   gboolean ret;
144   GstVideoInfo info;
145
146   ret = gst_video_info_from_caps (&info, caps);
147
148   if (G_UNLIKELY (!ret))
149     goto done;
150
151   pngenc->info = info;
152
153   switch (GST_VIDEO_INFO_FORMAT (&info)) {
154     case GST_VIDEO_FORMAT_RGBA:
155       pngenc->png_color_type = PNG_COLOR_TYPE_RGBA;
156       pngenc->depth = 8;
157       break;
158     case GST_VIDEO_FORMAT_RGB:
159       pngenc->png_color_type = PNG_COLOR_TYPE_RGB;
160       pngenc->depth = 8;
161       break;
162     case GST_VIDEO_FORMAT_GRAY8:
163       pngenc->png_color_type = PNG_COLOR_TYPE_GRAY;
164       pngenc->depth = 8;
165       break;
166     case GST_VIDEO_FORMAT_GRAY16_BE:
167       pngenc->png_color_type = PNG_COLOR_TYPE_GRAY;
168       pngenc->depth = 16;
169       break;
170     default:
171       ret = FALSE;
172       goto done;
173   }
174
175   pngenc->width = GST_VIDEO_INFO_WIDTH (&info);
176   pngenc->height = GST_VIDEO_INFO_HEIGHT (&info);
177   fps_n = GST_VIDEO_INFO_FPS_N (&info);
178   fps_d = GST_VIDEO_INFO_FPS_D (&info);
179
180   if (G_UNLIKELY (pngenc->width < 16 || pngenc->width > 1000000 ||
181           pngenc->height < 16 || pngenc->height > 1000000)) {
182     ret = FALSE;
183     goto done;
184   }
185
186   pcaps = gst_caps_new_simple ("image/png",
187       "width", G_TYPE_INT, pngenc->width,
188       "height", G_TYPE_INT, pngenc->height,
189       "framerate", GST_TYPE_FRACTION, fps_n, fps_d, NULL);
190
191   ret = gst_pad_set_caps (pngenc->srcpad, pcaps);
192
193   gst_caps_unref (pcaps);
194
195   /* Fall-through. */
196 done:
197   if (G_UNLIKELY (!ret)) {
198     pngenc->width = 0;
199     pngenc->height = 0;
200   }
201
202   return ret;
203 }
204
205 static void
206 gst_pngenc_init (GstPngEnc * pngenc)
207 {
208   /* sinkpad */
209   pngenc->sinkpad = gst_pad_new_from_static_template
210       (&pngenc_sink_template, "sink");
211   gst_pad_set_chain_function (pngenc->sinkpad,
212       GST_DEBUG_FUNCPTR (gst_pngenc_chain));
213   gst_pad_set_event_function (pngenc->sinkpad,
214       GST_DEBUG_FUNCPTR (gst_pngenc_sink_event));
215   gst_element_add_pad (GST_ELEMENT (pngenc), pngenc->sinkpad);
216
217   /* srcpad */
218   pngenc->srcpad = gst_pad_new_from_static_template
219       (&pngenc_src_template, "src");
220   gst_pad_use_fixed_caps (pngenc->srcpad);
221   gst_element_add_pad (GST_ELEMENT (pngenc), pngenc->srcpad);
222
223   /* init settings */
224   pngenc->png_struct_ptr = NULL;
225   pngenc->png_info_ptr = NULL;
226
227   pngenc->snapshot = DEFAULT_SNAPSHOT;
228 /*   pngenc->newmedia = FALSE; */
229   pngenc->compression_level = DEFAULT_COMPRESSION_LEVEL;
230 }
231
232 static void
233 user_flush_data (png_structp png_ptr G_GNUC_UNUSED)
234 {
235 }
236
237 static void
238 user_write_data (png_structp png_ptr, png_bytep data, png_uint_32 length)
239 {
240   GstPngEnc *pngenc;
241   GstMapInfo map;
242
243   pngenc = (GstPngEnc *) png_get_io_ptr (png_ptr);
244
245   gst_buffer_map (pngenc->buffer_out, &map, GST_MAP_WRITE);
246   if (pngenc->written + length >= map.size) {
247     gst_buffer_unmap (pngenc->buffer_out, &map);
248     GST_ERROR_OBJECT (pngenc, "output buffer bigger than the input buffer!?");
249     png_error (png_ptr, "output buffer bigger than the input buffer!?");
250
251     /* never reached */
252     return;
253   }
254
255   GST_DEBUG_OBJECT (pngenc, "writing %u bytes", (guint) length);
256
257   memcpy (map.data + pngenc->written, data, length);
258   gst_buffer_unmap (pngenc->buffer_out, &map);
259   pngenc->written += length;
260 }
261
262 static GstFlowReturn
263 gst_pngenc_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
264 {
265   GstPngEnc *pngenc;
266   gint row_index;
267   png_byte **row_pointers;
268   GstFlowReturn ret = GST_FLOW_OK;
269   GstBuffer *encoded_buf = NULL;
270   GstVideoFrame frame;
271
272   pngenc = GST_PNGENC (parent);
273
274   GST_DEBUG_OBJECT (pngenc, "BEGINNING");
275
276   if (G_UNLIKELY (pngenc->width <= 0 || pngenc->height <= 0)) {
277     ret = GST_FLOW_NOT_NEGOTIATED;
278     goto exit;
279   }
280
281   if (!gst_video_frame_map (&frame, &pngenc->info, buf, GST_MAP_READ)) {
282     GST_ELEMENT_ERROR (pngenc, STREAM, FORMAT, (NULL),
283         ("Failed to map video frame, caps problem?"));
284     ret = GST_FLOW_ERROR;
285     goto exit;
286   }
287
288   /* initialize png struct stuff */
289   pngenc->png_struct_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING,
290       (png_voidp) NULL, user_error_fn, user_warning_fn);
291   if (pngenc->png_struct_ptr == NULL) {
292     GST_ELEMENT_ERROR (pngenc, LIBRARY, INIT, (NULL),
293         ("Failed to initialize png structure"));
294     ret = GST_FLOW_ERROR;
295     goto done;
296   }
297
298   pngenc->png_info_ptr = png_create_info_struct (pngenc->png_struct_ptr);
299   if (!pngenc->png_info_ptr) {
300     png_destroy_write_struct (&(pngenc->png_struct_ptr), (png_infopp) NULL);
301     GST_ELEMENT_ERROR (pngenc, LIBRARY, INIT, (NULL),
302         ("Failed to initialize the png info structure"));
303     ret = GST_FLOW_ERROR;
304     goto done;
305   }
306
307   /* non-0 return is from a longjmp inside of libpng */
308   if (setjmp (png_jmpbuf (pngenc->png_struct_ptr)) != 0) {
309     png_destroy_write_struct (&pngenc->png_struct_ptr, &pngenc->png_info_ptr);
310     GST_ELEMENT_ERROR (pngenc, LIBRARY, FAILED, (NULL),
311         ("returning from longjmp"));
312     ret = GST_FLOW_ERROR;
313     goto done;
314   }
315
316   png_set_filter (pngenc->png_struct_ptr, 0,
317       PNG_FILTER_NONE | PNG_FILTER_VALUE_NONE);
318   png_set_compression_level (pngenc->png_struct_ptr, pngenc->compression_level);
319
320   png_set_IHDR (pngenc->png_struct_ptr,
321       pngenc->png_info_ptr,
322       pngenc->width,
323       pngenc->height,
324       pngenc->depth,
325       pngenc->png_color_type,
326       PNG_INTERLACE_NONE,
327       PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
328
329   png_set_write_fn (pngenc->png_struct_ptr, pngenc,
330       (png_rw_ptr) user_write_data, user_flush_data);
331
332   row_pointers = g_new (png_byte *, pngenc->height);
333
334   for (row_index = 0; row_index < pngenc->height; row_index++) {
335     row_pointers[row_index] = GST_VIDEO_FRAME_COMP_DATA (&frame, 0) +
336         (row_index * GST_VIDEO_FRAME_COMP_STRIDE (&frame, 0));
337   }
338
339   /* allocate the output buffer */
340   pngenc->buffer_out =
341       gst_buffer_new_and_alloc (pngenc->height * pngenc->width);
342   pngenc->written = 0;
343
344   png_write_info (pngenc->png_struct_ptr, pngenc->png_info_ptr);
345   png_write_image (pngenc->png_struct_ptr, row_pointers);
346   png_write_end (pngenc->png_struct_ptr, NULL);
347
348   g_free (row_pointers);
349
350   GST_DEBUG_OBJECT (pngenc, "written %d", pngenc->written);
351
352   encoded_buf =
353       gst_buffer_copy_region (pngenc->buffer_out, GST_BUFFER_COPY_MEMORY,
354       0, pngenc->written);
355
356   png_destroy_info_struct (pngenc->png_struct_ptr, &pngenc->png_info_ptr);
357   png_destroy_write_struct (&pngenc->png_struct_ptr, (png_infopp) NULL);
358
359   GST_BUFFER_TIMESTAMP (encoded_buf) = GST_BUFFER_TIMESTAMP (buf);
360   GST_BUFFER_DURATION (encoded_buf) = GST_BUFFER_DURATION (buf);
361
362   if ((ret = gst_pad_push (pngenc->srcpad, encoded_buf)) != GST_FLOW_OK)
363     goto done;
364
365   if (pngenc->snapshot) {
366     GstEvent *event;
367
368     GST_DEBUG_OBJECT (pngenc, "snapshot mode, sending EOS");
369     /* send EOS event, since a frame has been pushed out */
370     event = gst_event_new_eos ();
371
372     gst_pad_push_event (pngenc->srcpad, event);
373     ret = GST_FLOW_EOS;
374   }
375
376 done:
377   gst_video_frame_unmap (&frame);
378 exit:
379   gst_buffer_unref (buf);
380   GST_DEBUG_OBJECT (pngenc, "END, ret:%d", ret);
381
382   if (pngenc->buffer_out != NULL) {
383     gst_buffer_unref (pngenc->buffer_out);
384     pngenc->buffer_out = NULL;
385   }
386
387   return ret;
388 }
389
390 static gboolean
391 gst_pngenc_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
392 {
393   GstPngEnc *enc;
394   gboolean res;
395
396   enc = GST_PNGENC (parent);
397
398   switch (GST_EVENT_TYPE (event)) {
399     case GST_EVENT_CAPS:
400     {
401       GstCaps *caps;
402
403       gst_event_parse_caps (event, &caps);
404       res = gst_pngenc_setcaps (enc, caps);
405       gst_event_unref (event);
406       break;
407     }
408     default:
409       res = gst_pad_push_event (enc->srcpad, event);
410       break;
411   }
412   return res;
413 }
414
415 static void
416 gst_pngenc_get_property (GObject * object,
417     guint prop_id, GValue * value, GParamSpec * pspec)
418 {
419   GstPngEnc *pngenc;
420
421   pngenc = GST_PNGENC (object);
422
423   switch (prop_id) {
424     case ARG_SNAPSHOT:
425       g_value_set_boolean (value, pngenc->snapshot);
426       break;
427 /*     case ARG_NEWMEDIA: */
428 /*       g_value_set_boolean (value, pngenc->newmedia); */
429 /*       break; */
430     case ARG_COMPRESSION_LEVEL:
431       g_value_set_uint (value, pngenc->compression_level);
432       break;
433     default:
434       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
435       break;
436   }
437 }
438
439
440 static void
441 gst_pngenc_set_property (GObject * object,
442     guint prop_id, const GValue * value, GParamSpec * pspec)
443 {
444   GstPngEnc *pngenc;
445
446   pngenc = GST_PNGENC (object);
447
448   switch (prop_id) {
449     case ARG_SNAPSHOT:
450       pngenc->snapshot = g_value_get_boolean (value);
451       break;
452 /*     case ARG_NEWMEDIA: */
453 /*       pngenc->newmedia = g_value_get_boolean (value); */
454 /*       break; */
455     case ARG_COMPRESSION_LEVEL:
456       pngenc->compression_level = g_value_get_uint (value);
457       break;
458     default:
459       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
460       break;
461   }
462 }