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