Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement partial jpx-write support and add new opj_merge utility #1523

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ scripts/opjstyle*

# Ignore directories made by `make`.
/bin/
/build/
1 change: 1 addition & 0 deletions src/bin/common/format_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/bin/jp2/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
198 changes: 198 additions & 0 deletions src/bin/jp2/opj_merge.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#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 <file>\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 <output file>\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(&parameters);

// 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, &parameters, 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;
}
2 changes: 2 additions & 0 deletions src/lib/openjp2/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
85 changes: 17 additions & 68 deletions src/lib/openjp2/jp2.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
*
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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 *,
Expand Down
Loading