| /* | |
| * GPAC - Multimedia Framework C SDK | |
| * | |
| * Authors: Jean Le Feuvre | |
| * Copyright (c) Telecom ParisTech 2000-2022 | |
| * All rights reserved | |
| * | |
| * This file is part of GPAC / Media Tools sub-project | |
| * | |
| * GPAC is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU Lesser General Public License as published by | |
| * the Free Software Foundation; either version 2, or (at your option) | |
| * any later version. | |
| * | |
| * GPAC is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; see the file COPYING. If not, write to | |
| * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | |
| * | |
| */ | |
| #include <gpac/internal/media_dev.h> | |
| #include <gpac/internal/isomedia_dev.h> | |
| #include <gpac/constants.h> | |
| #ifdef GPAC_HAS_PNG | |
| #if (defined(WIN32) || defined(_WIN32_WCE)) && !defined(__GNUC__) | |
| #pragma comment(lib, "libpng") | |
| #endif | |
| /*include png.h before setjmp.h, otherwise we get compilation errors*/ | |
| #include <png.h> | |
| #endif /*GPAC_HAS_PNG*/ | |
| #ifdef GPAC_HAS_JPEG | |
| #if (defined(WIN32) || defined(_WIN32_WCE)) && !defined(__GNUC__) | |
| #pragma comment(lib, "libjpeg") | |
| #endif | |
| #include <jpeglib.h> | |
| #include <setjmp.h> | |
| #endif /*GPAC_HAS_JPEG*/ | |
| GF_EXPORT | |
| void gf_img_parse(GF_BitStream *bs, u32 *codecid, u32 *width, u32 *height, u8 **dsi, u32 *dsi_len) | |
| { | |
| u8 b1, b2, b3; | |
| u32 size, type; | |
| u64 pos; | |
| pos = gf_bs_get_position(bs); | |
| gf_bs_seek(bs, 0); | |
| *width = *height = 0; | |
| *codecid = 0; | |
| if (dsi) { | |
| *dsi = NULL; | |
| *dsi_len = 0; | |
| } | |
| b1 = gf_bs_read_u8(bs); | |
| b2 = gf_bs_read_u8(bs); | |
| b3 = gf_bs_read_u8(bs); | |
| /*JPEG*/ | |
| if ((b1==0xFF) && (b2==0xD8) && (b3==0xFF)) { | |
| u32 offset = 0; | |
| u32 Xdens, Ydens, nb_comp; | |
| gf_bs_read_u8(bs); | |
| gf_bs_skip_bytes(bs, 10); /*2 size, 5 JFIF\0, 2 version, 1 units*/ | |
| Xdens = gf_bs_read_int(bs, 16); | |
| Ydens = gf_bs_read_int(bs, 16); | |
| nb_comp = 0; | |
| /*get frame header FFC0*/ | |
| while (gf_bs_available(bs)) { | |
| u32 w, h,length; | |
| if (gf_bs_read_u8(bs) != 0xFF) continue; | |
| if (!offset) offset = (u32)gf_bs_get_position(bs) - 1; | |
| type = gf_bs_read_u8(bs); | |
| /*process all Start of Image markers*/ | |
| switch (type) { | |
| case 0xC0: | |
| case 0xC1: | |
| case 0xC2: | |
| case 0xC3: | |
| case 0xC5: | |
| case 0xC6: | |
| case 0xC7: | |
| case 0xC9: | |
| case 0xCA: | |
| case 0xCB: | |
| case 0xCD: | |
| case 0xCE: | |
| case 0xCF: | |
| length = gf_bs_read_u16(bs); | |
| /*prec = */gf_bs_read_u8(bs); | |
| h = gf_bs_read_int(bs, 16); | |
| w = gf_bs_read_int(bs, 16); | |
| nb_comp = gf_bs_read_int(bs, 8); | |
| if (length != 8 + 3*nb_comp) continue; //This is not the right marker | |
| if ((w > *width) || (h > *height)) { | |
| *width = w; | |
| *height = h; | |
| } | |
| break; | |
| } | |
| } | |
| *codecid = GF_CODECID_JPEG; | |
| if (dsi) { | |
| GF_BitStream *bs_dsi = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); | |
| gf_bs_write_u16(bs_dsi, offset); | |
| gf_bs_write_u16(bs_dsi, Xdens); | |
| gf_bs_write_u16(bs_dsi, Ydens); | |
| gf_bs_write_u8(bs_dsi, nb_comp); | |
| gf_bs_get_content(bs_dsi, dsi, dsi_len); | |
| gf_bs_del(bs_dsi); | |
| } | |
| } | |
| /*PNG*/ | |
| else if ((b1==0x89) && (b2==0x50) && (b3==0x4E)) { | |
| /*check for PNG sig*/ | |
| if ( (gf_bs_read_u8(bs) != 0x47) || (gf_bs_read_u8(bs) != 0x0D) || (gf_bs_read_u8(bs) != 0x0A) | |
| || (gf_bs_read_u8(bs) != 0x1A) || (gf_bs_read_u8(bs) != 0x0A) ) goto exit; | |
| gf_bs_read_u32(bs); | |
| /*check for PNG IHDR*/ | |
| if ( (gf_bs_read_u8(bs) != 'I') || (gf_bs_read_u8(bs) != 'H') | |
| || (gf_bs_read_u8(bs) != 'D') || (gf_bs_read_u8(bs) != 'R')) goto exit; | |
| *width = gf_bs_read_u32(bs); | |
| *height = gf_bs_read_u32(bs); | |
| *codecid = GF_CODECID_PNG; | |
| } | |
| /* try jpeg200 */ | |
| else { | |
| u32 jp2h_size=0, jp2h_start=0; | |
| size = gf_bs_read_u8(bs); | |
| type = gf_bs_read_u32(bs); | |
| /* try jp2 isomedia */ | |
| if ( ((size==12) && (type==GF_ISOM_BOX_TYPE_JP )) | |
| || (type==GF_ISOM_BOX_TYPE_JP2H ) ) { | |
| if (type==GF_ISOM_BOX_TYPE_JP2H) { | |
| *codecid = GF_CODECID_J2K; | |
| goto j2k_restart; | |
| } | |
| type = gf_bs_read_u32(bs); | |
| if (type!=0x0D0A870A) goto exit; | |
| *codecid = GF_CODECID_J2K; | |
| while (gf_bs_available(bs)) { | |
| j2k_restart: | |
| size = gf_bs_read_u32(bs); | |
| type = gf_bs_read_u32(bs); | |
| switch (type) { | |
| case GF_ISOM_BOX_TYPE_JP2H: | |
| jp2h_size=size-8; | |
| jp2h_start = (u32) gf_bs_get_position(bs); | |
| goto j2k_restart; | |
| case GF_ISOM_BOX_TYPE_IHDR: | |
| { | |
| *height = gf_bs_read_u32(bs); | |
| *width = gf_bs_read_u32(bs); | |
| /*nb_comp = gf_bs_read_u16(bs); | |
| BPC = gf_bs_read_u8(bs); | |
| C = gf_bs_read_u8(bs); | |
| UnkC = gf_bs_read_u8(bs); | |
| IPR = gf_bs_read_u8(bs); | |
| */ | |
| if (dsi && jp2h_size) { | |
| *dsi = gf_malloc(sizeof(char)*jp2h_size); | |
| gf_bs_seek(bs, jp2h_start); | |
| gf_bs_read_data(bs, *dsi, jp2h_size); | |
| *dsi_len = jp2h_size; | |
| } | |
| goto exit; | |
| } | |
| break; | |
| default: | |
| gf_bs_skip_bytes(bs, size-8); | |
| break; | |
| } | |
| } | |
| } | |
| /* try j2k codestream */ | |
| else { | |
| gf_bs_seek(bs, pos); | |
| type = gf_bs_read_u32(bs); | |
| /* Start-of-codestream (SOC) + Image size (SIZ) magic number */ | |
| if (type == 0xFF4FFF51 ) { | |
| /* u32 Lsiz = */gf_bs_read_u16(bs); | |
| /* u32 Rsiz = */gf_bs_read_u16(bs); | |
| *width = gf_bs_read_u32(bs); | |
| *height = gf_bs_read_u32(bs); | |
| *codecid = GF_CODECID_J2K; | |
| } | |
| } | |
| } | |
| exit: | |
| gf_bs_seek(bs, pos); | |
| } | |
| #ifdef GPAC_HAS_JPEG | |
| void gf_jpeg_nonfatal_error2(j_common_ptr cinfo, int lev) | |
| { | |
| } | |
| /*JPG context while decoding*/ | |
| typedef struct | |
| { | |
| struct jpeg_error_mgr pub; | |
| jmp_buf jmpbuf; | |
| } JPGErr; | |
| typedef struct | |
| { | |
| /*io manager*/ | |
| struct jpeg_source_mgr src; | |
| s32 skip; | |
| struct jpeg_decompress_struct cinfo; | |
| } JPGCtx; | |
| static void gf_jpeg_output_message (j_common_ptr cinfo) | |
| { | |
| if (cinfo) { | |
| char buffer[JMSG_LENGTH_MAX]; | |
| /* Create the message */ | |
| (*cinfo->err->format_message) (cinfo, buffer); | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[JPEG OUTPUT MESSAGE]: %s\n", buffer)); | |
| } | |
| } | |
| static void gf_jpeg_fatal_error(j_common_ptr cinfo) | |
| { | |
| if (cinfo) { | |
| JPGErr *err = (JPGErr *) cinfo->err; | |
| gf_jpeg_output_message(cinfo); | |
| longjmp(err->jmpbuf, 1); | |
| } | |
| } | |
| void gf_jpeg_stub(j_decompress_ptr cinfo) {} | |
| /*a JPEG is always carried in a complete, single MPEG4 AU so no refill*/ | |
| static boolean gf_jpeg_fill_input_buffer(j_decompress_ptr cinfo) | |
| { | |
| return 0; | |
| } | |
| static void gf_jpeg_skip_input_data(j_decompress_ptr cinfo, long num_bytes) | |
| { | |
| if (cinfo) { | |
| JPGCtx *jpx = (JPGCtx *) cinfo->src; | |
| if (num_bytes > (long) jpx->src.bytes_in_buffer) { | |
| jpx->skip = (s32) (num_bytes - jpx->src.bytes_in_buffer); | |
| jpx->src.next_input_byte += jpx->src.bytes_in_buffer; | |
| jpx->src.bytes_in_buffer = 0; | |
| } else { | |
| jpx->src.bytes_in_buffer -= num_bytes; | |
| jpx->src.next_input_byte += num_bytes; | |
| jpx->skip = 0; | |
| } | |
| } | |
| } | |
| #define JPEG_MAX_SCAN_BLOCK_HEIGHT 16 | |
| GF_EXPORT | |
| GF_Err gf_img_jpeg_dec(u8 *jpg, u32 jpg_size, u32 *width, u32 *height, u32 *pixel_format, u8 *dst, u32 *dst_size, u32 dst_nb_comp) | |
| { | |
| s32 i, j, scans, k; | |
| u32 stride; | |
| char *scan_line, *ptr, *tmp; | |
| char *lines[JPEG_MAX_SCAN_BLOCK_HEIGHT]; | |
| JPGErr jper; | |
| JPGCtx jpx; | |
| #ifdef GPAC_ENABLE_COVERAGE | |
| if (gf_sys_is_cov_mode()) { | |
| gf_jpeg_fatal_error(NULL); | |
| gf_jpeg_output_message(NULL); | |
| gf_jpeg_nonfatal_error2(NULL, 0); | |
| gf_jpeg_fill_input_buffer(NULL); | |
| gf_jpeg_skip_input_data(NULL, 0); | |
| } | |
| #endif | |
| if (!dst) *dst_size = 0; | |
| jpx.cinfo.err = jpeg_std_error(&(jper.pub)); | |
| jper.pub.error_exit = gf_jpeg_fatal_error; | |
| jper.pub.output_message = gf_jpeg_output_message; | |
| jper.pub.emit_message = gf_jpeg_nonfatal_error2; | |
| if (setjmp(jper.jmpbuf)) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[JPEGDecode] : Failed to decode\n")); | |
| jpeg_destroy_decompress(&jpx.cinfo); | |
| return GF_IO_ERR; | |
| } | |
| /*create decompress struct*/ | |
| jpeg_create_decompress(&jpx.cinfo); | |
| /*prepare IO*/ | |
| jpx.src.init_source = gf_jpeg_stub; | |
| jpx.src.fill_input_buffer = gf_jpeg_fill_input_buffer; | |
| jpx.src.skip_input_data = gf_jpeg_skip_input_data; | |
| jpx.src.resync_to_restart = jpeg_resync_to_restart; | |
| jpx.src.term_source = gf_jpeg_stub; | |
| jpx.skip = 0; | |
| jpx.src.next_input_byte = (JOCTET *) jpg; | |
| jpx.src.bytes_in_buffer = jpg_size; | |
| jpx.cinfo.src = (void *) &jpx.src; | |
| /*read header*/ | |
| do { | |
| i = jpeg_read_header(&jpx.cinfo, TRUE); | |
| } while (i == JPEG_HEADER_TABLES_ONLY); | |
| /*we're supposed to have the full image in the buffer, wrong stream*/ | |
| if (i == JPEG_SUSPENDED) { | |
| jpeg_destroy_decompress(&jpx.cinfo); | |
| return GF_NON_COMPLIANT_BITSTREAM; | |
| } | |
| *width = jpx.cinfo.image_width; | |
| *height = jpx.cinfo.image_height; | |
| stride = *width * jpx.cinfo.num_components; | |
| switch (jpx.cinfo.num_components) { | |
| case 1: | |
| *pixel_format = GF_PIXEL_GREYSCALE; | |
| break; | |
| case 3: | |
| *pixel_format = GF_PIXEL_RGB; | |
| break; | |
| default: | |
| jpeg_destroy_decompress(&jpx.cinfo); | |
| return GF_NON_COMPLIANT_BITSTREAM; | |
| } | |
| if (!dst || (*dst_size < *height * *width * jpx.cinfo.num_components)) { | |
| *dst_size = *height * *width * jpx.cinfo.num_components; | |
| jpeg_destroy_decompress(&jpx.cinfo); | |
| return GF_BUFFER_TOO_SMALL; | |
| } | |
| if (!dst_nb_comp) dst_nb_comp = jpx.cinfo.num_components; | |
| scan_line = NULL; | |
| /*decode*/ | |
| jpx.cinfo.do_fancy_upsampling = FALSE; | |
| jpx.cinfo.do_block_smoothing = FALSE; | |
| if (!jpeg_start_decompress(&jpx.cinfo)) { | |
| jpeg_destroy_decompress(&jpx.cinfo); | |
| return GF_NON_COMPLIANT_BITSTREAM; | |
| } | |
| if (jpx.cinfo.rec_outbuf_height>JPEG_MAX_SCAN_BLOCK_HEIGHT) { | |
| jpeg_destroy_decompress(&jpx.cinfo); | |
| GF_LOG(GF_LOG_WARNING, GF_LOG_CODING, ("[gf_img_jpeg_dec] : jpx.cinfo.rec_outbuf_height>JPEG_MAX_SCAN_BLOCK_HEIGHT\n")); | |
| return GF_IO_ERR; | |
| } | |
| /*read scanlines (the scan is not one line by one line so alloc a placeholder for block scaning) */ | |
| scan_line = gf_malloc(sizeof(char) * stride * jpx.cinfo.rec_outbuf_height); | |
| for (i = 0; i<jpx.cinfo.rec_outbuf_height; i++) { | |
| lines[i] = scan_line + i * stride; | |
| } | |
| tmp = dst; | |
| for (j=0; j< (s32) *height; j += jpx.cinfo.rec_outbuf_height) { | |
| jpeg_read_scanlines(&jpx.cinfo, (unsigned char **) lines, jpx.cinfo.rec_outbuf_height); | |
| scans = jpx.cinfo.rec_outbuf_height; | |
| if (( (s32) *height - j) < scans) scans = *height - j; | |
| ptr = scan_line; | |
| /*for each line in the scan*/ | |
| for (k = 0; k < scans; k++) { | |
| if (dst_nb_comp==(u32)jpx.cinfo.num_components) { | |
| memcpy(tmp, ptr, sizeof(char) * stride); | |
| ptr += stride; | |
| tmp += stride; | |
| } else { | |
| u32 z, c; | |
| for (z=0; z<*width; z++) { | |
| for (c=0; c<(u32)jpx.cinfo.num_components; c++) { | |
| if (c >= dst_nb_comp) break; | |
| tmp[c] = ptr[c]; | |
| } | |
| ptr += jpx.cinfo.num_components; | |
| tmp += dst_nb_comp; | |
| } | |
| } | |
| } | |
| } | |
| /*done*/ | |
| jpeg_finish_decompress(&jpx.cinfo); | |
| jpeg_destroy_decompress(&jpx.cinfo); | |
| gf_free(scan_line); | |
| return GF_OK; | |
| } | |
| #else | |
| GF_EXPORT | |
| GF_Err gf_img_jpeg_dec(u8 *jpg, u32 jpg_size, u32 *width, u32 *height, u32 *pixel_format, u8 *dst, u32 *dst_size, u32 dst_nb_comp) | |
| { | |
| return GF_NOT_SUPPORTED; | |
| } | |
| #endif /*GPAC_HAS_JPEG*/ | |
| #ifdef GPAC_HAS_PNG | |
| typedef struct | |
| { | |
| char *buffer; | |
| u32 pos; | |
| u32 size; | |
| png_byte **rows; | |
| } GFpng; | |
| static void gf_png_user_read_data(png_structp png_ptr, png_bytep data, png_size_t length) | |
| { | |
| GFpng *ctx = (GFpng*)png_get_io_ptr(png_ptr); | |
| if (ctx->pos + length > ctx->size) { | |
| png_error(png_ptr, "Read Error"); | |
| } else { | |
| memcpy(data, (char*) ctx->buffer + ctx->pos, length); | |
| ctx->pos += (u32) length; | |
| } | |
| } | |
| static void gf_png_user_error_fn(png_structp png_ptr,png_const_charp error_msg) | |
| { | |
| if (png_ptr) { | |
| longjmp(png_jmpbuf(png_ptr), 1); | |
| } | |
| } | |
| GF_EXPORT | |
| GF_Err gf_img_png_dec(u8 *png, u32 png_size, u32 *width, u32 *height, u32 *pixel_format, u8 *dst, u32 *dst_size) | |
| { | |
| GFpng udta; | |
| png_struct *png_ptr; | |
| png_info *info_ptr; | |
| u32 i, stride, out_size; | |
| png_bytep trans_alpha; | |
| int num_trans; | |
| png_color_16p trans_color; | |
| if ((png_size<8) || png_sig_cmp((png_bytep)png, 0, 8) ) { | |
| GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[PNG]: Wrong signature\n")); | |
| return GF_NON_COMPLIANT_BITSTREAM; | |
| } | |
| udta.buffer = png; | |
| udta.size = png_size; | |
| udta.pos = 0; | |
| udta.rows=NULL; | |
| #ifdef GPAC_ENABLE_COVERAGE | |
| if (gf_sys_is_cov_mode()) { | |
| gf_png_user_error_fn(NULL, NULL); | |
| } | |
| #endif | |
| png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp) &udta, NULL, NULL); | |
| if (!png_ptr) return GF_IO_ERR; | |
| info_ptr = png_create_info_struct(png_ptr); | |
| if (info_ptr == NULL) { | |
| png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); | |
| return GF_IO_ERR; | |
| } | |
| if (setjmp(png_jmpbuf(png_ptr))) { | |
| png_destroy_info_struct(png_ptr,(png_infopp) & info_ptr); | |
| png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); | |
| if (udta.rows) gf_free(udta.rows); | |
| return GF_IO_ERR; | |
| } | |
| png_set_read_fn(png_ptr, &udta, (png_rw_ptr) gf_png_user_read_data); | |
| png_set_error_fn(png_ptr, &udta, (png_error_ptr) gf_png_user_error_fn, NULL); | |
| png_read_info(png_ptr, info_ptr); | |
| /*unpaletize*/ | |
| if (png_get_color_type(png_ptr, info_ptr)==PNG_COLOR_TYPE_PALETTE) { | |
| png_set_expand(png_ptr); | |
| png_read_update_info(png_ptr, info_ptr); | |
| } | |
| num_trans = 0; | |
| png_get_tRNS(png_ptr, info_ptr, &trans_alpha, &num_trans, &trans_color); | |
| if (num_trans) { | |
| png_set_tRNS_to_alpha(png_ptr); | |
| png_read_update_info(png_ptr, info_ptr); | |
| } | |
| *width = (u32) png_get_image_width(png_ptr, info_ptr); | |
| *height = (u32) png_get_image_height(png_ptr, info_ptr); | |
| switch (png_get_color_type(png_ptr, info_ptr)) { | |
| case PNG_COLOR_TYPE_GRAY: | |
| *pixel_format = GF_PIXEL_GREYSCALE; | |
| break; | |
| case PNG_COLOR_TYPE_GRAY_ALPHA: | |
| *pixel_format = GF_PIXEL_GREYALPHA; | |
| break; | |
| case PNG_COLOR_TYPE_RGB: | |
| *pixel_format = GF_PIXEL_RGB; | |
| break; | |
| case PNG_COLOR_TYPE_RGB_ALPHA: | |
| *pixel_format = GF_PIXEL_RGBA; | |
| break; | |
| default: | |
| png_destroy_info_struct(png_ptr,(png_infopp) & info_ptr); | |
| png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); | |
| return GF_NOT_SUPPORTED; | |
| } | |
| out_size = (u32) (png_get_rowbytes(png_ptr, info_ptr) * png_get_image_height(png_ptr, info_ptr)); | |
| /*new cfg, reset*/ | |
| if (*dst_size != out_size) { | |
| *dst_size = out_size; | |
| png_destroy_info_struct(png_ptr,(png_infopp) & info_ptr); | |
| png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); | |
| return GF_BUFFER_TOO_SMALL; | |
| } | |
| *dst_size = out_size; | |
| if (!dst) return GF_BAD_PARAM; | |
| /*read*/ | |
| stride = (u32) png_get_rowbytes(png_ptr, info_ptr); | |
| udta.rows = (png_bytepp) gf_malloc(sizeof(png_bytep) * png_get_image_height(png_ptr, info_ptr)); | |
| for (i=0; i<png_get_image_height(png_ptr, info_ptr); i++) { | |
| udta.rows[i] = (png_bytep)dst + i*stride; | |
| } | |
| png_read_image(png_ptr, udta.rows); | |
| png_read_end(png_ptr, NULL); | |
| gf_free(udta.rows); | |
| png_destroy_info_struct(png_ptr,(png_infopp) & info_ptr); | |
| png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL); | |
| return GF_OK; | |
| } | |
| void gf_png_write(png_structp png, png_bytep data, png_size_t size) | |
| { | |
| GFpng *p = (GFpng *)png_get_io_ptr(png); | |
| memcpy(p->buffer+p->pos, data, sizeof(char)*size); | |
| p->pos += (u32) size; | |
| } | |
| void gf_png_flush(png_structp png) | |
| { | |
| } | |
| /* write a png file */ | |
| GF_EXPORT | |
| GF_Err gf_img_png_enc(u8 *data, u32 width, u32 height, s32 stride, u32 pixel_format, u8 *dst, u32 *dst_size) | |
| { | |
| GFpng udta; | |
| png_color_8 sig_bit; | |
| png_int_32 k; | |
| png_bytep *row_pointers; | |
| png_structp png_ptr; | |
| png_infop info_ptr; | |
| u32 type, nb_comp; | |
| switch (pixel_format) { | |
| case GF_PIXEL_GREYSCALE: | |
| nb_comp = 1; | |
| type = PNG_COLOR_TYPE_GRAY; | |
| break; | |
| case GF_PIXEL_GREYALPHA: | |
| nb_comp = 1; | |
| type = PNG_COLOR_TYPE_GRAY_ALPHA; | |
| break; | |
| case GF_PIXEL_RGB: | |
| case GF_PIXEL_BGR: | |
| case GF_PIXEL_RGBX: | |
| case GF_PIXEL_BGRX: | |
| nb_comp = 3; | |
| type = PNG_COLOR_TYPE_RGB; | |
| break; | |
| case GF_PIXEL_RGBA: | |
| case GF_PIXEL_ARGB: | |
| nb_comp = 4; | |
| type = PNG_COLOR_TYPE_RGB_ALPHA; | |
| break; | |
| default: | |
| return GF_NOT_SUPPORTED; | |
| } | |
| if (*dst_size < width*height*nb_comp) return GF_BUFFER_TOO_SMALL; | |
| png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); | |
| // png_voidp user_error_ptr, user_error_fn, user_warning_fn); | |
| if (png_ptr == NULL) return GF_IO_ERR; | |
| #ifdef GPAC_ENABLE_COVERAGE | |
| if (gf_sys_is_cov_mode()) { | |
| gf_png_flush(NULL); | |
| } | |
| #endif | |
| /* Allocate/initialize the image information data. REQUIRED */ | |
| info_ptr = png_create_info_struct(png_ptr); | |
| if (info_ptr == NULL) { | |
| png_destroy_write_struct(&png_ptr, NULL); | |
| return GF_IO_ERR; | |
| } | |
| /* Set error handling. REQUIRED if you aren't supplying your own | |
| * error handling functions in the png_create_write_struct() call. | |
| */ | |
| if (setjmp(png_jmpbuf(png_ptr))) { | |
| png_destroy_write_struct(&png_ptr, &info_ptr); | |
| return GF_NON_COMPLIANT_BITSTREAM; | |
| } | |
| udta.buffer = dst; | |
| udta.pos = 0; | |
| png_set_write_fn(png_ptr, &udta, gf_png_write, gf_png_flush); | |
| png_set_IHDR(png_ptr, info_ptr, width, height, 8, type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); | |
| /* otherwise, if we are dealing with a color image then */ | |
| sig_bit.red = 8; | |
| sig_bit.green = 8; | |
| sig_bit.blue = 8; | |
| sig_bit.gray = 8; | |
| sig_bit.alpha = 8; | |
| png_set_sBIT(png_ptr, info_ptr, &sig_bit); | |
| #if 0 | |
| /* Optionally write comments into the image */ | |
| text_ptr[0].key = "Title"; | |
| text_ptr[0].text = "Mona Lisa"; | |
| text_ptr[0].compression = PNG_TEXT_COMPRESSION_NONE; | |
| text_ptr[1].key = "Author"; | |
| text_ptr[1].text = "Leonardo DaVinci"; | |
| text_ptr[1].compression = PNG_TEXT_COMPRESSION_NONE; | |
| text_ptr[2].key = "Description"; | |
| text_ptr[2].text = "<long text>"; | |
| text_ptr[2].compression = PNG_TEXT_COMPRESSION_zTXt; | |
| #ifdef PNG_iTXt_SUPPORTED | |
| text_ptr[0].lang = NULL; | |
| text_ptr[1].lang = NULL; | |
| text_ptr[2].lang = NULL; | |
| #endif | |
| png_set_text(png_ptr, info_ptr, text_ptr, 3); | |
| #endif | |
| png_write_info(png_ptr, info_ptr); | |
| /* Shift the pixels up to a legal bit depth and fill in | |
| * as appropriate to correctly scale the image. | |
| */ | |
| png_set_shift(png_ptr, &sig_bit); | |
| /* pack pixels into bytes */ | |
| png_set_packing(png_ptr); | |
| if (pixel_format==GF_PIXEL_ARGB) { | |
| // png_set_swap_alpha(png_ptr); | |
| png_set_bgr(png_ptr); | |
| } | |
| switch (pixel_format) { | |
| case GF_PIXEL_RGBX: | |
| png_set_filler(png_ptr, 0, PNG_FILLER_AFTER); | |
| png_set_bgr(png_ptr); | |
| break; | |
| case GF_PIXEL_BGRX: | |
| png_set_filler(png_ptr, 0, PNG_FILLER_AFTER); | |
| break; | |
| case GF_PIXEL_BGR: | |
| png_set_bgr(png_ptr); | |
| break; | |
| } | |
| row_pointers = gf_malloc(sizeof(png_bytep)*height); | |
| for (k = 0; k < (s32)height; k++) | |
| row_pointers[k] = (png_bytep) data + k*stride; | |
| png_write_image(png_ptr, row_pointers); | |
| png_write_end(png_ptr, info_ptr); | |
| gf_free(row_pointers); | |
| /* clean up after the write, and free any memory allocated */ | |
| png_destroy_write_struct(&png_ptr, &info_ptr); | |
| *dst_size = udta.pos; | |
| return GF_OK; | |
| } | |
| #else | |
| GF_EXPORT | |
| GF_Err gf_img_png_dec(u8 *png, u32 png_size, u32 *width, u32 *height, u32 *pixel_format, u8 *dst, u32 *dst_size) | |
| { | |
| return GF_NOT_SUPPORTED; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_img_png_enc(u8 *data, u32 width, u32 height, s32 stride, u32 pixel_format, u8 *dst, u32 *dst_size) | |
| { | |
| return GF_NOT_SUPPORTED; | |
| } | |
| #endif /*GPAC_HAS_PNG*/ |