1 /*
2 * GPAC - Multimedia Framework C SDK
3 *
4 * Authors: Jean Le Feuvre
5 * Copyright (c) Telecom ParisTech 2018
6 * All rights reserved
7 *
8 * This file is part of GPAC / libpng encoder filter
9 *
10 * GPAC is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU Lesser General Public License as published by
12 * the Free Software Foundation; either version 2, or (at your option)
13 * any later version.
14 *
15 * GPAC is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; see the file COPYING. If not, write to
22 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23 *
24 */
25
26 #include <gpac/filters.h>
27 #include <gpac/constants.h>
28 #include <gpac/avparse.h>
29
30 #ifdef GPAC_HAS_PNG
31
32 #include <png.h>
33
34 typedef struct
35 {
36 //opts
37 u32 dctmode;
38 u32 quality;
39
40 GF_FilterPid *ipid, *opid;
41 u32 width, height, pixel_format, stride, stride_uv, nb_planes, uv_height;
42 u32 nb_alloc_rows;
43
44 u32 max_size, pos, alloc_size;
45 u32 png_type;
46 png_bytep *row_pointers;
47
48 GF_FilterPacket *dst_pck;
49 u8 *output;
50
51 } GF_PNGEncCtx;
52
pngenc_configure_pid(GF_Filter * filter,GF_FilterPid * pid,Bool is_remove)53 static GF_Err pngenc_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
54 {
55 const GF_PropertyValue *prop;
56 GF_PNGEncCtx *ctx = (GF_PNGEncCtx *) gf_filter_get_udta(filter);
57
58 //disconnect of src pid (not yet supported)
59 if (is_remove) {
60 //one in one out, this is simple
61 gf_filter_pid_remove(ctx->opid);
62 ctx->ipid = NULL;
63 return GF_OK;
64 }
65 if (! gf_filter_pid_check_caps(pid))
66 return GF_NOT_SUPPORTED;
67
68 ctx->ipid = pid;
69 if (!ctx->opid) {
70 ctx->opid = gf_filter_pid_new(filter);
71 }
72 //copy properties at init or reconfig
73 gf_filter_pid_copy_properties(ctx->opid, ctx->ipid);
74 gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, & PROP_UINT( GF_CODECID_PNG ));
75 gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE, NULL);
76 gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STRIDE_UV, NULL);
77 gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG, NULL);
78 gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_DECODER_CONFIG_ENHANCEMENT, NULL);
79
80 gf_filter_set_name(filter, "encpng:"PNG_LIBPNG_VER_STRING );
81 //not yeat ready
82 prop = gf_filter_pid_get_property(pid, GF_PROP_PID_WIDTH);
83 if (!prop) return GF_OK;
84 ctx->width = prop->value.uint;
85
86 prop = gf_filter_pid_get_property(pid, GF_PROP_PID_HEIGHT);
87 if (!prop) return GF_OK;
88 ctx->height = prop->value.uint;
89
90 prop = gf_filter_pid_get_property(pid, GF_PROP_PID_PIXFMT);
91 if (!prop) return GF_OK;
92 ctx->pixel_format = prop->value.uint;
93
94 prop = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE);
95 if (prop) ctx->stride = prop->value.uint;
96
97 prop = gf_filter_pid_get_property(pid, GF_PROP_PID_STRIDE_UV);
98 if (prop) ctx->stride_uv = prop->value.uint;
99
100 gf_pixel_get_size_info(ctx->pixel_format, ctx->width, ctx->height, NULL, &ctx->stride, &ctx->stride_uv, &ctx->nb_planes, &ctx->uv_height);
101
102 switch (ctx->pixel_format) {
103 case GF_PIXEL_GREYSCALE:
104 ctx->png_type = PNG_COLOR_TYPE_GRAY;
105 break;
106 case GF_PIXEL_GREYALPHA:
107 ctx->png_type = PNG_COLOR_TYPE_GRAY_ALPHA;
108 break;
109 case GF_PIXEL_RGB:
110 case GF_PIXEL_BGR:
111 case GF_PIXEL_RGBX:
112 case GF_PIXEL_XRGB:
113 case GF_PIXEL_BGRX:
114 case GF_PIXEL_XBGR:
115 ctx->png_type = PNG_COLOR_TYPE_RGB;
116 break;
117 case GF_PIXEL_RGBA:
118 ctx->png_type = PNG_COLOR_TYPE_RGB_ALPHA;
119 break;
120 default:
121 gf_filter_pid_negociate_property(pid, GF_PROP_PID_PIXFMT, &PROP_UINT(GF_PIXEL_RGB));
122 break;
123 }
124 if (ctx->height > ctx->nb_alloc_rows) {
125 ctx->nb_alloc_rows = ctx->height;
126 ctx->row_pointers = gf_realloc(ctx->row_pointers, sizeof(png_bytep) * ctx->height);
127 }
128 return GF_OK;
129 }
130
pngenc_finalize(GF_Filter * filter)131 static void pngenc_finalize(GF_Filter *filter)
132 {
133 GF_PNGEncCtx *ctx = (GF_PNGEncCtx *) gf_filter_get_udta(filter);
134 if (ctx->row_pointers) gf_free(ctx->row_pointers);
135 }
136
137 #define PNG_BLOCK_SIZE 4096
138
pngenc_write(png_structp png,png_bytep data,png_size_t size)139 static void pngenc_write(png_structp png, png_bytep data, png_size_t size)
140 {
141 GF_PNGEncCtx *ctx = (GF_PNGEncCtx *)png_get_io_ptr(png);
142 if (!ctx->dst_pck) {
143 while (ctx->alloc_size<size) ctx->alloc_size+=PNG_BLOCK_SIZE;
144 ctx->dst_pck = gf_filter_pck_new_alloc(ctx->opid, ctx->alloc_size, &ctx->output);
145 } else if (ctx->pos + size > ctx->alloc_size) {
146 u8 *new_data;
147 u32 new_size;
148 u32 old_size = ctx->alloc_size;
149 while (ctx->pos + size > ctx->alloc_size)
150 ctx->alloc_size += PNG_BLOCK_SIZE;
151
152 if (gf_filter_pck_expand(ctx->dst_pck, ctx->alloc_size - old_size, &ctx->output, &new_data, &new_size) != GF_OK) {
153 return;
154 }
155 }
156
157 memcpy(ctx->output + ctx->pos, data, sizeof(char)*size);
158 ctx->pos += (u32) size;
159 }
160
pngenc_flush(png_structp png)161 void pngenc_flush(png_structp png)
162 {
163 if (!png) {
164 GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[PNGEnc] coverage test\n"));
165 }
166 }
167
pngenc_error(png_structp cbk,png_const_charp msg)168 static void pngenc_error(png_structp cbk, png_const_charp msg)
169 {
170 if (msg) {
171 GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[PNGEnc] Error %s", msg));
172 } else {
173 GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[PNGEnc] coverage test\n"));
174 }
175 }
pngenc_warn(png_structp cbk,png_const_charp msg)176 static void pngenc_warn(png_structp cbk, png_const_charp msg)
177 {
178 if (msg) {
179 GF_LOG(GF_LOG_WARNING, GF_LOG_CODEC, ("[PNGEnc] Warning %s", msg));
180 } else {
181 GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[PNGEnc] coverage test\n"));
182 }
183 }
184
pngenc_process(GF_Filter * filter)185 static GF_Err pngenc_process(GF_Filter *filter)
186 {
187 GF_FilterPacket *pck;
188 GF_PNGEncCtx *ctx = (GF_PNGEncCtx *) gf_filter_get_udta(filter);
189 png_color_8 sig_bit;
190 u32 k;
191 GF_Err e = GF_OK;
192 png_structp png_ptr;
193 png_infop info_ptr;
194 char *in_data;
195 u32 size, stride;
196
197 pck = gf_filter_pid_get_packet(ctx->ipid);
198 if (!pck) {
199 if (gf_filter_pid_is_eos(ctx->ipid)) {
200 gf_filter_pid_set_eos(ctx->opid);
201 return GF_EOS;
202 }
203 return GF_OK;
204 }
205 stride = ctx->stride;
206 in_data = (char *) gf_filter_pck_get_data(pck, &size);
207 if (!in_data) {
208 GF_FilterFrameInterface *frame_ifce = gf_filter_pck_get_frame_interface(pck);
209 if (!frame_ifce || !frame_ifce->get_plane) {
210 gf_filter_pid_drop_packet(ctx->ipid);
211 return GF_NOT_SUPPORTED;
212 }
213 e = frame_ifce->get_plane(frame_ifce, 0, (const u8 **) &in_data, &stride);
214 if (e) {
215 GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[PNGEnc] Failed to fetch first plane in hardware frame\n"));
216 gf_filter_pid_drop_packet(ctx->ipid);
217 return GF_NOT_SUPPORTED;
218 }
219 }
220
221 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, ctx, pngenc_error, pngenc_warn);
222
223 if (png_ptr == NULL) {
224 gf_filter_pid_drop_packet(ctx->ipid);
225 return GF_IO_ERR;
226 }
227
228 /* Allocate/initialize the image information data. REQUIRED */
229 info_ptr = png_create_info_struct(png_ptr);
230 if (info_ptr == NULL) {
231 png_destroy_write_struct(&png_ptr, NULL);
232 gf_filter_pid_drop_packet(ctx->ipid);
233 return GF_IO_ERR;
234 }
235
236 /* Set error handling. REQUIRED if you aren't supplying your own
237 * error handling functions in the png_create_write_struct() call.
238 */
239 if (setjmp(png_jmpbuf(png_ptr))) {
240 e = GF_NON_COMPLIANT_BITSTREAM;
241 goto exit;
242 }
243
244 ctx->output = NULL;
245 ctx->pos = 0;
246 if (ctx->max_size) {
247 ctx->dst_pck = gf_filter_pck_new_alloc(ctx->opid, ctx->max_size, &ctx->output);
248 ctx->alloc_size = ctx->max_size;
249 }
250 png_set_write_fn(png_ptr, ctx, pngenc_write, pngenc_flush);
251
252 png_set_IHDR(png_ptr, info_ptr, ctx->width, ctx->height, 8, ctx->png_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
253
254 memset(&sig_bit, 0, sizeof(sig_bit));
255 switch (ctx->png_type) {
256 case PNG_COLOR_TYPE_GRAY:
257 sig_bit.gray = 8;
258 break;
259 case PNG_COLOR_TYPE_GRAY_ALPHA:
260 sig_bit.gray = 8;
261 sig_bit.alpha = 8;
262 break;
263 case PNG_COLOR_TYPE_RGB_ALPHA:
264 sig_bit.alpha = 8;
265 case PNG_COLOR_TYPE_RGB:
266 sig_bit.red = 8;
267 sig_bit.green = 8;
268 sig_bit.blue = 8;
269 break;
270 default:
271 break;
272 }
273 png_set_sBIT(png_ptr, info_ptr, &sig_bit);
274
275 //todo add support for tags
276 #if 0
277 {
278 png_text text_ptr[3];
279 /* Optionally write comments into the image */
280 text_ptr[0].key = "Title";
281 text_ptr[0].text = "Mona Lisa";
282 text_ptr[0].compression = PNG_TEXT_COMPRESSION_NONE;
283 text_ptr[1].key = "Author";
284 text_ptr[1].text = "Leonardo DaVinci";
285 text_ptr[1].compression = PNG_TEXT_COMPRESSION_NONE;
286 text_ptr[2].key = "Description";
287 text_ptr[2].text = "<long text>";
288 text_ptr[2].compression = PNG_TEXT_COMPRESSION_zTXt;
289 png_set_text(png_ptr, info_ptr, text_ptr, 3);
290 }
291 #endif
292
293 png_write_info(png_ptr, info_ptr);
294
295 /* Shift the pixels up to a legal bit depth and fill in
296 * as appropriate to correctly scale the image.
297 */
298 png_set_shift(png_ptr, &sig_bit);
299
300 /* pack pixels into bytes */
301 png_set_packing(png_ptr);
302
303 switch (ctx->pixel_format) {
304 case GF_PIXEL_ARGB:
305 png_set_bgr(png_ptr);
306 break;
307 case GF_PIXEL_RGBX:
308 png_set_filler(png_ptr, 0, PNG_FILLER_AFTER);
309 png_set_bgr(png_ptr);
310 break;
311 case GF_PIXEL_BGRX:
312 png_set_filler(png_ptr, 0, PNG_FILLER_AFTER);
313 break;
314 case GF_PIXEL_BGR:
315 png_set_bgr(png_ptr);
316 break;
317 }
318 for (k=0; k<ctx->height; k++) {
319 ctx->row_pointers[k] = (png_bytep) in_data + k*stride;
320 }
321
322 png_write_image(png_ptr, ctx->row_pointers);
323 png_write_end(png_ptr, info_ptr);
324
325 exit:
326 /* clean up after the write, and free any memory allocated */
327 png_destroy_write_struct(&png_ptr, &info_ptr);
328 if (ctx->dst_pck) {
329 if (!e) {
330 gf_filter_pck_truncate(ctx->dst_pck, ctx->pos);
331 gf_filter_pck_merge_properties(pck, ctx->dst_pck);
332 gf_filter_pck_send(ctx->dst_pck);
333 } else {
334 gf_filter_pck_discard(ctx->dst_pck);
335 }
336 }
337 if (ctx->max_size<ctx->pos)
338 ctx->max_size = ctx->pos;
339
340 ctx->dst_pck = NULL;
341 ctx->output = NULL;
342 ctx->pos = ctx->alloc_size = 0;
343 gf_filter_pid_drop_packet(ctx->ipid);
344 return GF_OK;
345 }
346
pngenc_initialize(GF_Filter * filter)347 static GF_Err pngenc_initialize(GF_Filter *filter)
348 {
349 #ifdef GPAC_ENABLE_COVERAGE
350 if (gf_sys_is_cov_mode()) {
351 pngenc_flush(NULL);
352 pngenc_error(NULL, NULL);
353 pngenc_warn(NULL, NULL);
354 }
355 #endif
356 return GF_OK;
357 }
358
359 static const GF_FilterCapability PNGEncCaps[] =
360 {
361 CAP_UINT(GF_CAPS_INPUT_OUTPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_VISUAL),
362 CAP_UINT(GF_CAPS_INPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW),
363 CAP_UINT(GF_CAPS_OUTPUT,GF_PROP_PID_CODECID, GF_CODECID_PNG)
364 };
365
366 GF_FilterRegister PNGEncRegister = {
367 .name = "pngenc",
368 GF_FS_SET_DESCRIPTION("PNG encoder")
369 GF_FS_SET_HELP("This filter encodes a single uncompressed video PID to PNG using libpng.")
370 .private_size = sizeof(GF_PNGEncCtx),
371 .initialize = pngenc_initialize,
372 .finalize = pngenc_finalize,
373 SETCAPS(PNGEncCaps),
374 .configure_pid = pngenc_configure_pid,
375 .process = pngenc_process,
376 };
377
378 #endif
379
pngenc_register(GF_FilterSession * session)380 const GF_FilterRegister *pngenc_register(GF_FilterSession *session)
381 {
382 #ifdef GPAC_HAS_PNG
383 return &PNGEncRegister;
384 #else
385 return NULL;
386 #endif
387 }
388