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