diff --git a/.gitignore b/.gitignore index a6f4eff73..1d8dda6fb 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ scripts/opjstyle* # Ignore directories made by `make`. /bin/ +/build/ \ No newline at end of file diff --git a/src/bin/common/format_defs.h b/src/bin/common/format_defs.h index 1985b54f7..2d39c0045 100644 --- a/src/bin/common/format_defs.h +++ b/src/bin/common/format_defs.h @@ -41,6 +41,7 @@ #define J2K_CFMT 0 #define JP2_CFMT 1 #define JPT_CFMT 2 +#define JPX_CFMT 3 #define PXM_DFMT 10 #define PGX_DFMT 11 diff --git a/src/bin/jp2/CMakeLists.txt b/src/bin/jp2/CMakeLists.txt index 26156bcbf..9cf10bb64 100644 --- a/src/bin/jp2/CMakeLists.txt +++ b/src/bin/jp2/CMakeLists.txt @@ -42,7 +42,7 @@ if(WIN32) endif() # Loop over all executables: -foreach(exe opj_decompress opj_compress opj_dump) +foreach(exe opj_decompress opj_compress opj_dump opj_merge) add_executable(${exe} ${exe}.c ${common_SRCS}) target_compile_options(${exe} PRIVATE ${OPENJP2_COMPILE_OPTIONS}) target_link_libraries(${exe} ${OPENJPEG_LIBRARY_NAME} diff --git a/src/bin/jp2/opj_merge.c b/src/bin/jp2/opj_merge.c new file mode 100644 index 000000000..7abb12d75 --- /dev/null +++ b/src/bin/jp2/opj_merge.c @@ -0,0 +1,198 @@ +#include +#include +#include +#include "openjpeg.h" +#include "opj_getopt.h" +#include "format_defs.h" +#ifdef _WIN32 +#define strcasecmp _stricmp +#endif + +/** + * Prints usage information for opj_merge. + */ +void merge_help_display(void); + +void merge_help_display(void) +{ + fprintf(stdout, + "\nThis is the opj_merge utility from the OpenJPEG project.\n" + "It provides limited support for creating a jpx file with\n" + "references to multiple local jp2 files.\n" + "It has been compiled against openjp2 library v%s.\n\n", opj_version()); + fprintf(stdout, + "At this time, this utility can only insert references into jpx files.\n" + "It does not include all jp2 codestreams. All jp2 files being\n" + "merged must have the same resolution and color format.\n\n"); + fprintf(stdout, "Parameters:\n"); + fprintf(stdout, "-----------\n"); + fprintf(stdout, "Required Parameters (except with -h):\n"); + fprintf(stdout, "-i \n"); + fprintf(stdout, " Input file, may be passed multiple times to\n"); + fprintf(stdout, " indicate all jp2 files that will be merged.\n"); + fprintf(stdout, "-o \n"); + fprintf(stdout, " Output file (accepted extensions are jpx).\n"); + fprintf(stdout, "-h\n"); + fprintf(stdout, " Display this help information.\n"); +} + +static int get_file_format(char *filename) +{ + unsigned int i; + static const char *extension[] = { + "pgx", "pnm", "pgm", "ppm", "pbm", "pam", "bmp", "tif", "tiff", "raw", "yuv", "rawl", "tga", "png", "j2k", "jp2", "j2c", "jpc", "jpx" + }; + static const int format[] = { + PGX_DFMT, PXM_DFMT, PXM_DFMT, PXM_DFMT, PXM_DFMT, PXM_DFMT, BMP_DFMT, TIF_DFMT, TIF_DFMT, RAW_DFMT, RAW_DFMT, RAWL_DFMT, TGA_DFMT, PNG_DFMT, J2K_CFMT, JP2_CFMT, J2K_CFMT, J2K_CFMT, JPX_CFMT + }; + char * ext = strrchr(filename, '.'); + if (ext == NULL) { + return -1; + } + ext++; + for (i = 0; i < sizeof(format) / sizeof(*format); i++) { + if (strcasecmp(ext, extension[i]) == 0) { + return format[i]; + } + } + return -1; +} + +/** + * Parse incoming command line arguments and store the + * result in args + * @param[in] argc Program argc + * @param[in] argv Program argv + * @param[out] outfile ptr to const char* which will get the output file. + * @param[inout] input_file_list array instance, should be length argc to ensure it's big enough. + * @param[out] infile_count Number of input files given. + */ +static void parse_cmdline(int argc, + char** argv, + char** outfile, + const char** input_file_list, + OPJ_UINT32* infile_count) +{ + const char optlist[] = "i:o:h"; + OPJ_UINT32 num_files = 0; + int c; + while ((c = opj_getopt(argc, argv, optlist)) != -1) { + switch (c) { + case 'i': // Input file + // Store the input file in the array + input_file_list[num_files++] = opj_optarg; + break; + case 'o': + *outfile = opj_optarg; + if (get_file_format(*outfile) != JPX_CFMT) { + fprintf(stderr, "Unknown output file %s [output must be *.jpx]\n", *outfile); + exit(1); + } + break; + case 'h': + merge_help_display(); + exit(0); + } + } + + *infile_count = num_files; + + if (num_files == 0) { + fprintf(stderr, "No input files given!\n"); + exit(1); + } + + if (*outfile == NULL) { + fprintf(stderr, "No output file given!\n"); + exit(1); + } + + // Null terminate the list of input files. + input_file_list[num_files] = NULL; +} + +int main(int argc, char **argv) { + OPJ_UINT64 i = 0; + /** Create jpx encoder */ + opj_codec_t* codec = opj_create_compress(OPJ_CODEC_JPX); + /* set encoding parameters to default values */ + opj_cparameters_t parameters; + /** Output file */ + opj_stream_t *outfile = NULL; + /** Creation status */ + OPJ_BOOL bSuccess = OPJ_FALSE; + OPJ_UINT32 file_count; + const char** input_files = calloc(argc, sizeof(const char*)); + char* output_file; + /** Program return code */ + int ret = 1; + if (!input_files) { + fprintf(stderr, "Failed to allocate memory for input file list\n"); + exit(1); + } + + parse_cmdline(argc, argv, &output_file, input_files, &file_count); + printf("Merging\n"); + for(i = 0; i < file_count; i++) { + printf(" - %s\n", input_files[i]); + } + printf("Into %s\n", output_file); + + if (!codec) { + fprintf(stderr, "Failed to initialize the jpx codec.\n"); + return ret; + } + + opj_set_default_encoder_parameters(¶meters); + + // Creating references to other jpx files doesn't require image data. + // so pass NULL for the image parameter and hope for the best. + if (! opj_setup_encoder(codec, ¶meters, OPJ_NO_IMAGE_DATA)) { + fprintf(stderr, "Failed to setup encoder: opj_setup_encoder\n"); + goto fin; + } + + // Use extra options to specify the list of files to be merged into the jpx file. + { + if (!opj_encoder_set_extra_options(codec, input_files)) { + fprintf(stderr, "Failed to set list of jp2 files to include: opj_encoder_set_extra_options\n"); + goto fin; + } + } + + // /* open a byte stream for writing */ + outfile = opj_stream_create_default_file_stream(output_file, OPJ_FALSE); + if (!outfile) { + fprintf(stderr, "Failed to allocate memory for the output file"); + goto fin; + } + + bSuccess = opj_start_compress(codec, OPJ_NO_IMAGE_DATA, outfile); + if (!bSuccess) { + fprintf(stderr, "Failed to create the jpx image: opj_start_compress\n"); + goto fin; + } + + bSuccess = bSuccess && opj_encode(codec, outfile); + if (!bSuccess) { + fprintf(stderr, "Failed to encode the image: opj_encode\n"); + } + + bSuccess = bSuccess && opj_end_compress(codec, outfile); + if (!bSuccess) { + fprintf(stderr, "Failed to encode the image: opj_end_compress\n"); + goto fin; + } + + ret = 0; + +fin: + if (codec) { + opj_destroy_codec(codec); + } + if (outfile) { + opj_stream_destroy(outfile); + } + free(input_files); + return ret; +} \ No newline at end of file diff --git a/src/lib/openjp2/CMakeLists.txt b/src/lib/openjp2/CMakeLists.txt index fd62335f5..421fc7600 100644 --- a/src/lib/openjp2/CMakeLists.txt +++ b/src/lib/openjp2/CMakeLists.txt @@ -28,6 +28,8 @@ set(OPENJPEG_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/j2k.h ${CMAKE_CURRENT_SOURCE_DIR}/jp2.c ${CMAKE_CURRENT_SOURCE_DIR}/jp2.h + ${CMAKE_CURRENT_SOURCE_DIR}/jpx.c + ${CMAKE_CURRENT_SOURCE_DIR}/jpx.h ${CMAKE_CURRENT_SOURCE_DIR}/mct.c ${CMAKE_CURRENT_SOURCE_DIR}/mct.h ${CMAKE_CURRENT_SOURCE_DIR}/mqc.c diff --git a/src/lib/openjp2/jp2.c b/src/lib/openjp2/jp2.c index 6015190e1..34052614d 100644 --- a/src/lib/openjp2/jp2.c +++ b/src/lib/openjp2/jp2.c @@ -14,6 +14,7 @@ * Copyright (c) 2010-2011, Kaori Hagihara * Copyright (c) 2008, 2011-2012, Centre National d'Etudes Spatiales (CNES), FR * Copyright (c) 2012, CS Systemes d'Information, France + * Copyright (c) 2024, Daniel Garcia Briseno, ADNET Systems Inc, NASA * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -133,19 +134,6 @@ static OPJ_BYTE * opj_jp2_write_cdef(opj_jp2_t *jp2, static OPJ_BYTE * opj_jp2_write_colr(opj_jp2_t *jp2, OPJ_UINT32 * p_nb_bytes_written); -/** - * Writes a FTYP box - File type box - * - * @param cio the stream to write data to. - * @param jp2 the jpeg2000 file codec. - * @param p_manager the user event manager. - * - * @return true if writing was successful. - */ -static OPJ_BOOL opj_jp2_write_ftyp(opj_jp2_t *jp2, - opj_stream_private_t *cio, - opj_event_mgr_t * p_manager); - /** * Reads a a FTYP box - File type box * @@ -180,19 +168,6 @@ static OPJ_BOOL opj_jp2_read_jp2h(opj_jp2_t *jp2, OPJ_UINT32 p_header_size, opj_event_mgr_t * p_manager); -/** - * Writes the Jpeg2000 file Header box - JP2 Header box (warning, this is a super box). - * - * @param jp2 the jpeg2000 file codec. - * @param stream the stream to write data to. - * @param p_manager user event manager. - * - * @return true if writing was successful. - */ -static OPJ_BOOL opj_jp2_write_jp2h(opj_jp2_t *jp2, - opj_stream_private_t *stream, - opj_event_mgr_t * p_manager); - /** * Writes the Jpeg2000 codestream Header box - JP2C Header box. This function must be called AFTER the coding has been done. * @@ -253,19 +228,6 @@ static OPJ_BOOL opj_jp2_read_jp(opj_jp2_t *jp2, OPJ_UINT32 p_header_size, opj_event_mgr_t * p_manager); -/** - * Writes a jpeg2000 file signature box. - * - * @param cio the stream to write data to. - * @param jp2 the jpeg2000 file codec. - * @param p_manager the user event manager. - * - * @return true if writing was successful. - */ -static OPJ_BOOL opj_jp2_write_jp(opj_jp2_t *jp2, - opj_stream_private_t *cio, - opj_event_mgr_t * p_manager); - /** Apply collected palette data @param image Image. @@ -356,20 +318,6 @@ static OPJ_BOOL opj_jp2_read_header_procedure(opj_jp2_t *jp2, opj_stream_private_t *stream, opj_event_mgr_t * p_manager); -/** - * Executes the given procedures on the given codec. - * - * @param p_procedure_list the list of procedures to execute - * @param jp2 the jpeg2000 file codec to execute the procedures on. - * @param stream the stream to execute the procedures on. - * @param p_manager the user manager. - * - * @return true if all the procedures were successfully executed. - */ -static OPJ_BOOL opj_jp2_exec(opj_jp2_t * jp2, - opj_procedure_list_t * p_procedure_list, - opj_stream_private_t *stream, - opj_event_mgr_t * p_manager); /** * Reads a box header. The box is the way data is packed inside a jpeg2000 file structure. @@ -1647,10 +1595,10 @@ OPJ_BOOL opj_jp2_decode(opj_jp2_t *jp2, return opj_jp2_apply_color_postprocessing(jp2, p_image, p_manager); } -static OPJ_BOOL opj_jp2_write_jp2h(opj_jp2_t *jp2, - opj_stream_private_t *stream, - opj_event_mgr_t * p_manager - ) +OPJ_BOOL opj_jp2_write_jp2h(opj_jp2_t *jp2, + opj_stream_private_t *stream, + opj_event_mgr_t * p_manager + ) { opj_jp2_img_header_writer_handler_t l_writers [4]; opj_jp2_img_header_writer_handler_t * l_current_writer; @@ -1754,9 +1702,9 @@ static OPJ_BOOL opj_jp2_write_jp2h(opj_jp2_t *jp2, return l_result; } -static OPJ_BOOL opj_jp2_write_ftyp(opj_jp2_t *jp2, - opj_stream_private_t *cio, - opj_event_mgr_t * p_manager) +OPJ_BOOL opj_jp2_write_ftyp(opj_jp2_t *jp2, + opj_stream_private_t *cio, + opj_event_mgr_t * p_manager) { OPJ_UINT32 i; OPJ_UINT32 l_ftyp_size; @@ -1792,6 +1740,7 @@ static OPJ_BOOL opj_jp2_write_ftyp(opj_jp2_t *jp2, for (i = 0; i < jp2->numcl; i++) { opj_write_bytes(l_current_data_ptr, jp2->cl[i], 4); /* CL */ + l_current_data_ptr += 4; } l_result = (opj_stream_write_data(cio, l_ftyp_data, l_ftyp_size, @@ -1844,9 +1793,9 @@ static OPJ_BOOL opj_jp2_write_jp2c(opj_jp2_t *jp2, return OPJ_TRUE; } -static OPJ_BOOL opj_jp2_write_jp(opj_jp2_t *jp2, - opj_stream_private_t *cio, - opj_event_mgr_t * p_manager) +OPJ_BOOL opj_jp2_write_jp(opj_jp2_t *jp2, + opj_stream_private_t *cio, + opj_event_mgr_t * p_manager) { /* 12 bytes will be read */ OPJ_BYTE l_signature_data [12]; @@ -2414,11 +2363,11 @@ static OPJ_BOOL opj_jp2_read_header_procedure(opj_jp2_t *jp2, * * @return true if all the procedures were successfully executed. */ -static OPJ_BOOL opj_jp2_exec(opj_jp2_t * jp2, - opj_procedure_list_t * p_procedure_list, - opj_stream_private_t *stream, - opj_event_mgr_t * p_manager - ) +OPJ_BOOL opj_jp2_exec(opj_jp2_t * jp2, + opj_procedure_list_t * p_procedure_list, + opj_stream_private_t *stream, + opj_event_mgr_t * p_manager + ) { OPJ_BOOL(** l_procedure)(opj_jp2_t * jp2, opj_stream_private_t *, diff --git a/src/lib/openjp2/jp2.h b/src/lib/openjp2/jp2.h index 173f25119..bf14a1e13 100644 --- a/src/lib/openjp2/jp2.h +++ b/src/lib/openjp2/jp2.h @@ -10,6 +10,7 @@ * Copyright (c) 2005, Herve Drolon, FreeImage Team * Copyright (c) 2008, 2011-2012, Centre National d'Etudes Spatiales (CNES), FR * Copyright (c) 2012, CS Systemes d'Information, France + * Copyright (c) 2024, Daniel Garcia Briseno, ADNET Systems Inc, NASA * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -59,11 +60,11 @@ #define JP2_DTBL 0x6474626c /**< Data Reference box */ #define JP2_BPCC 0x62706363 /**< Bits per component box */ #define JP2_JP2 0x6a703220 /**< File type fields */ +#define JP2_XML 0x786d6c20 /**< XML box */ /* For the future */ /* #define JP2_RES 0x72657320 */ /**< Resolution box (super-box) */ /* #define JP2_JP2I 0x6a703269 */ /**< Intellectual property box */ -/* #define JP2_XML 0x786d6c20 */ /**< XML box */ /* #define JP2_UUID 0x75756994 */ /**< UUID box */ /* #define JP2_UINF 0x75696e66 */ /**< UUID info box (super-box) */ /* #define JP2_ULST 0x756c7374 */ /**< UUID list box */ @@ -512,6 +513,60 @@ opj_codestream_info_v2_t* jp2_get_cstr_info(opj_jp2_t* p_jp2); */ opj_codestream_index_t* jp2_get_cstr_index(opj_jp2_t* p_jp2); +/** + * Writes a jpeg2000 file signature box. + * + * @param cio the stream to write data to. + * @param jp2 the jpeg2000 file codec. + * @param p_manager the user event manager. + * + * @return true if writing was successful. + */ +OPJ_BOOL opj_jp2_write_jp(opj_jp2_t *jp2, + opj_stream_private_t *cio, + opj_event_mgr_t * p_manager); + +/** + * Writes a FTYP box - File type box + * + * @param cio the stream to write data to. + * @param jp2 the jpeg2000 file codec. + * @param p_manager the user event manager. + * + * @return true if writing was successful. + */ +OPJ_BOOL opj_jp2_write_ftyp(opj_jp2_t *jp2, + opj_stream_private_t *cio, + opj_event_mgr_t * p_manager); + + +/** + * Writes the Jpeg2000 file Header box - JP2 Header box (warning, this is a super box). + * + * @param jp2 the jpeg2000 file codec. + * @param stream the stream to write data to. + * @param p_manager user event manager. + * + * @return true if writing was successful. + */ +OPJ_BOOL opj_jp2_write_jp2h(opj_jp2_t *jp2, + opj_stream_private_t *stream, + opj_event_mgr_t * p_manager); + +/** + * Executes the given procedures on the given codec. + * + * @param p_procedure_list the list of procedures to execute + * @param jp2 the jpeg2000 file codec to execute the procedures on. + * @param stream the stream to execute the procedures on. + * @param p_manager the user manager. + * + * @return true if all the procedures were successfully executed. + */ +OPJ_BOOL opj_jp2_exec(opj_jp2_t * jp2, + opj_procedure_list_t * p_procedure_list, + opj_stream_private_t *stream, + opj_event_mgr_t * p_manager); /*@}*/ diff --git a/src/lib/openjp2/jpx.c b/src/lib/openjp2/jpx.c new file mode 100644 index 000000000..46d70c5a1 --- /dev/null +++ b/src/lib/openjp2/jpx.c @@ -0,0 +1,989 @@ +/* + * The copyright in this software is being made available under the 2-clauses + * BSD License, included below. This software may be subject to other third + * party and contributor rights, including patent rights, and no such rights + * are granted under this license. + * + * Copyright (c) 2024, Daniel Garcia Briseno, ADNET Systems Inc, NASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS `AS IS' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include "opj_includes.h" +#ifdef WIN32 +#include +#endif + +/** + * Container for a box. + */ +typedef struct box { + OPJ_UINT32 length; + OPJ_UINT32 type; + OPJ_BYTE* contents; +} box_t; + +/** + * Writes out a fragment table. + * + * @param jp2file Path to jp2 file being embedded + * @param cio the stream to write data to. + * @param jpx the jpx file codec. + * @param p_manager user event manager. + * + * @return true if writing was successful. +*/ +static OPJ_BOOL opj_jpx_write_ftbl(const char* jp2file, + opj_jpx_t *jpx, + opj_stream_private_t *cio, + opj_event_mgr_t * p_manager); + +/** + * Returns the absolute path for the given file + * The buffer returned must be free'd with opj_free + * @returns NULL on failure, else the absolute path + */ +static char* opj_jpx_get_absolute_path(const char* relative_path); + +/** + * Writes out an association table. + * + * @param jp2file Path to jp2 file being embedded + * @param cio the stream to write data to. + * @param jpx the jpx file codec. + * @param p_manager user event manager. + * + * @return true if writing was successful. +*/ +static OPJ_BOOL opj_jpx_write_asoc(const char* jp2file, + opj_jpx_t *jpx, + opj_stream_private_t *cio, + opj_event_mgr_t * p_manager); + +/** + * Writes out the data reference table. + * + * @param jp2file Path to jp2 file being embedded + * @param cio the stream to write data to. + * @param jpx the jpx file codec. + * @param p_manager user event manager. + * + * @return true if writing was successful. + */ +static OPJ_BOOL opj_jpx_write_dtbl(opj_jpx_t *jpx, + opj_stream_private_t *cio, + opj_event_mgr_t * p_manager); + +/** + * Computes the size of the Data Reference box based on the jpx encoder parameters. + */ +static OPJ_UINT32 opj_jpx_compute_dtbl_size(opj_jpx_t *jpx); + +/** + * Computes the size of the url box to hold the given file path. + * @returns UINT32_MAX on failure, else the url box size + */ +static OPJ_UINT32 opj_jpx_compute_urlbox_size(const char* filepath); + +/** + * Reads a box from the given stream. + * Assumes that the stream is currently pointing to a box header. + * The resulting box must be destroyed with opj_jpx_destroy_box. + * @param[in] l_stream stream to read from + * @param[in] p_manager user event manager + * @param[out] box Box struct to store box data. + */ +static OPJ_BOOL opj_jpx_read_box(opj_stream_private_t *l_stream, + opj_event_mgr_t * p_manager, + box_t* box); + +/** Frees memory allocated for the given box */ +static void opj_jpx_destroy_box(box_t box); + +/** Initializes options for the jp2 encoder. */ +static OPJ_BOOL opj_jpx_init_jp2_options(opj_jpx_t* jpx, + opj_event_mgr_t * p_manager); + +/** + * Writes data via a cursor. + * Each call, cursor will be moved forward by the number of bytes written. + */ +static void opj_jpx_cursor_write(OPJ_BYTE** p_cursor, + OPJ_UINT32 p_value, + OPJ_UINT32 p_nb_bytes); + +/** + * Searches a given jp2 file for codestream information. + * + * @param[in] jp2file JPEG2000 file to search through for codestream info. + * @param[in] p_manager user event manager. + * @param[out] codestream_offset Byte offset from start of file where the codestream is located + * @param[out] codestream_length Length of the codestream + * @returns OPJ_TRUE if successful + */ +static OPJ_BOOL opj_jpx_find_codestream(const char* jp2file, + opj_event_mgr_t * p_manager, + OPJ_UINT32* codestream_offset, + OPJ_UINT32* codestream_length); + +/** Write out fragment tables for the linked jp2 files */ +OPJ_BOOL opj_jpx_encode(opj_jpx_t *jpx, + opj_stream_private_t *stream, + opj_event_mgr_t * p_manager) +{ + OPJ_UINT32 index = 0; + + assert(jpx != 00); + assert(jpx->files != 00); + assert(stream != 00); + assert(p_manager != 00); + + /* + * Iterate over each jp2 file that we're linking to, and add a fragment + * table box for each one. + */ + /* The jpx spec only allows a u16 for the number of references. */ + /* We should never have more than that. */ + assert(jpx->file_count < UINT16_MAX); + for (index = 0; index < jpx->file_count; index += 1) { + const char* jp2_fname = jpx->files[index]; + + // Current file index is 1-based. + jpx->current_file_index = ((OPJ_UINT16)index) + 1; + // Write out the fragment table for this jp2 file. + if (!opj_jpx_write_ftbl(jp2_fname, jpx, stream, p_manager)) { + opj_event_msg(p_manager, EVT_ERROR, + "Failed to write fragment table boxes\n"); + return OPJ_FALSE; + } + } + + /** + * Iterate over each file again, this time inserting any + * associations (xml boxes) + */ + for (index = 0; index < jpx->file_count; index += 1) { + const char* jp2_fname = jpx->files[index]; + // Current file index is 1-based. + jpx->current_file_index = (OPJ_UINT16)index + 1; + + // Write out associations for this jp2 file. + if (!opj_jpx_write_asoc(jp2_fname, jpx, stream, p_manager)) { + opj_event_msg(p_manager, EVT_ERROR, + "Failed to write association boxes\n"); + return OPJ_FALSE; + } + } + + /** + * Final step. Create the data reference table for the jp2 files. + */ + if (!opj_jpx_write_dtbl(jpx, stream, p_manager)) { + opj_event_msg(p_manager, EVT_ERROR, + "Failed to write data reference table\n"); + return OPJ_FALSE; + } + + /** Flush data to output stream */ + if (! opj_stream_flush(stream, p_manager)) { + return OPJ_FALSE; + } + + return OPJ_TRUE; +} + +OPJ_BOOL opj_jpx_end_compress(opj_jpx_t *jpx, + opj_stream_private_t *cio, + opj_event_mgr_t * p_manager + ) +{ + OPJ_UNUSED(jpx); + OPJ_UNUSED(cio); + OPJ_UNUSED(p_manager); + return OPJ_TRUE; +} + +static OPJ_BOOL opj_jpx_setup_header_writing(opj_jpx_t *jpx, + opj_event_mgr_t * p_manager) +{ + /* preconditions */ + assert(jpx != 00); + assert(p_manager != 00); + + if (! opj_procedure_list_add_procedure(jpx->jp2->m_procedure_list, + (opj_procedure)opj_jp2_write_jp, p_manager)) { + return OPJ_FALSE; + } + if (! opj_procedure_list_add_procedure(jpx->jp2->m_procedure_list, + (opj_procedure)opj_jp2_write_ftyp, p_manager)) { + return OPJ_FALSE; + } + + if (! opj_procedure_list_add_procedure(jpx->jp2->m_procedure_list, + (opj_procedure)opj_jpx_write_rreq, p_manager)) { + return OPJ_FALSE; + } + + if (! opj_procedure_list_add_procedure(jpx->jp2->m_procedure_list, + (opj_procedure)opj_jp2_write_jp2h, p_manager)) { + return OPJ_FALSE; + } + + /* DEVELOPER CORNER, insert your custom procedures */ + + return OPJ_TRUE; +} + +OPJ_BOOL opj_jpx_start_compress(opj_jpx_t *jpx, + opj_stream_private_t *stream, + opj_image_t * p_image, + opj_event_mgr_t * p_manager + ) +{ + assert(jpx != 00); + assert(stream != 00); + assert(p_manager != 00); + + OPJ_UNUSED(p_image); + + if (! opj_jpx_setup_header_writing(jpx, p_manager)) { + return OPJ_FALSE; + } + + /* write header */ + if (! opj_jp2_exec(jpx->jp2, jpx->jp2->m_procedure_list, stream, p_manager)) { + return OPJ_FALSE; + } + + /** Flush header to output stream */ + if (! opj_stream_flush(stream, p_manager)) { + return OPJ_FALSE; + } + + return OPJ_TRUE; +} + +OPJ_BOOL opj_jpx_setup_encoder(opj_jpx_t *jpx, + opj_cparameters_t *parameters, + opj_image_t *image, + opj_event_mgr_t * p_manager) +{ + assert(jpx != 00); + assert(parameters != 00); + assert(p_manager != 00); + return OPJ_TRUE; +} + +OPJ_BOOL opj_jpx_encoder_set_extra_options( + opj_jpx_t *p_jpx, + const char* const* p_options, + opj_event_mgr_t * p_manager) +{ + OPJ_UINT32 i = 0; + assert(p_jpx != 00); + assert(p_options != 00); + assert(p_manager != 00); + p_jpx->files = p_options; + // Count the number of files given in the null terminated list. + while (p_jpx->files[i] != NULL) { + i++; + } + p_jpx->file_count = i; + + // Read the first jp2 file to set the remaining jp2 parameters + if (!opj_jpx_init_jp2_options(p_jpx, p_manager)) { + return OPJ_FALSE; + } + return OPJ_TRUE; +} + +OPJ_BOOL opj_jpx_set_threads(opj_jpx_t *jpx, OPJ_UINT32 num_threads) +{ + OPJ_UNUSED(jpx); + OPJ_UNUSED(num_threads); + return OPJ_TRUE; +} + +opj_jpx_t* opj_jpx_create(void) +{ + opj_jpx_t *jpx = (opj_jpx_t*)opj_calloc(1, sizeof(opj_jpx_t)); + /* execution list creation */ + jpx->m_procedure_list = opj_procedure_list_create(); + if (! jpx->m_procedure_list) { + opj_jpx_destroy(jpx); + return 00; + } + + jpx->jp2 = opj_jp2_create(OPJ_TRUE); + if (!jpx->jp2) { + opj_jpx_destroy(jpx); + return 00; + } + return jpx; +} + +void opj_jpx_destroy(opj_jpx_t *jpx) +{ + if (jpx) { + if (jpx->m_procedure_list) { + opj_procedure_list_destroy(jpx->m_procedure_list); + } + if (jpx->jp2) { + opj_jp2_destroy(jpx->jp2); + } + opj_free(jpx); + } +} + +/** + * Writes an RREQ box - Reader Requirements box + * + * @param cio the stream to write data to. + * @param jpx the jpeg2000 file codec. + * @param p_manager the user event manager. + * + * @return true if writing was successful. + */ +OPJ_BOOL opj_jpx_write_rreq(opj_jp2_t *jp2, + opj_stream_private_t *cio, + opj_event_mgr_t * p_manager) +{ + /** + * The reader requirements box is a set of feature flags that list all + * the reader requirements needed to either display the file, or fully + * understand the file. Some features like reading metadata are needed + * to fully understand the file, but not needed for displaying the file. + */ + + // For this implementation of jpx support, we embed multiple codestreams + // in the file. So we set feature flag 2: Contains multiple composition layers + // jp2 files are only being linked, not embedded, so we set + // flag 15: Fragmented codestream where not all fragments are within the file + // but all are in locally accessible files + // We only need 1 byte to support both features masks + OPJ_UINT8 mask_length = 1; + OPJ_UINT8 fully_understand = 0b00000011; + OPJ_UINT8 display_mask = 0b00000011; + OPJ_UINT16 num_flags = 2; + OPJ_UINT16 flags[2] = {RREQ_FLAG_MULTILAYERED, + RREQ_FLAG_REFERENCE_LOCAL_FILES + }; + OPJ_UINT8 flag_masks[2] = {0b00000001, /* mask for multilayered */ + 0b00000010 + }; /* mask for local files */ + OPJ_UINT16 num_vendor_features = 0; + /* Above adds up to 13 bytes.Add 8 bytes for box length and box type. */ + OPJ_UINT32 box_length = 21; + OPJ_BYTE rreq_data[21]; + OPJ_BYTE * cursor = &rreq_data[0]; + OPJ_UINT32 i; + + /* preconditions */ + assert(cio != 00); + assert(jp2 != 00); + assert(p_manager != 00); + + OPJ_UNUSED(jp2); + + /* write box length */ + opj_jpx_cursor_write(&cursor, box_length, 4); + + /* writes box type */ + opj_jpx_cursor_write(&cursor, JPX_RREQ, 4); + + /* write reader requirements */ + /* mask length */ + opj_jpx_cursor_write(&cursor, mask_length, 1); + + /* fully understand aspects mask */ + opj_jpx_cursor_write(&cursor, fully_understand, 1); + + /* display contents mask */ + opj_jpx_cursor_write(&cursor, display_mask, 1); + + /* number of flags */ + opj_jpx_cursor_write(&cursor, num_flags, 2); + + /* Write out each flag followed by its mask */ + for (i = 0; i < num_flags; i++) { + opj_jpx_cursor_write(&cursor, flags[i], 2); + opj_jpx_cursor_write(&cursor, flag_masks[i], 1); + } + + /* Write out vendor features */ + opj_jpx_cursor_write(&cursor, num_vendor_features, 2); + + /* Assert there was no overflow. */ + assert((cursor - rreq_data) == 21); + + if (opj_stream_write_data(cio, rreq_data, box_length, + p_manager) != box_length) { + return OPJ_FALSE; + } + + return OPJ_TRUE; +} + +/** + * Ignored for jpx files. Here for codec compatibility. + * + * @param p_jpx the jpeg2000 codec. + * @param p_tile_index FIXME DOC + * @param p_data FIXME DOC + * @param p_data_size FIXME DOC + * @param p_stream the stream to write data to. + * @param p_manager the user event manager. + */ +OPJ_BOOL opj_jpx_write_tile(opj_jpx_t *p_jpx, + OPJ_UINT32 p_tile_index, + OPJ_BYTE * p_data, + OPJ_UINT32 p_data_size, + opj_stream_private_t *p_stream, + opj_event_mgr_t * p_manager) +{ + assert(p_manager != 00); + OPJ_UNUSED(p_jpx); + OPJ_UNUSED(p_tile_index); + OPJ_UNUSED(p_data); + OPJ_UNUSED(p_data_size); + OPJ_UNUSED(p_stream); + opj_event_msg(p_manager, EVT_WARNING, + "JPX encoder does not support write tile. Ignoring write tile request.\n"); + return OPJ_TRUE; +} + +static OPJ_BOOL opj_jpx_write_ftbl(const char* jp2file, + opj_jpx_t *jpx, + opj_stream_private_t *cio, + opj_event_mgr_t * p_manager) +{ + /** A fragment table consists of a fragment table box + a fragment list box. */ + /** A 1-length fragment list box is 24 bytes */ + /** So the whole table box should be 32 bytes */ + OPJ_BYTE ftbl[32]; + OPJ_BYTE* cursor = &ftbl[0]; + // FIXME: the specification states offset can be a uint64. + // opj_write_bytes only writes uint32s. + OPJ_UINT32 codestream_offset; + OPJ_UINT32 codestream_length; + OPJ_UINT16 file_index = jpx->current_file_index; + OPJ_BOOL bSuccess = opj_jpx_find_codestream(jp2file, p_manager, + &codestream_offset, &codestream_length); + if (!bSuccess) { + opj_event_msg(p_manager, EVT_ERROR, + "Failed to find codestream information in %s\n", jp2file); + return OPJ_FALSE; + } + + /** Write box size */ + opj_jpx_cursor_write(&cursor, 32, 4); + /** Write box type */ + opj_jpx_cursor_write(&cursor, JPX_FTBL, 4); + /** Write out fragment list */ + /** Fragment list box size */ + opj_jpx_cursor_write(&cursor, 24, 4); + /** Fragment list box type */ + opj_jpx_cursor_write(&cursor, JPX_FLST, 4); + /** Fragment list contents */ + // Number of fragments + opj_jpx_cursor_write(&cursor, 1, 2); + /* Codestream offset */ + opj_jpx_cursor_write(&cursor, 0, 4); + opj_jpx_cursor_write(&cursor, codestream_offset, 4); + /* Codestream length */ + opj_jpx_cursor_write(&cursor, codestream_length, 4); + /* Data Reference index */ + opj_jpx_cursor_write(&cursor, file_index, 2); + + if (opj_stream_write_data(cio, ftbl, 32, p_manager) != 32) { + return OPJ_FALSE; + } + + return OPJ_TRUE; +} + +/** + * Does a forward search for the given box type and + * sets the stream position to the header of that box. + * On success, the stream will be pointing to the box length. + * + * If the box is not found, then the stream position is + * undefined. If this function returns false, you should use + * opj_stream_seek to place the stream position back to a known location. + * + * @param p_stream The stream to read + * @param p_manager user event manager + * @param box_type The box type to search for within the stream + * @returns OPJ_TRUE if the box is found. + */ +static OPJ_BOOL opj_jpx_find_box(opj_stream_t* p_stream, + opj_event_mgr_t * p_manager, + OPJ_UINT32 box_type) +{ + OPJ_BYTE l_data_header [8]; + OPJ_OFF_T stream_position = 0; + OPJ_UINT32 lbox = 0; + OPJ_UINT32 tbox = 0; + opj_stream_private_t* l_stream = (opj_stream_private_t*) p_stream; + + + /* Iterate over the boxes in the jp2 file until we find the desired box */ + while (tbox != JP2_JP2C) { + // Seek to the next box in the stream. + if (!opj_stream_seek(l_stream, stream_position, p_manager)) { + opj_event_msg(p_manager, EVT_ERROR, + "Failed to seek while reading stream\n"); + return OPJ_FALSE; + } + + // Read the box header. + if (opj_stream_read_data(l_stream, l_data_header, 8, p_manager) != 8) { + // This probably means we reached the end of the file without + // finding the desired box. + opj_event_msg(p_manager, EVT_WARNING, + "Failed to read box information\n"); + return OPJ_FALSE; + } + + // Parse the box header + opj_read_bytes(l_data_header, &lbox, 4); + opj_read_bytes(l_data_header + 4, &tbox, 4); + + // If we found the box + if (tbox == box_type) { + // Set the stream back to the top of the box + if (!opj_stream_seek(l_stream, stream_position, p_manager)) { + // If the seek fails, return false. + opj_event_msg(p_manager, EVT_ERROR, + "Failed to seek while reading stream\n"); + return OPJ_FALSE; + } + + // And return success + return OPJ_TRUE; + } + + // Update the stream location + stream_position += lbox; + } + + // We reached the codestream without finding the box. + return OPJ_FALSE; +} + +static OPJ_BOOL opj_jpx_find_codestream(const char* jp2file, + opj_event_mgr_t * p_manager, + OPJ_UINT32* codestream_offset, + OPJ_UINT32* codestream_length) +{ + OPJ_BOOL b_found_codestream = OPJ_FALSE; + OPJ_OFF_T stream_position = 0; + OPJ_OFF_T bytes_remaining = 0; + opj_stream_private_t* stream; + + assert(jp2file != 00); + assert(codestream_offset != 00); + assert(codestream_length != 00); + + /* Create read stream for the jp2 file. */ + stream = (opj_stream_private_t*) opj_stream_create_default_file_stream(jp2file, + OPJ_TRUE); + if (!stream) { + opj_event_msg(p_manager, EVT_ERROR, + "Failed to open %s for reading\n", jp2file); + opj_stream_destroy((opj_stream_t*) stream); + return OPJ_FALSE; + } + + // Find the codestream section of the file. + b_found_codestream = opj_jpx_find_box((opj_stream_t*) stream, p_manager, + JP2_JP2C); + if (!b_found_codestream) { + opj_event_msg(p_manager, EVT_ERROR, + "Couldn't find jp2 codestream in %s\n", jp2file); + opj_stream_destroy((opj_stream_t*) stream); + return OPJ_FALSE; + } + + /* If the codestream was found, then the stream is pointing to the codestream header. */ + /* The actual codestream starts at stream position + 8 and the length of the codestream */ + /* is the remaining bytes - 8. */ + stream_position = opj_stream_tell(stream); + + assert(stream_position < (UINT32_MAX - 8)); + *codestream_offset = (OPJ_UINT32)stream_position + 8; + /* Assume the codestream runs until the end of the file. */ + bytes_remaining = opj_stream_get_number_byte_left(stream); + assert(bytes_remaining < UINT32_MAX); + *codestream_length = (OPJ_UINT32)bytes_remaining - 8; + + opj_stream_destroy((opj_stream_t*) stream); + return OPJ_TRUE; +} + +static OPJ_BOOL opj_jpx_write_asoc(const char* jp2file, + opj_jpx_t *jpx, + opj_stream_private_t *cio, + opj_event_mgr_t * p_manager) +{ + // For the current implementation, only xml boxes are associated with the + // embedded jp2 files. If there's no xml box, then the association is + // skipped + OPJ_BOOL b_has_xml_box = OPJ_FALSE; + + // Create read stream for the jp2 file. + opj_stream_t* p_stream = opj_stream_create_default_file_stream(jp2file, + OPJ_TRUE); + opj_stream_private_t* l_stream = (opj_stream_private_t*) p_stream; + if (!p_stream) { + opj_event_msg(p_manager, EVT_ERROR, + "Failed to open %s for reading\n", jp2file); + return OPJ_FALSE; + } + + b_has_xml_box = opj_jpx_find_box(p_stream, p_manager, JP2_XML); + // If there is an xml box, then embed it in an association table. + if (b_has_xml_box) { + // Read the xml box into memory + box_t xmlbox; + if (opj_jpx_read_box(l_stream, p_manager, &xmlbox)) { + // Association will contain the xmlbox (xmlbox.length), a number list box (16), and the asoc box header (8) + OPJ_UINT32 asoc_length = xmlbox.length + 16 + 8; + // Allocate memory for the asoc box + OPJ_BYTE* asoc = opj_malloc(asoc_length); + OPJ_BYTE* cursor = asoc; + OPJ_UINT32 associate_number; + OPJ_UINT64 i = 0; + if (asoc) { + /* Write asoc header */ + opj_jpx_cursor_write(&cursor, asoc_length, 4); + /* Write asoc box type */ + opj_jpx_cursor_write(&cursor, JPX_ASOC, 4); + /* Write number list box length */ + opj_jpx_cursor_write(&cursor, 16, 4); + /* Write number list type */ + opj_jpx_cursor_write(&cursor, JPX_NLST, 4); + /* Write codestream association */ + associate_number = 0x01000000; + // current_file_index is 1-based, so need to subtract one since + // associate numbers are 0 based, not 1 based like others. + associate_number |= (jpx->current_file_index - 1); + opj_jpx_cursor_write(&cursor, associate_number, 4); + /* Write compositing layer association */ + associate_number = 0x02000000; + associate_number |= (jpx->current_file_index - 1); + opj_jpx_cursor_write(&cursor, associate_number, 4); + /* Write xml box header */ + opj_jpx_cursor_write(&cursor, xmlbox.length, 4); + opj_jpx_cursor_write(&cursor, JP2_XML, 4); + /* Write xml box contents */ + /* FIXME: is there a better way to dump the buffer than 1 byte at a time?) */ + for (i = 0; i < (xmlbox.length - 8); i++) { + opj_jpx_cursor_write(&cursor, xmlbox.contents[i], 1); + } + + /* Dump contents to stream */ + if (opj_stream_write_data(cio, asoc, asoc_length, p_manager) != asoc_length) { + opj_event_msg(p_manager, EVT_WARNING, + "Failed to write association contents to stream. Stream may be corrupted.\n"); + } + + opj_free(asoc); + } else { + opj_event_msg(p_manager, EVT_WARNING, + "Failed to allocate memory for association box. Skipping.\n", jp2file); + } + + opj_jpx_destroy_box(xmlbox); + } else { + opj_event_msg(p_manager, EVT_WARNING, + "Failed to read xmlbox from %s. Skipping.\n", jp2file); + } + } + + opj_stream_destroy(p_stream); + return OPJ_TRUE; +} + +static OPJ_BOOL opj_jpx_read_box(opj_stream_private_t *l_stream, + opj_event_mgr_t * p_manager, + box_t* box) +{ + OPJ_BYTE box_header[8]; + + assert(l_stream != 00); + assert(p_manager != 00); + + if (opj_stream_read_data(l_stream, box_header, 8, p_manager) != 8) { + opj_event_msg(p_manager, EVT_ERROR, "Failed to read box header\n"); + return OPJ_FALSE; + } + + opj_read_bytes(box_header, &box->length, 4); + opj_read_bytes(box_header + 4, &box->type, 4); + + box->contents = opj_malloc(box->length); + if (!box->contents) { + opj_event_msg(p_manager, EVT_ERROR, + "Failed to allocate memory for the box\n"); + return OPJ_FALSE; + } + + if (opj_stream_read_data(l_stream, box->contents, box->length, + p_manager) != box->length) { + opj_event_msg(p_manager, EVT_ERROR, + "Failed to read all the box contents into memory\n"); + opj_free(box->contents); + return OPJ_FALSE; + } + + return OPJ_TRUE; +} + +static void opj_jpx_destroy_box(box_t box) +{ + if (box.contents) { + opj_free(box.contents); + } +} + +static void opj_jpx_cursor_write(OPJ_BYTE** p_cursor, + OPJ_UINT32 p_value, + OPJ_UINT32 p_nb_bytes) +{ + opj_write_bytes(*p_cursor, p_value, p_nb_bytes); + *p_cursor += p_nb_bytes; +} + +static OPJ_BOOL opj_jpx_write_dtbl(opj_jpx_t *jpx, + opj_stream_private_t *cio, + opj_event_mgr_t * p_manager) +{ + OPJ_UINT64 i = 0; + OPJ_BYTE* dtbl = 00; + OPJ_BYTE* cursor = 00; + // Compute the size of the data table. + OPJ_UINT32 dtbl_size = opj_jpx_compute_dtbl_size(jpx); + if (dtbl_size == UINT32_MAX) { + opj_event_msg(p_manager, EVT_ERROR, + "Failed to compute the data reference table size\n"); + return OPJ_FALSE; + } + + dtbl = opj_malloc(dtbl_size); + if (!dtbl) { + opj_event_msg(p_manager, EVT_ERROR, + "Failed to allocate memory for the data reference table\n"); + return OPJ_FALSE; + } + + cursor = dtbl; + /* Write box header */ + opj_jpx_cursor_write(&cursor, dtbl_size, 4); + opj_jpx_cursor_write(&cursor, JPX_DTBL, 4); + /* Write number of references */ + opj_jpx_cursor_write(&cursor, jpx->file_count, 2); + /* Write data reference boxes */ + for (i = 0; i < jpx->file_count; i++) { + /* Write box header */ + const char* jp2file = jpx->files[i]; + char* absolute_path = opj_jpx_get_absolute_path(jp2file); + if (absolute_path == NULL) { + opj_event_msg(p_manager, EVT_ERROR, + "Failed to get absolute path of file %s\n", jp2file); + opj_free(dtbl); + return OPJ_FALSE; + } + + { + OPJ_UINT32 box_size = opj_jpx_compute_urlbox_size(jpx->files[i]); + opj_jpx_cursor_write(&cursor, box_size, 4); + opj_jpx_cursor_write(&cursor, JPX_URL, 4); + /** Version and Flags are defined to be 0. */ + opj_jpx_cursor_write(&cursor, 0, 4); + /** Write the local file path url */ + strcpy((char*) cursor, "file://"); + cursor += strlen("file://"); + strcpy((char*) cursor, absolute_path); + cursor += strlen(absolute_path); + /* write null terminator */ + opj_jpx_cursor_write(&cursor, 0, 1); + } + + opj_free(absolute_path); + } + + /* Assert that there was no heap buffer overflow on dtbl memory. */ + /* The cursor should be exactly the computed size away from the */ + /* start of the reference table. */ + assert((cursor - dtbl) == dtbl_size); + + if (opj_stream_write_data(cio, dtbl, dtbl_size, p_manager) != dtbl_size) { + opj_event_msg(p_manager, EVT_ERROR, + "Failed to write data reference table to output stream\n"); + opj_free(dtbl); + return OPJ_FALSE; + } + + opj_free(dtbl); + return OPJ_TRUE; +} + +static OPJ_UINT32 opj_jpx_compute_dtbl_size(opj_jpx_t *jpx) +{ + OPJ_UINT32 counter = 0; + // The data reference table is made up of a data reference box + // followed by a number of data entry url boxes. + // Each data entry url box is the size of a box header + 4 bytes of 0 + // + the length of a null terminated utf8 string. + // To compute the size of the dtbl, we have to compute the length of + // all the strings that will be embedded. + OPJ_UINT32 i = 0; + for (i = 0; i < jpx->file_count; i++) { + const char* jp2file = jpx->files[i]; + OPJ_UINT32 urlbox_size = opj_jpx_compute_urlbox_size(jp2file); + if (urlbox_size == UINT32_MAX) { + return UINT32_MAX; + } + counter += urlbox_size; + } + + /* Add the data reference box size */ + // header size (8) + num_reference (2) + counter += 8 + 2; + return counter; +} + +static OPJ_UINT32 opj_jpx_compute_urlbox_size(const char* filepath) +{ + OPJ_SIZE_T counter = 0; + char* absolute_path = opj_jpx_get_absolute_path(filepath); + if (!absolute_path) { + return UINT32_MAX; + } + + /* Count the size of a data url box */ + /* header size (8) + version size (1) + flag size (3) */ + counter += 8 + 1 + 3; + /* + variable string size */ + counter += strlen(absolute_path) + strlen("file://") + 1; + + assert(counter <= UINT32_MAX); + opj_free(absolute_path); + return (OPJ_UINT32)counter; +} + +static OPJ_BOOL opj_jpx_init_jp2_options(opj_jpx_t* jpx, + opj_event_mgr_t* p_manager) +{ + opj_stream_t* p_stream = NULL; + opj_image_t* tmp_img = opj_image_create0(); + if (!tmp_img) { + opj_event_msg(p_manager, EVT_ERROR, + "Failed to create temporary image buffer\n", jpx->files[0]); + return OPJ_FALSE; + } + + // Read the header options from the first jp2 file + assert(jpx->file_count > 0); + + p_stream = opj_stream_create_default_file_stream(jpx->files[0], OPJ_TRUE); + if (!p_stream) { + opj_event_msg(p_manager, EVT_ERROR, + "Failed to open %s for reading\n", jpx->files[0]); + return OPJ_FALSE; + } + if (!opj_jp2_read_header((opj_stream_private_t*) p_stream, jpx->jp2, &tmp_img, + p_manager)) { + opj_event_msg(p_manager, EVT_ERROR, + "Failed to read header from %s\n", jpx->files[0]); + return OPJ_FALSE; + } + + /* Replace jp2 header with jpx information */ + /* Need to set brand in ftyp box to "jpx " */ + jpx->jp2->brand = JPX_JPX; + /* JPX compatibility list should contain "jpx ", "jp2 ", and "jpxb" */ + jpx->jp2->numcl = 3; + /* Allocate memory for the compatibility list. */ + if (jpx->jp2->cl) { + opj_free(jpx->jp2->cl); + } + jpx->jp2->cl = (OPJ_UINT32*) opj_malloc(jpx->jp2->numcl * sizeof(OPJ_UINT32)); + /* Return failure if we couldn't allocate memory for the cl */ + if (!jpx->jp2->cl) { + opj_event_msg(p_manager, EVT_ERROR, + "Not enough memory when setting up the JPX encoder\n"); + return OPJ_FALSE; + } + // Assign the cl values + jpx->jp2->cl[0] = JPX_JPX; + jpx->jp2->cl[1] = JP2_JP2; + jpx->jp2->cl[2] = JPX_JPXB; + + opj_image_destroy(tmp_img); + return OPJ_TRUE; +} + +#ifdef _WIN32 +static char* opj_jpx_get_absolute_path(const char* relative_path) +{ + /* Maximum path length for windows */ + DWORD buffer_size = 32767; + DWORD result = 0; + char* outbuf = opj_calloc(buffer_size, 1); + if (!outbuf) { + return NULL; + } + + result = GetFullPathName(relative_path, buffer_size, outbuf, NULL); + /* On failure, GetFullPathName will return either 0 */ + /* Or if the buffer is too small, then it returns the required buffer size. */ + if ((result == 0) || (result > buffer_size)) { + opj_free(outbuf); + return NULL; + } + + return outbuf; +} +#else +static char* opj_jpx_get_absolute_path(const char* relative_path) +{ + char* outbuf = NULL; + char path_buf[PATH_MAX]; + OPJ_SIZE_T path_length; + if (realpath(relative_path, path_buf) == NULL) { + return NULL; + } + + outbuf = opj_malloc(strlen(path_buf) + 1); + if (!outbuf) { + return NULL; + } + + strcpy(outbuf, path_buf); + return outbuf; +} +#endif \ No newline at end of file diff --git a/src/lib/openjp2/jpx.h b/src/lib/openjp2/jpx.h new file mode 100644 index 000000000..417bdeca3 --- /dev/null +++ b/src/lib/openjp2/jpx.h @@ -0,0 +1,188 @@ +/* + * The copyright in this software is being made available under the 2-clauses + * BSD License, included below. This software may be subject to other third + * party and contributor rights, including patent rights, and no such rights + * are granted under this license. + * + * Copyright (c) 2024, Daniel Garcia Briseno, ADNET Systems Inc, NASA + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS `AS IS' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#define JPX_JPX 0x6a707820 /**< JPX ftyp brand */ +#define JPX_JPXB 0x6a707862 /**< JPX baseline */ +#define JPX_RREQ 0x72726571 /**< Reader Requirements box */ +#define JPX_FTBL 0x6674626c /**< Fragment table box */ +#define JPX_FLST 0x666c7374 /**< Fragment list box */ +#define JPX_ASOC 0x61736f63 /**< Association box */ +#define JPX_ASOC 0x61736f63 /**< Association box */ +#define JPX_NLST 0x6e6c7374 /**< Number List box */ +#define JPX_URL 0x75726c20 /**< URL Box */ +#define JPX_DTBL 0x6474626c /**< Data Reference Box */ + + +/** ----------- Feature Flags ----------- **/ +#define RREQ_FLAG_MULTILAYERED 2 +#define RREQ_FLAG_REFERENCE_LOCAL_FILES 15 + + +typedef struct opj_jpx { + /** JPX depends on jp2 codec for writing out header data */ + opj_jp2_t* jp2; + /** List of files to be embedded/linked in the jpx file */ + const char *const *files; + /** Number of files to be linked */ + OPJ_UINT32 file_count; + /** list of execution procedures */ + struct opj_procedure_list * m_procedure_list; + /** The current file being processed */ + OPJ_UINT16 current_file_index; +} opj_jpx_t; + +/** +Encode an image into a JPEG-2000 jpx file +@param jp2 JP2 compressor handle +@param stream Output buffer stream +@param p_manager event manager +@return Returns true if successful, returns false otherwise +*/ +OPJ_BOOL opj_jpx_encode(opj_jpx_t *jpx, + opj_stream_private_t *stream, + opj_event_mgr_t * p_manager); + +/** + * Ends the compression procedures. + */ +OPJ_BOOL opj_jpx_end_compress(opj_jpx_t *jpx, + opj_stream_private_t *cio, + opj_event_mgr_t * p_manager + ); + +/** + * Starts a compression scheme, i.e. validates the codec parameters, writes the header. + * + * @param jp2 the jpeg2000 file codec. + * @param stream the stream object. + * @param p_image FIXME DOC + * @param p_manager FIXME DOC + * + * @return true if the codec is valid. + */ +OPJ_BOOL opj_jpx_start_compress(opj_jpx_t *jpx, + opj_stream_private_t *stream, + opj_image_t * p_image, + opj_event_mgr_t * p_manager); + +/** + * Setup the encoder parameters using the current image and using user parameters. + * Coding parameters are returned in jp2->j2k->cp. + * + * @param jp2 JP2 compressor handle + * @param parameters compression parameters + * @param image input filled image + * @param p_manager FIXME DOC + * @return OPJ_TRUE if successful, OPJ_FALSE otherwise +*/ +OPJ_BOOL opj_jpx_setup_encoder(opj_jpx_t *jpx, + opj_cparameters_t *parameters, + opj_image_t *image, + opj_event_mgr_t * p_manager); + +/** + * Specify extra options for the encoder. + * + * @param p_jp2 the jpeg2000 codec. + * @param p_options options + * @param p_manager the user event manager + * + * @see opj_encoder_set_extra_options() for more details. + */ +OPJ_BOOL opj_jpx_encoder_set_extra_options( + opj_jpx_t *p_jp2, + const char* const* p_options, + opj_event_mgr_t * p_manager); + +/** + * Does nothing at the moment. + * + * @param jp2 JP2 decompressor handle + * @param num_threads Number of threads. + * @return OPJ_TRUE in case of success. + */ +OPJ_BOOL opj_jpx_set_threads(opj_jpx_t *jpx, OPJ_UINT32 num_threads); + +/** + * Creates a jpx file compressor. + * + * @return an empty jpeg2000 file codec. + */ +opj_jpx_t* opj_jpx_create(void); + +/** + * Destroy a JPX handle + * @param jpx JPX handle to destroy + */ +void opj_jpx_destroy(opj_jpx_t *jpx); + +/** + * Writes an RREQ box - Reader Requirements box + * + * @param cio the stream to write data to. + * @param jp2 the jpeg2000 file codec. + * @param p_manager the user event manager. + * + * @return true if writing was successful. + */ +OPJ_BOOL opj_jpx_write_rreq(opj_jp2_t *jp2, + opj_stream_private_t *cio, + opj_event_mgr_t * p_manager); + +/** + * Writes an RREQ box - Reader Requirements box + * + * @param cio the stream to write data to. + * @param jp2 the jpeg2000 file codec. + * @param p_manager the user event manager. + * + * @return true if writing was successful. + */ +OPJ_BOOL opj_jpx_write_rreq(opj_jp2_t *jp2, + opj_stream_private_t *cio, + opj_event_mgr_t * p_manager); + +/** + * Ignored for jpx files. Here for codec compatibility. + * + * @param p_jpx the jpeg2000 codec. + * @param p_tile_index FIXME DOC + * @param p_data FIXME DOC + * @param p_data_size FIXME DOC + * @param p_stream the stream to write data to. + * @param p_manager the user event manager. + */ +OPJ_BOOL opj_jpx_write_tile(opj_jpx_t *p_jpx, + OPJ_UINT32 p_tile_index, + OPJ_BYTE * p_data, + OPJ_UINT32 p_data_size, + opj_stream_private_t *p_stream, + opj_event_mgr_t * p_manager); \ No newline at end of file diff --git a/src/lib/openjp2/openjpeg.c b/src/lib/openjp2/openjpeg.c index 382d8f4f0..a46629cbe 100644 --- a/src/lib/openjp2/openjpeg.c +++ b/src/lib/openjp2/openjpeg.c @@ -7,6 +7,7 @@ * Copyright (c) 2005, Herve Drolon, FreeImage Team * Copyright (c) 2008, 2011-2012, Centre National d'Etudes Spatiales (CNES), FR * Copyright (c) 2012, CS Systemes d'Information, France + * Copyright (c) 2024, Daniel Garcia Briseno, ADNET Systems Inc, NASA * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -755,6 +756,51 @@ opj_codec_t* OPJ_CALLCONV opj_create_compress(OPJ_CODEC_FORMAT p_format) return 00; } + break; + case OPJ_CODEC_JPX: + /* get a JP2 decoder handle */ + l_codec->m_codec_data.m_compression.opj_encode = (OPJ_BOOL(*)(void *, + struct opj_stream_private *, + struct opj_event_mgr *)) opj_jpx_encode; + + l_codec->m_codec_data.m_compression.opj_end_compress = (OPJ_BOOL(*)(void *, + struct opj_stream_private *, + struct opj_event_mgr *)) opj_jpx_end_compress; + + l_codec->m_codec_data.m_compression.opj_start_compress = (OPJ_BOOL(*)(void *, + struct opj_stream_private *, + struct opj_image *, + struct opj_event_mgr *)) opj_jpx_start_compress; + + l_codec->m_codec_data.m_compression.opj_write_tile = (OPJ_BOOL(*)(void *, + OPJ_UINT32, + OPJ_BYTE*, + OPJ_UINT32, + struct opj_stream_private *, + struct opj_event_mgr *)) opj_jpx_write_tile; + + l_codec->m_codec_data.m_compression.opj_destroy = (void (*)( + void *)) opj_jpx_destroy; + + l_codec->m_codec_data.m_compression.opj_setup_encoder = (OPJ_BOOL(*)(void *, + opj_cparameters_t *, + struct opj_image *, + struct opj_event_mgr *)) opj_jpx_setup_encoder; + + l_codec->m_codec_data.m_compression.opj_encoder_set_extra_options = (OPJ_BOOL( + *)(void *, + const char* const*, + struct opj_event_mgr *)) opj_jpx_encoder_set_extra_options; + + l_codec->opj_set_threads = + (OPJ_BOOL(*)(void * p_codec, OPJ_UINT32 num_threads)) opj_jpx_set_threads; + + l_codec->m_codec = opj_jpx_create(); + if (! l_codec->m_codec) { + opj_free(l_codec); + return 00; + } + break; case OPJ_CODEC_UNKNOWN: diff --git a/src/lib/openjp2/openjpeg.h b/src/lib/openjp2/openjpeg.h index 113481bbb..efcb9c241 100644 --- a/src/lib/openjp2/openjpeg.h +++ b/src/lib/openjp2/openjpeg.h @@ -271,6 +271,7 @@ typedef size_t OPJ_SIZE_T; #define OPJ_CINEMA_24_COMP 1041666 /** Maximum size per color component for 2K & 4K @ 24fps */ #define OPJ_CINEMA_48_COMP 520833 /** Maximum size per color component for 2K @ 48fps */ +#define OPJ_NO_IMAGE_DATA ((opj_image_t*) 1) /* ========================================================== enum definitions diff --git a/src/lib/openjp2/opj_includes.h b/src/lib/openjp2/opj_includes.h index 13613ce52..20d920237 100644 --- a/src/lib/openjp2/opj_includes.h +++ b/src/lib/openjp2/opj_includes.h @@ -7,6 +7,7 @@ * Copyright (c) 2005, Herve Drolon, FreeImage Team * Copyright (c) 2008, 2011-2012, Centre National d'Etudes Spatiales (CNES), FR * Copyright (c) 2012, CS Systemes d'Information, France + * Copyright (c) 2024, Daniel Garcia Briseno, ADNET Systems Inc, NASA * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -234,6 +235,7 @@ typedef unsigned int OPJ_BITFIELD; #include "invert.h" #include "j2k.h" #include "jp2.h" +#include "jpx.h" #include "mqc.h" #include "bio.h"