diff --git a/src/bin/jp2/opj_dump.c b/src/bin/jp2/opj_dump.c index 1ee7973e..b76d099f 100644 --- a/src/bin/jp2/opj_dump.c +++ b/src/bin/jp2/opj_dump.c @@ -493,6 +493,7 @@ int main(int argc, char *argv[]) opj_stream_t *l_stream = NULL; /* Stream */ opj_codestream_info_v2_t* cstr_info = NULL; opj_codestream_index_t* cstr_index = NULL; + opj_jp2_metadata_t *jp2_metadata = NULL; unsigned int num_images, imageno; img_fol_t img_fol; @@ -640,6 +641,11 @@ int main(int argc, char *argv[]) cstr_info = opj_get_cstr_info(l_codec); + /* Dump associated data if there is any */ + jp2_metadata = opj_get_jp2_metadata(l_codec); + opj_dump_associated_data(jp2_metadata, stdout); + opj_destroy_jp2_metadata(&jp2_metadata); + cstr_index = opj_get_cstr_index(l_codec); /* close the byte stream */ diff --git a/src/lib/openjp2/j2k.c b/src/lib/openjp2/j2k.c index 923bd891..032eee13 100644 --- a/src/lib/openjp2/j2k.c +++ b/src/lib/openjp2/j2k.c @@ -11480,6 +11480,17 @@ opj_codestream_info_v2_t* j2k_get_cstr_info(opj_j2k_t* p_j2k) return cstr_info; } +opj_jp2_metadata_t* j2k_get_metadata( opj_j2k_t* p_j2k ) +{ + opj_jp2_metadata_t* p_metadata = opj_malloc(sizeof(opj_jp2_metadata_t)); + + /* A J2K stream can not contain ASOC boxes */ + p_metadata->nbasoc = 0; + p_metadata->asoc_info = 00; + + return p_metadata; +} + opj_codestream_index_t* j2k_get_cstr_index(opj_j2k_t* p_j2k) { opj_codestream_index_t* l_cstr_index = (opj_codestream_index_t*) diff --git a/src/lib/openjp2/j2k.h b/src/lib/openjp2/j2k.h index e0b9688a..24afc85b 100644 --- a/src/lib/openjp2/j2k.h +++ b/src/lib/openjp2/j2k.h @@ -825,6 +825,15 @@ void j2k_dump_image_comp_header(opj_image_comp_t* comp, OPJ_BOOL dev_dump_flag, */ opj_codestream_info_v2_t* j2k_get_cstr_info(opj_j2k_t* p_j2k); +/** + * Get the metadata from a JPEG2000 codec. + * + *@param p_j2k the component image header to dump. + * + *@return The metadata extract from the jpg2000 codec + */ + opj_jp2_metadata_t* j2k_get_metadata( opj_j2k_t* p_j2k ); + /** * Get the codestream index from a JPEG2000 codec. * diff --git a/src/lib/openjp2/jp2.c b/src/lib/openjp2/jp2.c index ec202272..df085d1a 100644 --- a/src/lib/openjp2/jp2.c +++ b/src/lib/openjp2/jp2.c @@ -111,6 +111,11 @@ static OPJ_BOOL opj_jp2_read_cdef(opj_jp2_t * jp2, static void opj_jp2_apply_cdef(opj_image_t *image, opj_jp2_color_t *color, opj_event_mgr_t *); +/** + * Destroy list of ASOC entities + */ +static void opj_jp2_asoc_destroy(opj_jp2_asoc_t *p_asoc, OPJ_UINT32 num); + /** * Writes the Channel Definition box. * @@ -161,6 +166,22 @@ static OPJ_BOOL opj_jp2_read_ftyp(opj_jp2_t *jp2, OPJ_UINT32 p_header_size, opj_event_mgr_t * p_manager); +/** + * Reads a ASOC box - Associated data box. + * Also reads contained label (LBL) and XML boxes + * + * @param p_header_data the data contained in the ASOC box. + * @param jp2 the jpeg2000 file codec. + * @param p_header_size the size of the data contained in the ASOC box. + * @param p_manager the user event manager. + * + * @return true if the ASOC box is valid. + */ +static OPJ_BOOL opj_jp2_read_asoc(opj_jp2_t *jp2, + OPJ_BYTE * p_header_data, + OPJ_UINT32 p_header_size, + opj_event_mgr_t * p_manager); + static OPJ_BOOL opj_jp2_skip_jp2c(opj_jp2_t *jp2, opj_stream_private_t *stream, opj_event_mgr_t * p_manager); @@ -425,7 +446,8 @@ static const opj_jp2_header_handler_t * opj_jp2_find_handler(OPJ_UINT32 p_id); static const opj_jp2_header_handler_t jp2_header [] = { {JP2_JP, opj_jp2_read_jp}, {JP2_FTYP, opj_jp2_read_ftyp}, - {JP2_JP2H, opj_jp2_read_jp2h} + {JP2_JP2H, opj_jp2_read_jp2h}, + {JP2_ASOC, opj_jp2_read_asoc} }; static const opj_jp2_header_handler_t jp2_img_header [] = { @@ -966,7 +988,7 @@ static OPJ_BOOL opj_jp2_check_color(opj_image_t *image, opj_jp2_color_t *color, for (i = 0; i < nr_channels; i++) { OPJ_BYTE mtyp = cmap[i].mtyp; OPJ_BYTE pcol = cmap[i].pcol; - /* See ISO 15444-1 Table I.14 – MTYPi field values */ + /* See ISO 15444-1 Table I.14 – MTYPi field values */ if (mtyp != 0 && mtyp != 1) { opj_event_msg(p_manager, EVT_ERROR, "Invalid value for cmap[%d].mtyp = %d.\n", i, @@ -2646,6 +2668,129 @@ static OPJ_BOOL opj_jp2_read_ftyp(opj_jp2_t *jp2, return OPJ_TRUE; } +static OPJ_BOOL opj_jp2_read_asoc(opj_jp2_t *jp2, + OPJ_BYTE * p_header_data, + OPJ_UINT32 p_header_size, + opj_event_mgr_t * p_manager) +{ + OPJ_UINT32 label_tag; + OPJ_UINT32 asoc_tag; + OPJ_UINT32 asoc_size; + opj_jp2_asoc_t *asoc; + + /* preconditions */ + assert(jp2 != 00); + assert(p_header_data != 00); + assert(p_manager != 00); + + if (p_header_size < 8) { + opj_event_msg(p_manager, EVT_ERROR, + "Cannot handle ASOC box of less than 8 bytes\n"); + return OPJ_FALSE; + } + + opj_read_bytes(p_header_data, &asoc_size, 4); + p_header_data += 4; + p_header_size -= 4; + + if (p_header_size < asoc_size) { + opj_event_msg(p_manager, EVT_ERROR, + "ASOC super box is smaller than containing sub box\n"); + return OPJ_FALSE; + } + + opj_read_bytes(p_header_data, &label_tag, 4); + p_header_data += 4; + p_header_size -= 4; + asoc_size -= 4; + + if (label_tag != JP2_LBL) { + /* TODO: Verify that ASOC must have a following label ? */ + opj_event_msg(p_manager, EVT_WARNING, + "ASOC data does not have a label (LBL)\n"); + return OPJ_TRUE; // No error if we could not parse + } + + if (jp2->numasoc == 0) { + /* Create a first asoc */ + jp2->numasoc = 1; + jp2->asoc = opj_malloc(sizeof(opj_jp2_asoc_t)); + } else { + /* Add an asoc to existing ones */ + (jp2->numasoc)++; + jp2->asoc = opj_realloc(jp2->asoc, jp2->numasoc * sizeof(opj_jp2_asoc_t)); + } + + asoc = &(jp2->asoc[jp2->numasoc - 1]); + + /* TODO: This is not correct if a parent asoc contains multiple child asocs! */ + asoc->level = jp2->numasoc - 1; + asoc->label_length = asoc_size + 1; + asoc->label = opj_malloc(asoc->label_length); + memcpy(asoc->label, p_header_data, asoc_size); + asoc->label[asoc->label_length - 1] = '\0'; /* NULL terminated label string */ + asoc->xml_buf = 00; + asoc->xml_len = 0; + + p_header_data += asoc_size; + p_header_size -= asoc_size; + + if (p_header_size < 4) { + opj_event_msg(p_manager, EVT_ERROR, + "Cannot handle ASOC sub box of less than 4 bytes\n"); + return OPJ_FALSE; + } + + opj_read_bytes(p_header_data, &asoc_tag, 4); + p_header_data += 4; + p_header_size -= 4; + + switch (asoc_tag) { + case JP2_ASOC: { + /* Start of nested ASOC tags. Parse this level. */ + if (!opj_jp2_read_asoc(jp2, p_header_data, p_header_size, p_manager)) { + return OPJ_FALSE; + } + } + break; + + case JP2_XML: { + asoc->xml_len = p_header_size + 1; + asoc->xml_buf = opj_malloc(asoc->xml_len); + memcpy(asoc->xml_buf, p_header_data, p_header_size); + asoc->xml_buf[asoc->xml_len - 1] = '\0'; + } + break; + + default: { + /* Copy the unknown data for external handling. + NOTE: This is not tested, but does the same as if an XML tag was found.*/ + asoc->xml_len = p_header_size + 1; + asoc->xml_buf = opj_malloc(asoc->xml_len); + memcpy(asoc->xml_buf, p_header_data, p_header_size); + asoc->xml_buf[asoc->xml_len - 1] = '\0'; + } + } + + return OPJ_TRUE; +} + +static void opj_jp2_asoc_destroy(opj_jp2_asoc_t *p_asoc, OPJ_UINT32 num) +{ + OPJ_UINT32 i; + opj_jp2_asoc_t *asoc; + for (i = 0; i < num; i++) { + asoc = &(p_asoc[i]); + opj_free(asoc->label); + asoc->label = 00; + asoc->label_length = 0; + + opj_free(asoc->xml_buf); + asoc->xml_buf = 00; + asoc->xml_len = 0; + } +} + static OPJ_BOOL opj_jp2_skip_jp2c(opj_jp2_t *jp2, opj_stream_private_t *stream, opj_event_mgr_t * p_manager) @@ -3078,6 +3223,12 @@ void opj_jp2_destroy(opj_jp2_t *jp2) jp2->m_procedure_list = 00; } + if (jp2->numasoc) { + opj_jp2_asoc_destroy(jp2->asoc, jp2->numasoc); + jp2->asoc = 00; + jp2->numasoc = 0; + } + opj_free(jp2); } } @@ -3213,6 +3364,11 @@ opj_jp2_t* opj_jp2_create(OPJ_BOOL p_is_decoder) opj_jp2_destroy(jp2); return 00; } + + /* Association data */ + jp2->asoc = 00; + jp2->numasoc = 0; + } return jp2; @@ -3235,7 +3391,45 @@ opj_codestream_index_t* jp2_get_cstr_index(opj_jp2_t* p_jp2) opj_codestream_info_v2_t* jp2_get_cstr_info(opj_jp2_t* p_jp2) { - return j2k_get_cstr_info(p_jp2->j2k); + opj_codestream_info_v2_t* p_info = j2k_get_cstr_info(p_jp2->j2k); + return p_info; +} + +opj_jp2_metadata_t* jp2_get_metadata( opj_jp2_t* p_jp2 ) +{ + opj_jp2_metadata_t* p_metadata = opj_malloc(sizeof(opj_jp2_metadata_t)); + jp2_copy_asoc_data(p_jp2, p_metadata); + return p_metadata; +} + +OPJ_BOOL jp2_copy_asoc_data( opj_jp2_t* p_jp2, opj_jp2_metadata_t* p_jp2_metadata ) +{ + OPJ_UINT32 i; + opj_jp2_asoc_t *asoc, *to_asoc; + + p_jp2_metadata->nbasoc = p_jp2->numasoc; + p_jp2_metadata->asoc_info = opj_malloc(p_jp2_metadata->nbasoc * sizeof(opj_jp2_asoc_t)); + for (i=0; inbasoc; i++) { + asoc = &(p_jp2->asoc[i]); + to_asoc = &(p_jp2_metadata->asoc_info[i]); + to_asoc->level = asoc->level; + to_asoc->label_length = asoc->label_length; + to_asoc->xml_len = asoc->xml_len; + if (asoc->label_length && asoc->label) { + to_asoc->label = opj_malloc(to_asoc->label_length); + memcpy(to_asoc->label, asoc->label, to_asoc->label_length); + } else { + to_asoc->label = 00; + } + if (asoc->xml_len && asoc->xml_buf) { + to_asoc->xml_buf = opj_malloc( to_asoc->xml_len); + memcpy(to_asoc->xml_buf, asoc->xml_buf, to_asoc->xml_len); + } else { + to_asoc->xml_buf = 00; + } + } + + return OPJ_TRUE; } OPJ_BOOL opj_jp2_set_decoded_resolution_factor(opj_jp2_t *p_jp2, diff --git a/src/lib/openjp2/jp2.h b/src/lib/openjp2/jp2.h index 173f2511..091d671d 100644 --- a/src/lib/openjp2/jp2.h +++ b/src/lib/openjp2/jp2.h @@ -59,6 +59,9 @@ #define JP2_DTBL 0x6474626c /**< Data Reference box */ #define JP2_BPCC 0x62706363 /**< Bits per component box */ #define JP2_JP2 0x6a703220 /**< File type fields */ +#define JP2_ASOC 0x61736f63 /**< Associated data */ +#define JP2_LBL 0x6c626c20 /**< Association label */ +#define JP2_XML 0x786d6c20 /**< XML data */ /* For the future */ /* #define JP2_RES 0x72657320 */ /**< Resolution box (super-box) */ @@ -186,6 +189,9 @@ typedef struct opj_jp2 { opj_jp2_color_t color; + opj_jp2_asoc_t *asoc; + OPJ_UINT32 numasoc; + OPJ_BOOL ignore_pclr_cmap_cdef; OPJ_BYTE has_jp2h; OPJ_BYTE has_ihdr; @@ -503,6 +509,20 @@ void jp2_dump(opj_jp2_t* p_jp2, OPJ_INT32 flag, FILE* out_stream); */ opj_codestream_info_v2_t* jp2_get_cstr_info(opj_jp2_t* p_jp2); +/** + * Get the metadata from a JPEG2000 codec. + * + *@param p_jp2 jp2 codec. + * + *@return the metadata extract from the jpg2000 codec + */ + opj_jp2_metadata_t* jp2_get_metadata( opj_jp2_t* p_jp2 ); + +/** + * Copy associated data + */ +OPJ_BOOL jp2_copy_asoc_data(opj_jp2_t* p_jp2, opj_jp2_metadata_t* p_jp2_metadata); + /** * Get the codestream index from a JPEG2000 codec. * diff --git a/src/lib/openjp2/openjpeg.c b/src/lib/openjp2/openjpeg.c index 29d3ee52..d333b1ba 100644 --- a/src/lib/openjp2/openjpeg.c +++ b/src/lib/openjp2/openjpeg.c @@ -37,6 +37,7 @@ #include "opj_includes.h" +static void opj_asoc_destroy(opj_jp2_asoc_t *p_asoc, OPJ_UINT32 num); /* ---------------------------------------------------------------------- */ /* Functions to set the message handlers */ @@ -197,6 +198,9 @@ opj_codec_t* OPJ_CALLCONV opj_create_decompress(OPJ_CODEC_FORMAT p_format) l_codec->opj_get_codec_index = (opj_codestream_index_t* (*)( void*)) j2k_get_cstr_index; + l_codec->opj_get_jp2_metadata = (opj_jp2_metadata_t* (*)( + void*)) j2k_get_metadata; + l_codec->m_codec_data.m_decompression.opj_decode = (OPJ_BOOL(*)(void *, struct opj_stream_private *, @@ -288,6 +292,9 @@ opj_codec_t* OPJ_CALLCONV opj_create_decompress(OPJ_CODEC_FORMAT p_format) l_codec->opj_get_codec_index = (opj_codestream_index_t* (*)( void*)) jp2_get_cstr_index; + l_codec->opj_get_jp2_metadata = (opj_jp2_metadata_t* (*)( + void*)) jp2_get_metadata; + l_codec->m_codec_data.m_decompression.opj_decode = (OPJ_BOOL(*)(void *, struct opj_stream_private *, @@ -1027,6 +1034,25 @@ void OPJ_CALLCONV opj_dump_codec(opj_codec_t *p_codec, return; } +void OPJ_CALLCONV opj_dump_associated_data( + opj_jp2_metadata_t* jp2_info, + FILE* output_stream) +{ + OPJ_UINT32 i; + if (jp2_info && jp2_info->asoc_info) { + fprintf(output_stream, "\n\nAssociated data: {\n"); + for (i = 0; i < jp2_info->nbasoc; i++) { + fprintf(output_stream, "\tlabel=%s, xml/data=", (char*) jp2_info->asoc_info[i].label); + if (jp2_info->asoc_info[i].xml_buf) { + fprintf(output_stream, "%s\n", (char*) jp2_info->asoc_info[i].xml_buf); + } else { + fprintf(output_stream, "NULL\n"); + } + } + fprintf(output_stream, "}\n"); + } +} + opj_codestream_info_v2_t* OPJ_CALLCONV opj_get_cstr_info(opj_codec_t *p_codec) { if (p_codec) { @@ -1038,6 +1064,22 @@ opj_codestream_info_v2_t* OPJ_CALLCONV opj_get_cstr_info(opj_codec_t *p_codec) return NULL; } +void opj_asoc_destroy(opj_jp2_asoc_t *p_asoc, OPJ_UINT32 num) +{ + OPJ_UINT32 i; + opj_jp2_asoc_t *asoc; + for (i = 0; i < num; i++) { + asoc = &(p_asoc[i]); + opj_free(asoc->label); + asoc->label = 00; + asoc->label_length = 0; + + opj_free(asoc->xml_buf); + asoc->xml_buf = 00; + asoc->xml_len = 0; + } +} + void OPJ_CALLCONV opj_destroy_cstr_info(opj_codestream_info_v2_t **cstr_info) { if (cstr_info) { @@ -1066,6 +1108,27 @@ opj_codestream_index_t * OPJ_CALLCONV opj_get_cstr_index(opj_codec_t *p_codec) return NULL; } +opj_jp2_metadata_t * OPJ_CALLCONV opj_get_jp2_metadata(opj_codec_t *p_codec) +{ + if (p_codec) { + opj_codec_private_t* l_codec = (opj_codec_private_t*) p_codec; + return l_codec->opj_get_jp2_metadata(l_codec->m_codec); + } + return NULL; +} + +OPJ_API void opj_destroy_jp2_metadata(opj_jp2_metadata_t **p_jp2_meta_data) +{ + if (*p_jp2_meta_data) { + if ((*p_jp2_meta_data)->nbasoc) { + opj_asoc_destroy((*p_jp2_meta_data)->asoc_info, (*p_jp2_meta_data)->nbasoc); + (*p_jp2_meta_data)->asoc_info = 00; + (*p_jp2_meta_data)->nbasoc = 0; + } + (*p_jp2_meta_data) = NULL; + } +} + void OPJ_CALLCONV opj_destroy_cstr_index(opj_codestream_index_t **p_cstr_index) { if (*p_cstr_index) { @@ -1124,7 +1187,6 @@ opj_stream_t* OPJ_CALLCONV opj_stream_create_file_stream( return l_stream; } - void* OPJ_CALLCONV opj_image_data_alloc(OPJ_SIZE_T size) { void* ret = opj_aligned_malloc(size); diff --git a/src/lib/openjp2/openjpeg.h b/src/lib/openjp2/openjpeg.h index ebe78444..3d808ba6 100644 --- a/src/lib/openjp2/openjpeg.h +++ b/src/lib/openjp2/openjpeg.h @@ -956,6 +956,21 @@ typedef struct opj_tile_v2_info { } opj_tile_info_v2_t; +/** +Collector for association box (ASOC data), defined by a level, label and optionally XML data. +E.g. georeferencing with GML uses this (http://docs.opengeospatial.org/is/08-085r4/08-085r4.html). +In this case the first asoc is labelled 'gml.data' and has no XML data. The second asoc is named +'gml.root-instance' and contains XML formatted geo-information. +*/ +typedef struct opj_jp2_asoc { + OPJ_UINT32 level; + OPJ_BYTE *label; + OPJ_UINT32 label_length; + OPJ_BYTE *xml_buf; + OPJ_UINT32 xml_len; + +} opj_jp2_asoc_t; + /** * Information structure about the codestream (FIXME should be expand and enhance) */ @@ -983,6 +998,7 @@ typedef struct opj_codestream_info_v2 { /** information regarding tiles inside image */ opj_tile_info_v2_t *tile_info; /* FIXME not used for the moment */ + } opj_codestream_info_v2_t; @@ -1068,11 +1084,15 @@ typedef struct opj_codestream_index { /** * Info structure of the JP2 file - * EXPERIMENTAL FOR THE MOMENT + * Includes associated data (ASOC boxes) if available in JP2 stream. See opj_dump for sample code. */ typedef struct opj_jp2_metadata { - /** */ - OPJ_INT32 not_used; + + /** Number of associated data boxes*/ + OPJ_UINT32 nbasoc; + + /** Associated data, e.g. GML geoinformation */ + opj_jp2_asoc_t *asoc_info; } opj_jp2_metadata_t; @@ -1697,6 +1717,18 @@ OPJ_API void OPJ_CALLCONV opj_dump_codec(opj_codec_t *p_codec, OPJ_API opj_codestream_info_v2_t* OPJ_CALLCONV opj_get_cstr_info( opj_codec_t *p_codec); +/** + * Dumps info in ASOC and nested LBL and XML boxes if any. + * For example GML geo data may be stored in these associated boxes. + * + * @param p_codec codestream information + * @param output_stream stream to dump info to + * + */ +OPJ_API void OPJ_CALLCONV opj_dump_associated_data( + opj_jp2_metadata_t* cstr_info, + FILE* output_stream); + /** * Get the codestream index from the codec * @@ -1713,16 +1745,21 @@ OPJ_API void OPJ_CALLCONV opj_destroy_cstr_index(opj_codestream_index_t /** - * Get the JP2 file information from the codec FIXME + * Get the JP2 metadata file information from the codec * * @param p_codec the jpeg2000 codec. * - * @return a pointer to a JP2 metadata structure. + * @return a pointer to a JP2 metadata structure or NULL if no metadata available. The metadata structure will contain associated data (ASOC tag) such as GML geoinfo-data if available. * */ OPJ_API opj_jp2_metadata_t* OPJ_CALLCONV opj_get_jp2_metadata( opj_codec_t *p_codec); +/** + * Destroy the JP2 metadata file information + */ +OPJ_API void opj_destroy_jp2_metadata(opj_jp2_metadata_t **p_jp2_meta_data); + /** * Get the JP2 file index from the codec FIXME * diff --git a/src/lib/openjp2/opj_codec.h b/src/lib/openjp2/opj_codec.h index 7cff6708..cb898bd1 100644 --- a/src/lib/openjp2/opj_codec.h +++ b/src/lib/openjp2/opj_codec.h @@ -168,6 +168,7 @@ typedef struct opj_codec_private { FILE* output_stream); opj_codestream_info_v2_t* (*opj_get_codec_info)(void* p_codec); opj_codestream_index_t* (*opj_get_codec_index)(void* p_codec); + opj_jp2_metadata_t* (*opj_get_jp2_metadata)(void* p_codec); /** Set number of threads */ OPJ_BOOL(*opj_set_threads)(void * p_codec, OPJ_UINT32 num_threads);