diff --git a/.github/workflows/ci_steps.yml b/.github/workflows/ci_steps.yml index fa172df642..b41da1b487 100644 --- a/.github/workflows/ci_steps.yml +++ b/.github/workflows/ci_steps.yml @@ -214,7 +214,7 @@ jobs: if: inputs.OPENEXR_FORCE_INTERNAL_OPENJPH == 'OFF' && inputs.msystem == '' run: | set -x - share/ci/scripts/install_openjph.sh 0.22.0 + share/ci/scripts/install_openjph.sh 0.27.3 echo "OPENEXR_PATH=$OPENEXR_PATH:$PROGRAM_FILES/openjph/bin:$PROGRAM_FILES/openjph/lib" >> $GITHUB_ENV shell: bash diff --git a/cmake/OpenEXRSetup.cmake b/cmake/OpenEXRSetup.cmake index eb06961ecb..4b28f7ed6d 100644 --- a/cmake/OpenEXRSetup.cmake +++ b/cmake/OpenEXRSetup.cmake @@ -274,8 +274,8 @@ set(OPENEXR_USE_INTERNAL_OPENJPH 0 CACHE INTERNAL if (NOT OPENEXR_FORCE_INTERNAL_OPENJPH) find_package(openjph CONFIG QUIET) if(openjph_FOUND) - if(openjph_VERSION VERSION_LESS "0.21.0") - message(FATAL_ERROR "OpenJPH >= 0.21.0 required, but found ${openjph_VERSION}") + if(openjph_VERSION VERSION_LESS "0.27.3") + message(FATAL_ERROR "OpenJPH >= 0.27.3 required, but found ${openjph_VERSION}") endif() message(STATUS "Using OpenJPH ${openjph_VERSION} from ${openjph_DIR}") @@ -285,7 +285,7 @@ if (NOT OPENEXR_FORCE_INTERNAL_OPENJPH) find_package(PkgConfig) if(PKG_CONFIG_FOUND) include(FindPkgConfig) - pkg_check_modules(openjph IMPORTED_TARGET GLOBAL QUIET openjph=0.21) + pkg_check_modules(openjph IMPORTED_TARGET GLOBAL QUIET openjph=0.27.3) if(openjph_FOUND) set(EXR_OPENJPH_LIB PkgConfig::openjph) message(STATUS "Using OpenJPH ${openjph_VERSION} from ${openjph_LINK_LIBRARIES}") @@ -297,7 +297,7 @@ endif() if(EXR_OPENJPH_LIB) # Using external library # For OpenEXR.pc.in for static build - set(EXR_OPENJPH_PKGCONFIG_REQUIRES "openjph >= 0.21.0") + set(EXR_OPENJPH_PKGCONFIG_REQUIRES "openjph >= 0.27.3") else() # Using internal openjph set(OPENEXR_USE_INTERNAL_OPENJPH 1 CACHE INTERNAL diff --git a/external/OpenJPH/CMakeLists.txt b/external/OpenJPH/CMakeLists.txt index 3c7cb8abba..88bded5ee1 100644 --- a/external/OpenJPH/CMakeLists.txt +++ b/external/OpenJPH/CMakeLists.txt @@ -40,7 +40,7 @@ option(OJPH_ENABLE_TIFF_SUPPORT "Enables input and output support for TIFF files option(OJPH_BUILD_TESTS "Enables building test code" OFF) option(OJPH_BUILD_EXECUTABLES "Enables building command line executables" ON) option(OJPH_BUILD_STREAM_EXPAND "Enables building ojph_stream_expand executable" OFF) -option(OJPH_INSTALL "Install OpenJPH libraries, headers, and CMake config" ON) +option(OJPH_BUILD_FUZZER "Enables building oss-fuzzing target executable" OFF) option(OJPH_DISABLE_SIMD "Disables the use of SIMD instructions -- agnostic to architectures" OFF) option(OJPH_DISABLE_SSE "Disables the use of SSE SIMD instructions and associated files" OFF) @@ -233,10 +233,19 @@ if (OJPH_INSTALL) endif() ################################################################################################ -# Testing (OJPH_BUILD_TESTS) +# Testing and fuzzing (OJPH_BUILD_TESTS) ################################################################################################ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND OJPH_BUILD_TESTS) enable_testing() add_subdirectory(tests) endif() + +################################################################################################ +# Testing and fuzzing (OJPH_BUILD_FUZZER) +################################################################################################ + +if(OJPH_BUILD_FUZZER) + add_subdirectory(fuzzing) +endif() + diff --git a/external/OpenJPH/README.md b/external/OpenJPH/README.md index 90064a73f5..19811cac46 100644 --- a/external/OpenJPH/README.md +++ b/external/OpenJPH/README.md @@ -18,7 +18,7 @@ The standard is available free of charge from [ITU website](https://www.itu.int/ * [Usage Example](./docs/usage_examples.md) * [Web-based Demos](./docs/web_demos.md) * [Doxygen Documentation Style](./docs/doxygen_style.md) +* [OSS-Fuzzing](./docs/fuzzing.md) # Repositories # [![Packaging status](https://repology.org/badge/vertical-allrepos/openjph.svg)](https://repology.org/project/openjph/versions) - diff --git a/external/OpenJPH/src/core/codestream/ojph_codestream_local.cpp b/external/OpenJPH/src/core/codestream/ojph_codestream_local.cpp index faa2182307..066d184cc0 100644 --- a/external/OpenJPH/src/core/codestream/ojph_codestream_local.cpp +++ b/external/OpenJPH/src/core/codestream/ojph_codestream_local.cpp @@ -548,7 +548,17 @@ namespace ojph { ui32 num_comments) { //finalize - siz.check_validity(cod); + siz.set_cod(cod); + // set the tile size if it was not set by the user + size tile_size = siz.get_tile_size(); + if (tile_size.h == 0 && tile_size.w == 0) + { + point img_offset = siz.get_image_offset(); + point img_extent = siz.get_image_extent(); + size t(img_extent.x + img_offset.x, img_extent.y + img_offset.y); + siz.set_tile_size(t); + } + siz.check_validity(); cod.check_validity(siz); cod.update_atk(&atk); qcd.check_validity(siz, cod); diff --git a/external/OpenJPH/src/core/codestream/ojph_params.cpp b/external/OpenJPH/src/core/codestream/ojph_params.cpp index 1f59a2905a..65973efe6b 100644 --- a/external/OpenJPH/src/core/codestream/ojph_params.cpp +++ b/external/OpenJPH/src/core/codestream/ojph_params.cpp @@ -58,29 +58,25 @@ namespace ojph { //////////////////////////////////////////////////////////////////////////// void param_siz::set_image_extent(point dims) { - state->Xsiz = dims.x; - state->Ysiz = dims.y; + state->set_image_extent(dims); } //////////////////////////////////////////////////////////////////////////// void param_siz::set_tile_size(size s) { - state->XTsiz = s.w; - state->YTsiz = s.h; + state->set_tile_size(s); } //////////////////////////////////////////////////////////////////////////// void param_siz::set_image_offset(point offset) - { // WARNING need to check if these are valid - state->XOsiz = offset.x; - state->YOsiz = offset.y; + { + state->set_image_offset(offset); } //////////////////////////////////////////////////////////////////////////// void param_siz::set_tile_offset(point offset) - { // WARNING need to check if these are valid - state->XTOsiz = offset.x; - state->YTOsiz = offset.y; + { + state->set_tile_offset(offset); } //////////////////////////////////////////////////////////////////////////// @@ -703,24 +699,24 @@ namespace ojph { if (file->read(&Ysiz, 4) != 4) OJPH_ERROR(0x00050046, "error reading SIZ marker"); Ysiz = swap_byte(Ysiz); - if (file->read(&XOsiz, 4) != 4) + ui32 t_XOsiz, t_YOsiz; + if (file->read(&t_XOsiz, 4) != 4) OJPH_ERROR(0x00050047, "error reading SIZ marker"); - XOsiz = swap_byte(XOsiz); - if (file->read(&YOsiz, 4) != 4) + if (file->read(&t_YOsiz, 4) != 4) OJPH_ERROR(0x00050048, "error reading SIZ marker"); - YOsiz = swap_byte(YOsiz); - if (file->read(&XTsiz, 4) != 4) + set_image_offset(point(swap_byte(t_XOsiz), swap_byte(t_YOsiz))); + ui32 t_XTsiz, t_YTsiz; + if (file->read(&t_XTsiz, 4) != 4) OJPH_ERROR(0x00050049, "error reading SIZ marker"); - XTsiz = swap_byte(XTsiz); - if (file->read(&YTsiz, 4) != 4) + if (file->read(&t_YTsiz, 4) != 4) OJPH_ERROR(0x0005004A, "error reading SIZ marker"); - YTsiz = swap_byte(YTsiz); - if (file->read(&XTOsiz, 4) != 4) + set_tile_size(size(swap_byte(t_XTsiz), swap_byte(t_YTsiz))); + ui32 t_XTOsiz, t_YTOsiz; + if (file->read(&t_XTOsiz, 4) != 4) OJPH_ERROR(0x0005004B, "error reading SIZ marker"); - XTOsiz = swap_byte(XTOsiz); - if (file->read(&YTOsiz, 4) != 4) + if (file->read(&t_YTOsiz, 4) != 4) OJPH_ERROR(0x0005004C, "error reading SIZ marker"); - YTOsiz = swap_byte(YTOsiz); + set_tile_offset(point(swap_byte(t_XTOsiz), swap_byte(t_YTOsiz))); if (file->read(&Csiz, 2) != 2) OJPH_ERROR(0x0005004D, "error reading SIZ marker"); Csiz = swap_byte(Csiz); @@ -745,6 +741,8 @@ namespace ojph { ws_kern_support_needed = (Rsiz & 0x20) != 0; dfs_support_needed = (Rsiz & 0x80) != 0; + + check_validity(); } ////////////////////////////////////////////////////////////////////////// @@ -994,10 +992,21 @@ namespace ojph { OJPH_ERROR(0x0005007E, "unsupported settings in a COD-SPcod parameter"); ui8 num_decompositions = get_num_decompositions(); - if (Scod & 1) - for (int i = 0; i <= num_decompositions; ++i) + if (Scod & 1) { + for (int i = 0; i <= num_decompositions; ++i) { if (file->read(&SPcod.precinct_size[i], 1) != 1) OJPH_ERROR(0x0005007B, "error reading COD segment"); + if (i) + if ((SPcod.precinct_size[i] & 0x0F) == 0 || + (SPcod.precinct_size[i] >> 4) == 0) + OJPH_ERROR(0x0005007F, + "Precinct width or height for resolutions other than the" + " coarsest must be larger than 1; here, they are %d and %d," + " respectively.", + 1 << (SPcod.precinct_size[i] & 0x0F), + 1 << (SPcod.precinct_size[i] >> 4)); + } + } if (Lcod != 12 + ((Scod & 1) ? 1 + SPcod.num_decomp : 0)) OJPH_ERROR(0x0005007C, "error in COD segment length"); } @@ -1053,10 +1062,21 @@ namespace ojph { OJPH_ERROR(0x0005012D, "unsupported settings in a COC-SPcoc parameter"); ui8 num_decompositions = get_num_decompositions(); - if (Scod & 1) - for (int i = 0; i <= num_decompositions; ++i) + if (Scod & 1) { + for (int i = 0; i <= num_decompositions; ++i) { if (file->read(&SPcod.precinct_size[i], 1) != 1) OJPH_ERROR(0x0005012A, "error reading COC segment"); + if (i) + if ((SPcod.precinct_size[i] & 0x0F) == 0 || + (SPcod.precinct_size[i] >> 4) == 0) + OJPH_ERROR(0x0005012E, + "Precinct width or height for resolutions other than the" + " coarsest must be larger than 1; here, they are %d and %d," + " respectively.", + 1 << (SPcod.precinct_size[i] & 0x0F), + 1 << (SPcod.precinct_size[i] >> 4)); + } + } ui32 t = 9; t += num_comps < 257 ? 0 : 1; t += (Scod & 1) ? 1 + num_decompositions : 0; @@ -1196,8 +1216,10 @@ namespace ojph { qcd_component < 3 ? employing_color_transform : false); else if (qcd_wavelet_kern == param_cod::DWT_IRV97) { - if (this->base_delta == -1.0f) - this->base_delta = 1.0f / (float)(1 << qcd_bit_depth); + if (this->base_delta == -1.0f) { + ui32 t = ojph_min(16, qcd_bit_depth); + this->base_delta = 1.0f / (float)(1 << t); + } set_irrev_quant(qcd_num_decompositions); } else @@ -1230,8 +1252,16 @@ namespace ojph { c < 3 ? employing_color_transform : false); else if (cp->get_wavelet_kern() == param_cod::DWT_IRV97) { - if (qp->base_delta == -1.0f) - qp->base_delta = 1.0f / (float)(1 << bit_depth); + if (qp->base_delta == -1.0f) { + if (qcd_wavelet_kern == param_cod::DWT_IRV97) { + assert(this->base_delta != -1.0f); + qp->base_delta = this->base_delta; + } + else { + ui32 t = ojph_min(16, qcd_bit_depth); + qp->base_delta = 1.0f / (float)(1 << t); + } + } qp->set_irrev_quant(num_decompositions); } else @@ -1255,8 +1285,16 @@ namespace ojph { c < 3 ? employing_color_transform : false); else if (cp->get_wavelet_kern() == param_cod::DWT_IRV97) { - if (qp->base_delta == -1.0f) - qp->base_delta = 1.0f / (float)(1 << bit_depth); + if (qp->base_delta == -1.0f) { + if (qcd_wavelet_kern == param_cod::DWT_IRV97) { + assert(this->base_delta != -1.0f); + qp->base_delta = this->base_delta; + } + else { + ui32 t = ojph_min(16, qcd_bit_depth); + qp->base_delta = 1.0f / (float)(1 << t); + } + } qp->set_irrev_quant(num_decompositions); } else @@ -1401,11 +1439,15 @@ namespace ojph { ////////////////////////////////////////////////////////////////////////// float param_qcd::get_irrev_delta(const param_dfs* dfs, - ui32 num_decompositions, + ui32 num_decompositions, ui32 comp_num, ui32 resolution, ui32 subband) const { float arr[] = { 1.0f, 2.0f, 2.0f, 4.0f }; - assert((Sqcd & 0x1F) == 2); + if ((Sqcd & 0x1F) != 2) + OJPH_ERROR(0x00050101, "There is something wrong in the configuration " + "of the codestream; for component %d, the codestream defines an " + "irreversible transform, for which the codestream provides a " + "reversible (no quantization) step sizes in Sqcd/Sqcc.", comp_num); ui32 idx; if (dfs != NULL && dfs->exists()) @@ -1657,6 +1699,9 @@ namespace ojph { if ((Sqcd & 0x1F) == 0) { num_subbands = (Lqcd - 3); + if (num_subbands == 0) + OJPH_ERROR(0x0005008A, "QCD marker segment that specifies no " + "quantization informtion"); if (num_subbands > 97 || Lqcd != 3 + num_subbands) OJPH_ERROR(0x00050083, "wrong Lqcd value of %d in QCD marker", Lqcd); for (ui32 i = 0; i < num_subbands; ++i) @@ -1674,6 +1719,9 @@ namespace ojph { else if ((Sqcd & 0x1F) == 2) { num_subbands = (Lqcd - 3) / 2; + if (num_subbands == 0) + OJPH_ERROR(0x0005008B, "QCD marker segment that specifies no " + "quantization informtion"); if (num_subbands > 97 || Lqcd != 3 + 2 * num_subbands) OJPH_ERROR(0x00050086, "wrong Lqcd value of %d in QCD marker", Lqcd); for (ui32 i = 0; i < num_subbands; ++i) @@ -1712,6 +1760,9 @@ namespace ojph { if ((Sqcd & 0x1F) == 0) { num_subbands = (Lqcd - offset); + if (num_subbands == 0) + OJPH_ERROR(0x000500AC, "QCC marker segment that specifies no " + "quantization informtion"); if (num_subbands > 97 || Lqcd != offset + num_subbands) OJPH_ERROR(0x000500A5, "wrong Lqcd value of %d in QCC marker", Lqcd); for (ui32 i = 0; i < num_subbands; ++i) @@ -1729,6 +1780,9 @@ namespace ojph { else if ((Sqcd & 0x1F) == 2) { num_subbands = (Lqcd - offset) / 2; + if (num_subbands == 0) + OJPH_ERROR(0x000500AD, "QCC marker segment that specifies no " + "quantization informtion"); if (num_subbands > 97 || Lqcd != offset + 2 * num_subbands) OJPH_ERROR(0x000500A8, "wrong Lqcc value of %d in QCC marker", Lqcd); for (ui32 i = 0; i < num_subbands; ++i) diff --git a/external/OpenJPH/src/core/codestream/ojph_params_local.h b/external/OpenJPH/src/core/codestream/ojph_params_local.h index 3683d05042..fd5e225633 100644 --- a/external/OpenJPH/src/core/codestream/ojph_params_local.h +++ b/external/OpenJPH/src/core/codestream/ojph_params_local.h @@ -217,21 +217,35 @@ namespace ojph { cptr[comp_num].YRsiz = (ui8)downsampling.y; } - void check_validity(const param_cod& cod) + void set_image_extent(point dims) { Xsiz = dims.x; Ysiz = dims.y; } + point get_image_extent() const { return point(Xsiz, Ysiz); } + void set_tile_size(size s) { XTsiz = s.w; YTsiz = s.h; } + size get_tile_size() const { return size(XTsiz, YTsiz); } + void set_image_offset(point offset) + { XOsiz = offset.x; YOsiz = offset.y; } + point get_image_offset() const + { return point(XOsiz, YOsiz); } + void set_tile_offset(point offset) + { XTOsiz = offset.x; YTOsiz = offset.y; } + point get_tile_offset() const + { return point(XTOsiz, YTOsiz); } + + void set_cod(const param_cod& cod) { this->cod = &cod; } + + void check_validity() { - this->cod = &cod; - - if (XTsiz == 0 && YTsiz == 0) - { XTsiz = Xsiz + XOsiz; YTsiz = Ysiz + YOsiz; } if (Xsiz == 0 || Ysiz == 0 || XTsiz == 0 || YTsiz == 0) OJPH_ERROR(0x00040001, - "You cannot set image extent nor tile size to zero"); + "Image extent and/or tile size cannot be zero"); if (XTOsiz > XOsiz || YTOsiz > YOsiz) OJPH_ERROR(0x00040002, - "tile offset has to be smaller than image offset"); + "Tile offset has to be smaller than the image offset"); if (XTsiz + XTOsiz <= XOsiz || YTsiz + YTOsiz <= YOsiz) OJPH_ERROR(0x00040003, - "the top left tile must intersect with the image"); + "The top left tile must intersect with the image"); + if (Xsiz <= XOsiz || Ysiz <= YOsiz) + OJPH_ERROR(0x00040004, + "The image extent must be larger than the image offset"); } ui16 get_num_components() const { return Csiz; } @@ -701,7 +715,7 @@ namespace ojph { ui32 resolution, ui32 subband) const; ui32 propose_precision(const param_cod* cod) const; float get_irrev_delta(const param_dfs* dfs, - ui32 num_decompositions, + ui32 num_decompositions, ui32 comp_num, ui32 resolution, ui32 subband) const; bool write(outfile_base *file); bool write_qcc(outfile_base *file, ui32 num_comps); diff --git a/external/OpenJPH/src/core/codestream/ojph_resolution.cpp b/external/OpenJPH/src/core/codestream/ojph_resolution.cpp index 59a3dfb65c..0c1d4cd1e5 100644 --- a/external/OpenJPH/src/core/codestream/ojph_resolution.cpp +++ b/external/OpenJPH/src/core/codestream/ojph_resolution.cpp @@ -2,21 +2,21 @@ // This software is released under the 2-Clause BSD license, included // below. // -// Copyright (c) 2019, Aous Naman +// Copyright (c) 2019, Aous Naman // Copyright (c) 2019, Kakadu Software Pty Ltd, Australia // Copyright (c) 2019, The University of New South Wales, Australia -// +// // 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 @@ -57,7 +57,7 @@ namespace ojph { { ////////////////////////////////////////////////////////////////////////// void resolution::pre_alloc(codestream* codestream, const rect& res_rect, - const rect& recon_res_rect, + const rect& recon_res_rect, ui32 comp_num, ui32 res_num) { mem_fixed_allocator* allocator = codestream->get_allocator(); @@ -150,7 +150,7 @@ namespace ojph { tby1 = try1 >> 1; re.org.y = tby0; re.siz.h = tby1 - tby0; - subband::pre_alloc(codestream, re, comp_num, res_num, + subband::pre_alloc(codestream, re, comp_num, res_num, transform_flags); } else if (ds == param_dfs::HORZ_DWT) @@ -170,7 +170,7 @@ namespace ojph { tbx1 = trx1 >> 1; re.org.x = tbx0; re.siz.w = tbx1 - tbx0; - subband::pre_alloc(codestream, re, comp_num, res_num, + subband::pre_alloc(codestream, re, comp_num, res_num, transform_flags); } else @@ -183,7 +183,7 @@ namespace ojph { } } else - subband::pre_alloc(codestream, res_rect, comp_num, res_num, + subband::pre_alloc(codestream, res_rect, comp_num, res_num, transform_flags); //prealloc precincts @@ -219,7 +219,7 @@ namespace ojph { allocator->pre_alloc_data(width, 1); allocator->pre_alloc_data(width, 1); } - else + else { for (ui32 i = 0; i < num_steps; ++i) allocator->pre_alloc_data(width, 1); @@ -328,7 +328,7 @@ namespace ojph { child_res = allocator->post_alloc_obj(1); child_res->finalize_alloc(codestream, re, skipped_res_for_recon ? recon_res_rect : re, comp_num, - res_num - 1, comp_downsamp, next_res_downsamp, + res_num - 1, comp_downsamp, next_res_downsamp, parent_tile_comp, this); } else @@ -407,7 +407,7 @@ namespace ojph { num_precincts.w -= trx0 >> log_PP.w; num_precincts.h = (try1 + (1 << log_PP.h) - 1) >> log_PP.h; num_precincts.h -= try0 >> log_PP.h; - precincts = + precincts = allocator->post_alloc_obj((size_t)num_precincts.area()); ui64 num = num_precincts.area(); for (ui64 i = 0; i < num; ++i) @@ -508,7 +508,7 @@ namespace ojph { allocator->post_alloc_data(width, 1), width, 1); } } - else + else { for (ui32 i = 0; i < num_steps; ++i) ssp[i].line->wrap( @@ -528,7 +528,7 @@ namespace ojph { ////////////////////////////////////////////////////////////////////////// line_buf* resolution::get_line() - { + { if (vert_even) { ++cur_line; @@ -742,11 +742,11 @@ namespace ojph { { if (vert_even) { // even if (transform_flags & HORZ_TRX) - rev_horz_syn(atk, aug->line, child_res->pull_line(), + rev_horz_syn(atk, aug->line, child_res->pull_line(), bands[1].pull_line(), width, horz_even); else memcpy(aug->line->p, child_res->pull_line()->p, - (size_t)width + (size_t)width * (aug->line->flags & line_buf::LFT_SIZE_MASK)); aug->active = true; vert_even = !vert_even; @@ -755,11 +755,11 @@ namespace ojph { } else { if (transform_flags & HORZ_TRX) - rev_horz_syn(atk, sig->line, bands[2].pull_line(), + rev_horz_syn(atk, sig->line, bands[2].pull_line(), bands[3].pull_line(), width, horz_even); else memcpy(sig->line->p, bands[2].pull_line()->p, - (size_t)width + (size_t)width * (sig->line->flags & line_buf::LFT_SIZE_MASK)); sig->active = true; vert_even = !vert_even; @@ -799,7 +799,7 @@ namespace ojph { bands[1].pull_line(), width, horz_even); else memcpy(aug->line->p, child_res->pull_line()->p, - (size_t)width + (size_t)width * (aug->line->flags & line_buf::LFT_SIZE_MASK)); } else @@ -809,11 +809,11 @@ namespace ojph { bands[3].pull_line(), width, horz_even); else memcpy(aug->line->p, bands[2].pull_line()->p, - (size_t)width + (size_t)width * (aug->line->flags & line_buf::LFT_SIZE_MASK)); if (aug->line->flags & line_buf::LFT_32BIT) { - si32* sp = aug->line->i32; + si32* sp = aug->line->i32; for (ui32 i = width; i > 0; --i) *sp++ >>= 1; } @@ -843,9 +843,9 @@ namespace ojph { { if (vert_even) { // even if (transform_flags & HORZ_TRX) - irv_horz_syn(atk, aug->line, child_res->pull_line(), + irv_horz_syn(atk, aug->line, child_res->pull_line(), bands[1].pull_line(), width, horz_even); - else + else memcpy(aug->line->f32, child_res->pull_line()->f32, width * sizeof(float)); aug->active = true; @@ -859,7 +859,7 @@ namespace ojph { } else { if (transform_flags & HORZ_TRX) - irv_horz_syn(atk, sig->line, bands[2].pull_line(), + irv_horz_syn(atk, sig->line, bands[2].pull_line(), bands[3].pull_line(), width, horz_even); else memcpy(sig->line->f32, bands[2].pull_line()->f32, @@ -924,7 +924,7 @@ namespace ojph { } } else - { + { if (reversible) { if (transform_flags & HORZ_TRX) diff --git a/external/OpenJPH/src/core/codestream/ojph_subband.cpp b/external/OpenJPH/src/core/codestream/ojph_subband.cpp index b712f20087..eae112d8e5 100644 --- a/external/OpenJPH/src/core/codestream/ojph_subband.cpp +++ b/external/OpenJPH/src/core/codestream/ojph_subband.cpp @@ -3,21 +3,21 @@ // This software is released under the 2-Clause BSD license, included // below. // -// Copyright (c) 2019, Aous Naman +// Copyright (c) 2019, Aous Naman // Copyright (c) 2019, Kakadu Software Pty Ltd, Australia // Copyright (c) 2019, The University of New South Wales, Australia -// +// // 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 @@ -127,7 +127,8 @@ namespace ojph { this->band_rect = band_rect; this->parent = res; - const param_cod* cdp = codestream->get_coc(parent->get_comp_num()); + ui32 comp_num = parent->get_comp_num(); + const param_cod* cdp = codestream->get_coc(comp_num); this->reversible = cdp->access_atk()->is_reversible(); size log_cb = cdp->get_log_block_dims(); log_PP = cdp->get_log_precinct_size(res_num); @@ -149,14 +150,14 @@ namespace ojph { if (dfs != NULL) dfs = dfs->get_dfs(cdp->get_dfs_index()); } - ui32 comp_num = parent->get_comp_num(); const param_qcd* qcd = codestream->access_qcd()->get_qcc(comp_num); ui32 num_decomps = cdp->get_num_decompositions(); this->K_max = qcd->get_Kmax(dfs, num_decomps, this->res_num, band_num); if (!reversible) { - float d = - qcd->get_irrev_delta(dfs, num_decomps, res_num, subband_num); + float d = + qcd->get_irrev_delta(dfs, num_decomps, + comp_num, res_num, subband_num); d /= (float)(1u << (31 - this->K_max)); delta = d; delta_inv = (1.0f/d); @@ -199,7 +200,7 @@ namespace ojph { ui32 cbx1 = ojph_min(tbx1, x_lower_bound + (i + 1) * nominal.w); cb_size.w = cbx1 - cbx0; blocks[i].finalize_alloc(codestream, this, nominal, cb_size, - coded_cbs + i, K_max, line_offset, + coded_cbs + i, K_max, line_offset, precision, comp_num); line_offset += cb_size.w; } @@ -210,7 +211,7 @@ namespace ojph { ui32 width = band_rect.siz.w + 1; if (reversible) { - if (precision <= 32) + if (precision <= 32) lines->wrap(allocator->post_alloc_data(width, 1), width, 1); else lines->wrap(allocator->post_alloc_data(width, 1), width, 1); diff --git a/external/OpenJPH/src/core/codestream/ojph_tile.cpp b/external/OpenJPH/src/core/codestream/ojph_tile.cpp index c36b6d364e..df95d274ff 100644 --- a/external/OpenJPH/src/core/codestream/ojph_tile.cpp +++ b/external/OpenJPH/src/core/codestream/ojph_tile.cpp @@ -2,21 +2,21 @@ // This software is released under the 2-Clause BSD license, included // below. // -// Copyright (c) 2019, Aous Naman +// Copyright (c) 2019, Aous Naman // Copyright (c) 2019, Kakadu Software Pty Ltd, Australia // Copyright (c) 2019, The University of New South Wales, Australia -// +// // 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 @@ -169,7 +169,7 @@ namespace ojph { ////////////////////////////////////////////////////////////////////////// void tile::finalize_alloc(codestream *codestream, const rect& tile_rect, - ui32 tile_idx, ui32& offset, + ui32 tile_idx, ui32& offset, ui32 &num_tileparts) { //this->parent = codestream; @@ -252,7 +252,7 @@ namespace ojph { ui32 recon_tcx1 = ojph_div_ceil(tx1, recon_downsamp.x); ui32 recon_tcy1 = ojph_div_ceil(ty1, recon_downsamp.y); - line_offsets[i] = + line_offsets[i] = recon_tcx0 - ojph_div_ceil(tx0 - offset, recon_downsamp.x); comp_rects[i].org.x = tcx0; comp_rects[i].org.y = tcy0; @@ -263,7 +263,7 @@ namespace ojph { recon_comp_rects[i].siz.w = recon_tcx1 - recon_tcx0; recon_comp_rects[i].siz.h = recon_tcy1 - recon_tcy0; - comps[i].finalize_alloc(codestream, this, i, comp_rects[i], + comps[i].finalize_alloc(codestream, this, i, comp_rects[i], recon_comp_rects[i]); width = ojph_max(width, recon_comp_rects[i].siz.w); @@ -274,7 +274,7 @@ namespace ojph { OJPH_ERROR(0x000300A1, "Mismatch between Ssiz (bit_depth = %d, " "is_signed = %s) from SIZ marker segment, and BDnlt " "(bit_depth = %d, is_signed = %s) from NLT marker segment, " - "for component %d", i, num_bits[i], + "for component %d", i, num_bits[i], is_signed[i] ? "True" : "False", bd, is ? "True" : "False"); if (result == false) nlt_type3[i] = param_nlt::nonlinearity::OJPH_NLT_NO_NLT; @@ -311,7 +311,7 @@ namespace ojph { ////////////////////////////////////////////////////////////////////////// bool tile::push(line_buf *line, ui32 comp_num) { - constexpr ui8 type3 = + constexpr ui8 type3 = param_nlt::nonlinearity::OJPH_NLT_BINARY_COMPLEMENT_NLT; assert(comp_num < num_comps); @@ -334,7 +334,7 @@ namespace ojph { tc, 0, shift + 1, comp_width); else { shift = is_signed[comp_num] ? 0 : -shift; - rev_convert(line, line_offsets[comp_num], tc, 0, + rev_convert(line, line_offsets[comp_num], tc, 0, shift, comp_width); } } @@ -356,11 +356,11 @@ namespace ojph { if (reversible[comp_num]) { if (is_signed[comp_num] && nlt_type3[comp_num] == type3) - rev_convert_nlt_type3(line, line_offsets[comp_num], - lines + comp_num, 0, shift + 1, comp_width); + rev_convert_nlt_type3(line, line_offsets[comp_num], + lines + comp_num, 0, shift + 1, comp_width); else { shift = is_signed[comp_num] ? 0 : -shift; - rev_convert(line, line_offsets[comp_num], lines + comp_num, 0, + rev_convert(line, line_offsets[comp_num], lines + comp_num, 0, shift, comp_width); } @@ -379,11 +379,11 @@ namespace ojph { { if (nlt_type3[comp_num] == type3) irv_convert_to_float_nlt_type3(line, line_offsets[comp_num], - lines + comp_num, num_bits[comp_num], is_signed[comp_num], + lines + comp_num, num_bits[comp_num], is_signed[comp_num], comp_width); else irv_convert_to_float(line, line_offsets[comp_num], - lines + comp_num, num_bits[comp_num], is_signed[comp_num], + lines + comp_num, num_bits[comp_num], is_signed[comp_num], comp_width); if (comp_num == 2) { // irreversible color transform @@ -404,7 +404,7 @@ namespace ojph { ////////////////////////////////////////////////////////////////////////// bool tile::pull(line_buf* tgt_line, ui32 comp_num) { - constexpr ui8 type3 = + constexpr ui8 type3 = param_nlt::nonlinearity::OJPH_NLT_BINARY_COMPLEMENT_NLT; assert(comp_num < num_comps); @@ -413,38 +413,40 @@ namespace ojph { cur_line[comp_num]++; + ui32 comp_width = recon_comp_rects[comp_num].siz.w; + if (comp_width == 0) + return true; // nothing to pull, but not an error + if (!employ_color_transform || num_comps == 1) { line_buf *src_line = comps[comp_num].pull_line(); - ui32 comp_width = recon_comp_rects[comp_num].siz.w; if (reversible[comp_num]) { si64 shift = (si64)1 << (num_bits[comp_num] - 1); if (is_signed[comp_num] && nlt_type3[comp_num] == type3) - rev_convert_nlt_type3(src_line, 0, tgt_line, + rev_convert_nlt_type3(src_line, 0, tgt_line, line_offsets[comp_num], shift + 1, comp_width); else { shift = is_signed[comp_num] ? 0 : shift; - rev_convert(src_line, 0, tgt_line, + rev_convert(src_line, 0, tgt_line, line_offsets[comp_num], shift, comp_width); } } else { if (nlt_type3[comp_num] == type3) - irv_convert_to_integer_nlt_type3(src_line, tgt_line, - line_offsets[comp_num], num_bits[comp_num], + irv_convert_to_integer_nlt_type3(src_line, tgt_line, + line_offsets[comp_num], num_bits[comp_num], is_signed[comp_num], comp_width); else - irv_convert_to_integer(src_line, tgt_line, - line_offsets[comp_num], num_bits[comp_num], + irv_convert_to_integer(src_line, tgt_line, + line_offsets[comp_num], num_bits[comp_num], is_signed[comp_num], comp_width); } } else { assert(num_comps >= 3); - ui32 comp_width = recon_comp_rects[comp_num].siz.w; if (comp_num == 0) { if (reversible[comp_num]) @@ -465,11 +467,11 @@ namespace ojph { else src_line = comps[comp_num].pull_line(); if (is_signed[comp_num] && nlt_type3[comp_num] == type3) - rev_convert_nlt_type3(src_line, 0, tgt_line, + rev_convert_nlt_type3(src_line, 0, tgt_line, line_offsets[comp_num], shift + 1, comp_width); else { shift = is_signed[comp_num] ? 0 : shift; - rev_convert(src_line, 0, tgt_line, + rev_convert(src_line, 0, tgt_line, line_offsets[comp_num], shift, comp_width); } } @@ -479,14 +481,14 @@ namespace ojph { if (comp_num < 3) lbp = lines + comp_num; else - lbp = comps[comp_num].pull_line(); + lbp = comps[comp_num].pull_line(); if (nlt_type3[comp_num] == type3) - irv_convert_to_integer_nlt_type3(lbp, tgt_line, - line_offsets[comp_num], num_bits[comp_num], + irv_convert_to_integer_nlt_type3(lbp, tgt_line, + line_offsets[comp_num], num_bits[comp_num], is_signed[comp_num], comp_width); else - irv_convert_to_integer(lbp, tgt_line, - line_offsets[comp_num], num_bits[comp_num], + irv_convert_to_integer(lbp, tgt_line, + line_offsets[comp_num], num_bits[comp_num], is_signed[comp_num], comp_width); } } @@ -511,48 +513,48 @@ namespace ojph { tlm->set_next_pair(sot.get_tile_index(), this->num_bytes); } else if (tilepart_div == OJPH_TILEPART_RESOLUTIONS) - { + { assert(prog_order != OJPH_PO_PCRL && prog_order != OJPH_PO_CPRL); ui32 max_decs = 0; for (ui32 c = 0; c < num_comps; ++c) max_decs = ojph_max(max_decs, comps[c].get_num_decompositions()); - for (ui32 r = 0; r <= max_decs; ++r) + for (ui32 r = 0; r <= max_decs; ++r) { ui32 bytes = 0; for (ui32 c = 0; c < num_comps; ++c) bytes += comps[c].get_num_bytes(r); tlm->set_next_pair(sot.get_tile_index(), bytes); } - } + } else if (tilepart_div == OJPH_TILEPART_COMPONENTS) { if (prog_order == OJPH_PO_LRCP || prog_order == OJPH_PO_RLCP) - { + { ui32 max_decs = 0; for (ui32 c = 0; c < num_comps; ++c) max_decs = ojph_max(max_decs, comps[c].get_num_decompositions()); - for (ui32 r = 0; r <= max_decs; ++r) + for (ui32 r = 0; r <= max_decs; ++r) for (ui32 c = 0; c < num_comps; ++c) if (r <= comps[c].get_num_decompositions()) - tlm->set_next_pair(sot.get_tile_index(), + tlm->set_next_pair(sot.get_tile_index(), comps[c].get_num_bytes(r)); } else if (prog_order == OJPH_PO_CPRL) for (ui32 c = 0; c < num_comps; ++c) tlm->set_next_pair(sot.get_tile_index(), comps[c].get_num_bytes()); - else + else assert(0); // should not be here } - else + else { assert(prog_order == OJPH_PO_LRCP || prog_order == OJPH_PO_RLCP); ui32 max_decs = 0; for (ui32 c = 0; c < num_comps; ++c) max_decs = ojph_max(max_decs, comps[c].get_num_decompositions()); - for (ui32 r = 0; r <= max_decs; ++r) + for (ui32 r = 0; r <= max_decs; ++r) for (ui32 c = 0; c < num_comps; ++c) if (r <= comps[c].get_num_decompositions()) - tlm->set_next_pair(sot.get_tile_index(), + tlm->set_next_pair(sot.get_tile_index(), comps[c].get_num_bytes(r)); } } @@ -588,9 +590,9 @@ namespace ojph { for (ui32 c = 0; c < num_comps; ++c) comps[c].write_precincts(r, file); } - else if (tilepart_div == OJPH_TILEPART_RESOLUTIONS) + else if (tilepart_div == OJPH_TILEPART_RESOLUTIONS) { - for (ui32 r = 0; r <= max_decompositions; ++r) + for (ui32 r = 0; r <= max_decompositions; ++r) { ui32 bytes = 0; for (ui32 c = 0; c < num_comps; ++c) @@ -604,26 +606,26 @@ namespace ojph { ui16 t = swap_byte(JP2K_MARKER::SOD); if (!file->write(&t, 2)) OJPH_ERROR(0x00030084, "Error writing to file"); - + //write precincts for (ui32 c = 0; c < num_comps; ++c) - comps[c].write_precincts(r, file); + comps[c].write_precincts(r, file); } } - else + else { ui32 num_tileparts = num_comps * (max_decompositions + 1); for (ui32 r = 0; r <= max_decompositions; ++r) for (ui32 c = 0; c < num_comps; ++c) if (r <= comps[c].get_num_decompositions()) { //write tile header - if (!sot.write(file, comps[c].get_num_bytes(r), + if (!sot.write(file, comps[c].get_num_bytes(r), (ui8)(c + r * num_comps), (ui8)num_tileparts)) OJPH_ERROR(0x00030085, "Error writing to file"); //write start of data ui16 t = swap_byte(JP2K_MARKER::SOD); if (!file->write(&t, 2)) - OJPH_ERROR(0x00030086, "Error writing to file"); + OJPH_ERROR(0x00030086, "Error writing to file"); comps[c].write_precincts(r, file); } } diff --git a/external/OpenJPH/src/core/coding/ojph_block_encoder.cpp b/external/OpenJPH/src/core/coding/ojph_block_encoder.cpp index f9c8d89db3..019f4f1f79 100644 --- a/external/OpenJPH/src/core/coding/ojph_block_encoder.cpp +++ b/external/OpenJPH/src/core/coding/ojph_block_encoder.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include "ojph_mem.h" #include "ojph_arch.h" @@ -253,17 +254,16 @@ namespace ojph { return true; } - ///////////////////////////////////////////////////////////////////////// - static bool tables_initialized = false; - ///////////////////////////////////////////////////////////////////////// bool initialize_block_encoder_tables() { - if (!tables_initialized) { + static bool tables_initialized = false; + static std::once_flag tables_initialized_flag; + std::call_once(tables_initialized_flag, []() { memset(vlc_tbl0, 0, 2048 * sizeof(ui16)); memset(vlc_tbl1, 0, 2048 * sizeof(ui16)); tables_initialized = vlc_init_tables(); tables_initialized = tables_initialized && uvlc_init_tables(); - } + }); return tables_initialized; } diff --git a/external/OpenJPH/src/core/coding/ojph_block_encoder_avx2.cpp b/external/OpenJPH/src/core/coding/ojph_block_encoder_avx2.cpp index 1bc7a43037..91d0195dee 100644 --- a/external/OpenJPH/src/core/coding/ojph_block_encoder_avx2.cpp +++ b/external/OpenJPH/src/core/coding/ojph_block_encoder_avx2.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include "ojph_mem.h" #include "ojph_arch.h" @@ -220,17 +221,16 @@ namespace ojph { return true; } - ///////////////////////////////////////////////////////////////////////// - static bool tables_initialized = false; - ///////////////////////////////////////////////////////////////////////// bool initialize_block_encoder_tables_avx2() { - if (!tables_initialized) { + static bool tables_initialized = false; + static std::once_flag tables_initialized_flag; + std::call_once(tables_initialized_flag, []() { memset(vlc_tbl0, 0, 2048 * sizeof(ui32)); memset(vlc_tbl1, 0, 2048 * sizeof(ui32)); tables_initialized = vlc_init_tables(); tables_initialized = tables_initialized && uvlc_init_tables(); - } + }); return tables_initialized; } diff --git a/external/OpenJPH/src/core/coding/ojph_block_encoder_avx512.cpp b/external/OpenJPH/src/core/coding/ojph_block_encoder_avx512.cpp index 6de1f3b0a9..3ae768416a 100644 --- a/external/OpenJPH/src/core/coding/ojph_block_encoder_avx512.cpp +++ b/external/OpenJPH/src/core/coding/ojph_block_encoder_avx512.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include "ojph_mem.h" #include "ojph_block_encoder.h" @@ -219,17 +220,16 @@ namespace ojph { return true; } - ///////////////////////////////////////////////////////////////////////// - static bool tables_initialized = false; - ///////////////////////////////////////////////////////////////////////// bool initialize_block_encoder_tables_avx512() { - if (!tables_initialized) { + static bool tables_initialized = false; + static std::once_flag tables_initialized_flag; + std::call_once(tables_initialized_flag, []() { memset(vlc_tbl0, 0, 2048 * sizeof(ui32)); memset(vlc_tbl1, 0, 2048 * sizeof(ui32)); tables_initialized = vlc_init_tables(); tables_initialized = tables_initialized && uvlc_init_tables(); - } + }); return tables_initialized; } diff --git a/external/OpenJPH/src/core/openjph/ojph_file.h b/external/OpenJPH/src/core/openjph/ojph_file.h index bd3bd04ace..72d99310b7 100644 --- a/external/OpenJPH/src/core/openjph/ojph_file.h +++ b/external/OpenJPH/src/core/openjph/ojph_file.h @@ -131,6 +131,20 @@ namespace ojph { /** A destructor */ ~mem_outfile() override; + mem_outfile(mem_outfile const&) = delete; + mem_outfile& operator=(mem_outfile const&) = delete; + + /** + * Move construction leaves the moved-from value in default constructed state + * and transfers ownership of the internal state to the moved-to instance. + **/ + mem_outfile(mem_outfile &&) noexcept; + /** + * move assignment with the same ownership transfer semantics as + * move construction. + **/ + mem_outfile& operator=(mem_outfile&&) noexcept; + /** * @brief Call this function to open a memory file. * @@ -219,6 +233,12 @@ namespace ojph { size_t get_buf_size() const { return buf_size; } private: + + /** + * @brief A utility function to swap the contents of two instances + */ + void swap(mem_outfile& other) noexcept; + /** * @brief This function expands storage by x1.5 needed space. * @@ -291,6 +311,20 @@ namespace ojph { mem_infile() { close(); } ~mem_infile() override { } + mem_infile(mem_infile const&) = delete; + mem_infile& operator=(mem_infile const&) = delete; + + /** + * Move construction leaves the moved-from value in default constructed state + * and transfers ownership of the internal state to the moved-to instance. + **/ + mem_infile(mem_infile &&) noexcept; + /** + * move assignment with the same ownership transfer semantics as + * move construction. + **/ + mem_infile& operator=(mem_infile&&) noexcept; + void open(const ui8* data, size_t size); //read reads size bytes, returns the number of bytes read @@ -302,6 +336,9 @@ namespace ojph { void close() override { data = cur_ptr = NULL; size = 0; } private: + // swap the contents of two instances + void swap(mem_infile&) noexcept; + const ui8 *data, *cur_ptr; size_t size; }; diff --git a/external/OpenJPH/src/core/openjph/ojph_mem.h b/external/OpenJPH/src/core/openjph/ojph_mem.h index bc04a140d1..d9f22b5457 100644 --- a/external/OpenJPH/src/core/openjph/ojph_mem.h +++ b/external/OpenJPH/src/core/openjph/ojph_mem.h @@ -45,6 +45,7 @@ #include #include "ojph_arch.h" +#include "ojph_message.h" namespace ojph { @@ -91,7 +92,7 @@ namespace ojph { allocated_data = allocated_data + (allocated_data + 19) / 20; // 5% store = malloc(allocated_data); if (store == NULL) - throw "malloc failed"; + OJPH_ERROR(0x00090001, "malloc failed"); } avail_obj = store; avail_data = (ui8*)store + size_obj; @@ -244,11 +245,19 @@ namespace ojph { private: struct stores_list { + // Payload (coded_lists + bitstream) must start at a multiple of 16 bytes. + // Otherwise coded_lists::buf can be 4 mod 8, which causes misalignment + // on 32-bit architectures. So round sizeof(stores_list) to next + // multiple of 16. + static constexpr ui32 stores_list_size16() + { + return (ui32) ((sizeof (stores_list) + 15u) & ~15u); + } stores_list(ui32 available_bytes) { this->next_store = NULL; this->orig_size = this->available = available_bytes; - this->orig_data = this->data = (ui8*)this + sizeof(stores_list); + this->orig_data = this->data = (ui8*)this + stores_list_size16(); } void restart() { @@ -258,7 +267,7 @@ namespace ojph { } static ui32 eval_store_bytes(ui32 available_bytes) { // calculates how many bytes need to be allocated - return available_bytes + (ui32)sizeof(stores_list); + return available_bytes + stores_list_size16(); } stores_list *next_store; ui8 *orig_data, *data; diff --git a/external/OpenJPH/src/core/openjph/ojph_version.h b/external/OpenJPH/src/core/openjph/ojph_version.h index 2b433057d4..77b692bc00 100644 --- a/external/OpenJPH/src/core/openjph/ojph_version.h +++ b/external/OpenJPH/src/core/openjph/ojph_version.h @@ -34,5 +34,5 @@ //***************************************************************************/ #define OPENJPH_VERSION_MAJOR 0 -#define OPENJPH_VERSION_MINOR 26 +#define OPENJPH_VERSION_MINOR 27 #define OPENJPH_VERSION_PATCH 3 diff --git a/external/OpenJPH/src/core/others/ojph_file.cpp b/external/OpenJPH/src/core/others/ojph_file.cpp index 2427467e45..e3456a4eea 100644 --- a/external/OpenJPH/src/core/others/ojph_file.cpp +++ b/external/OpenJPH/src/core/others/ojph_file.cpp @@ -1,4 +1,4 @@ -//***************************************************************************/ +//***************************************************************************; // This software is released under the 2-Clause BSD license, included // below. // @@ -42,6 +42,7 @@ #include #include +#include #include "ojph_mem.h" #include "ojph_file.h" @@ -95,19 +96,33 @@ namespace ojph { fh = NULL; } - //*************************************************************************/ + //////////////////////////////////////////////////////////////////////////// + // + // // mem_outfile - //*************************************************************************/ + // + // + //////////////////////////////////////////////////////////////////////////// - /** */ + //////////////////////////////////////////////////////////////////////////// mem_outfile::mem_outfile() { is_open = clear_mem = false; buf_size = used_size = 0; - buf = cur_ptr = NULL; + buf = cur_ptr = nullptr; + } + + //////////////////////////////////////////////////////////////////////////// + void mem_outfile::swap(mem_outfile& other) noexcept { + std::swap(this->is_open,other.is_open); + std::swap(this->clear_mem,other.clear_mem); + std::swap(this->buf_size,other.buf_size); + std::swap(this->used_size,other.used_size); + std::swap(this->buf,other.buf); + std::swap(this->cur_ptr,other.cur_ptr); } - /** */ + //////////////////////////////////////////////////////////////////////////// mem_outfile::~mem_outfile() { if (buf) @@ -117,7 +132,23 @@ namespace ojph { buf = cur_ptr = NULL; } - /** */ + //////////////////////////////////////////////////////////////////////////// + mem_outfile::mem_outfile(mem_outfile&& rhs) noexcept: mem_outfile() + { + this->swap(rhs); + } + + //////////////////////////////////////////////////////////////////////////// + mem_outfile& mem_outfile::operator=(mem_outfile&& rhs) noexcept + { + if (this != &rhs) { + mem_outfile tmp(std::move(rhs)); + this->swap(tmp); + } + return *this; + } + + //////////////////////////////////////////////////////////////////////////// void mem_outfile::open(size_t initial_size, bool clear_mem) { assert(this->is_open == false); @@ -131,12 +162,13 @@ namespace ojph { this->cur_ptr = this->buf; } - /** */ + //////////////////////////////////////////////////////////////////////////// void mem_outfile::close() { is_open = false; cur_ptr = buf; } + //////////////////////////////////////////////////////////////////////////// /** The seek function expands the buffer whenever offset goes beyond * the buffer end */ @@ -162,6 +194,7 @@ namespace ojph { return 0; } + //////////////////////////////////////////////////////////////////////////// /** Whenever the need arises, the buffer is expanded by a factor approx 1.5x */ size_t mem_outfile::write(const void *ptr, size_t new_size) @@ -183,7 +216,7 @@ namespace ojph { return new_size; } - /** */ + //////////////////////////////////////////////////////////////////////////// void mem_outfile::write_to_file(const char *file_name) const { assert(is_open == false); @@ -196,7 +229,7 @@ namespace ojph { fclose(f); } - /** */ + //////////////////////////////////////////////////////////////////////////// void mem_outfile::expand_storage(size_t needed_size, bool clear_all) { if (needed_size > buf_size) @@ -284,6 +317,22 @@ namespace ojph { // //////////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////////// + mem_infile::mem_infile(mem_infile&& rhs) noexcept: mem_infile() + { + this->swap(rhs); + } + + //////////////////////////////////////////////////////////////////////////// + mem_infile& mem_infile::operator=(mem_infile&& rhs) noexcept + { + if (this != &rhs) { + mem_infile tmp(std::move(rhs)); + this->swap(tmp); + } + return *this; + } + //////////////////////////////////////////////////////////////////////////// void mem_infile::open(const ui8* data, size_t size) { @@ -342,5 +391,12 @@ namespace ojph { return result; } + //////////////////////////////////////////////////////////////////////////// + void mem_infile::swap(mem_infile& other) noexcept + { + std::swap(this->data,other.data); + std::swap(this->cur_ptr,other.cur_ptr); + std::swap(this->size,other.size); + } } diff --git a/external/OpenJPH/src/core/others/ojph_mem.cpp b/external/OpenJPH/src/core/others/ojph_mem.cpp index fcb9e636ad..ca7a561631 100644 --- a/external/OpenJPH/src/core/others/ojph_mem.cpp +++ b/external/OpenJPH/src/core/others/ojph_mem.cpp @@ -112,7 +112,10 @@ namespace ojph { //////////////////////////////////////////////////////////////////////////// void mem_elastic_allocator::get_buffer(ui32 needed_bytes, coded_lists* &p) { - ui32 extended_bytes = needed_bytes + (ui32)sizeof(coded_lists); + // Round up so each coded_lists (and coded_lists::buf) stays 16-byte aligned + // within the store; avoids alignment fault on 32-bit architectures + ui32 raw = needed_bytes + (ui32)sizeof (coded_lists); + ui32 extended_bytes = (raw + 15u) & ~15u; if (store == NULL) cur_store = store = allocate(&store, extended_bytes); diff --git a/external/OpenJPH/src/core/others/ojph_mem.c b/external/OpenJPH/src/core/others/ojph_mem_c.c similarity index 99% rename from external/OpenJPH/src/core/others/ojph_mem.c rename to external/OpenJPH/src/core/others/ojph_mem_c.c index f88dc11a9f..8c66d242e1 100644 --- a/external/OpenJPH/src/core/others/ojph_mem.c +++ b/external/OpenJPH/src/core/others/ojph_mem_c.c @@ -30,7 +30,7 @@ // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //***************************************************************************/ // This file is part of the OpenJPH software implementation. -// File: ojph_mem.c +// File: ojph_mem_c.c // Author: Aous Naman // Date: 17 October 2025 //***************************************************************************/ diff --git a/external/OpenJPH/src/core/transform/ojph_colour.cpp b/external/OpenJPH/src/core/transform/ojph_colour.cpp index b3c05aea6f..ef147adeb8 100644 --- a/external/OpenJPH/src/core/transform/ojph_colour.cpp +++ b/external/OpenJPH/src/core/transform/ojph_colour.cpp @@ -37,6 +37,7 @@ #include #include +#include #include "ojph_defs.h" #include "ojph_arch.h" @@ -104,27 +105,23 @@ namespace ojph { (const float *y, const float *cb, const float *cr, float *r, float *g, float *b, ui32 repeat) = NULL; - ////////////////////////////////////////////////////////////////////////// - static bool colour_transform_functions_initialized = false; - ////////////////////////////////////////////////////////////////////////// void init_colour_transform_functions() { - if (colour_transform_functions_initialized) - return; - + static std::once_flag colour_transform_functions_init_flag; + std::call_once(colour_transform_functions_init_flag, []() { #if !defined(OJPH_ENABLE_WASM_SIMD) || !defined(OJPH_EMSCRIPTEN) - rev_convert = gen_rev_convert; - rev_convert_nlt_type3 = gen_rev_convert_nlt_type3; - irv_convert_to_integer = gen_irv_convert_to_integer; - irv_convert_to_float = gen_irv_convert_to_float; - irv_convert_to_integer_nlt_type3 = gen_irv_convert_to_integer_nlt_type3; - irv_convert_to_float_nlt_type3 = gen_irv_convert_to_float_nlt_type3; - rct_forward = gen_rct_forward; - rct_backward = gen_rct_backward; - ict_forward = gen_ict_forward; - ict_backward = gen_ict_backward; + rev_convert = gen_rev_convert; + rev_convert_nlt_type3 = gen_rev_convert_nlt_type3; + irv_convert_to_integer = gen_irv_convert_to_integer; + irv_convert_to_float = gen_irv_convert_to_float; + irv_convert_to_integer_nlt_type3 = gen_irv_convert_to_integer_nlt_type3; + irv_convert_to_float_nlt_type3 = gen_irv_convert_to_float_nlt_type3; + rct_forward = gen_rct_forward; + rct_backward = gen_rct_backward; + ict_forward = gen_ict_forward; + ict_backward = gen_ict_backward; #ifndef OJPH_DISABLE_SIMD @@ -186,20 +183,19 @@ namespace ojph { #else // OJPH_ENABLE_WASM_SIMD - rev_convert = wasm_rev_convert; - rev_convert_nlt_type3 = wasm_rev_convert_nlt_type3; - irv_convert_to_integer = wasm_irv_convert_to_integer; - irv_convert_to_float = wasm_irv_convert_to_float; - irv_convert_to_integer_nlt_type3 = wasm_irv_convert_to_integer_nlt_type3; - irv_convert_to_float_nlt_type3 = wasm_irv_convert_to_float_nlt_type3; - rct_forward = wasm_rct_forward; - rct_backward = wasm_rct_backward; - ict_forward = wasm_ict_forward; - ict_backward = wasm_ict_backward; + rev_convert = wasm_rev_convert; + rev_convert_nlt_type3 = wasm_rev_convert_nlt_type3; + irv_convert_to_integer = wasm_irv_convert_to_integer; + irv_convert_to_float = wasm_irv_convert_to_float; + irv_convert_to_integer_nlt_type3 = wasm_irv_convert_to_integer_nlt_type3; + irv_convert_to_float_nlt_type3 = wasm_irv_convert_to_float_nlt_type3; + rct_forward = wasm_rct_forward; + rct_backward = wasm_rct_backward; + ict_forward = wasm_ict_forward; + ict_backward = wasm_ict_backward; #endif // !OJPH_ENABLE_WASM_SIMD - - colour_transform_functions_initialized = true; + }); } ////////////////////////////////////////////////////////////////////////// diff --git a/external/OpenJPH/src/core/transform/ojph_transform.cpp b/external/OpenJPH/src/core/transform/ojph_transform.cpp index c4313ab29e..f67ea1b6ad 100644 --- a/external/OpenJPH/src/core/transform/ojph_transform.cpp +++ b/external/OpenJPH/src/core/transform/ojph_transform.cpp @@ -36,6 +36,7 @@ //***************************************************************************/ #include +#include #include "ojph_arch.h" #include "ojph_mem.h" @@ -93,25 +94,21 @@ namespace ojph { (const param_atk* atk, const line_buf* dst, const line_buf* lsrc, const line_buf* hsrc, ui32 width, bool even) = NULL; - //////////////////////////////////////////////////////////////////////////// - static bool wavelet_transform_functions_initialized = false; - ////////////////////////////////////////////////////////////////////////// void init_wavelet_transform_functions() { - if (wavelet_transform_functions_initialized) - return; - + static std::once_flag wavelet_transform_functions_init_flag; + std::call_once(wavelet_transform_functions_init_flag, [](){ #if !defined(OJPH_ENABLE_WASM_SIMD) || !defined(OJPH_EMSCRIPTEN) - rev_vert_step = gen_rev_vert_step; - rev_horz_ana = gen_rev_horz_ana; - rev_horz_syn = gen_rev_horz_syn; + rev_vert_step = gen_rev_vert_step; + rev_horz_ana = gen_rev_horz_ana; + rev_horz_syn = gen_rev_horz_syn; - irv_vert_step = gen_irv_vert_step; - irv_vert_times_K = gen_irv_vert_times_K; - irv_horz_ana = gen_irv_horz_ana; - irv_horz_syn = gen_irv_horz_syn; + irv_vert_step = gen_irv_vert_step; + irv_vert_times_K = gen_irv_vert_times_K; + irv_horz_ana = gen_irv_horz_ana; + irv_horz_syn = gen_irv_horz_syn; #ifndef OJPH_DISABLE_SIMD @@ -185,8 +182,7 @@ namespace ojph { irv_horz_ana = wasm_irv_horz_ana; irv_horz_syn = wasm_irv_horz_syn; #endif // !OJPH_ENABLE_WASM_SIMD - - wavelet_transform_functions_initialized = true; + }); } ////////////////////////////////////////////////////////////////////////// diff --git a/external/OpenJPH/target_arch.cmake b/external/OpenJPH/target_arch.cmake index 61067cdc47..727b9978a7 100644 --- a/external/OpenJPH/target_arch.cmake +++ b/external/OpenJPH/target_arch.cmake @@ -1,68 +1,68 @@ -# This is to detect the target architecture. -# The detection relies on the compiler's "#error" preprocessor directive to emit the architecture. - -# This is inspired by https://github.com/axr/solar-cmake/blob/master/TargetArch.cmake -# which is inspired by -# https://qt.gitorious.org/qt/qtbase/blobs/master/src/corelib/global/qprocessordetection.h - -set(archdetect_c_code " -#if defined(__arm__) || defined(__TARGET_ARCH_ARM) \ - || defined(__aarch64__) || defined(_M_ARM64) - #error cmake_ARCH OJPH_ARCH_ARM -#elif defined(__i386) || defined(__i386__) || defined(_M_IX86) - #error cmake_ARCH OJPH_ARCH_I386 -#elif defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(_M_X64) - #error cmake_ARCH OJPH_ARCH_X86_64 -#elif defined(__ia64) || defined(__ia64__) || defined(_M_IA64) - #error cmake_ARCH OJPH_ARCH_IA64 -#elif defined(__ppc__) || defined(__ppc) || defined(__powerpc__) \\ - || defined(_ARCH_COM) || defined(_ARCH_PWR) || defined(_ARCH_PPC) \\ - || defined(_M_MPPC) || defined(_M_PPC) - #if defined(__ppc64__) || defined(__powerpc64__) || defined(__64BIT__) - #error cmake_ARCH OJPH_ARCH_PPC64 - #else - #error cmake_ARCH OJPH_ARCH_PPC - #endif -#endif - -#error cmake_ARCH OJPH_ARCH_UNKNOWN -") - -function(target_architecture output_var) - - file(WRITE "${CMAKE_BINARY_DIR}/arch.c" "${archdetect_c_code}") - - enable_language(C) - - # Detect the architecture in a rather creative way... - # This compiles a small C program which is a series of ifdefs that selects a - # particular #error preprocessor directive whose message string contains the - # target architecture. The program will always fail to compile (both because - # file is not a valid C program, and obviously because of the presence of the - # #error preprocessor directives... but by exploiting the preprocessor in this - # way, we can detect the correct target architecture even when cross-compiling, - # since the program itself never needs to be run (only the compiler/preprocessor) - try_run( - run_result_unused - compile_result_unused - "${CMAKE_BINARY_DIR}" - "${CMAKE_BINARY_DIR}/arch.c" - COMPILE_OUTPUT_VARIABLE ARCH - ) - - # Parse the architecture name from the compiler output - string(REGEX MATCH "cmake_ARCH ([a-zA-Z0-9_]+)" ARCH "${ARCH}") - - # Get rid of the value marker leaving just the architecture name - string(REPLACE "cmake_ARCH " "" ARCH "${ARCH}") - - # If we are compiling with an unknown architecture this variable should - # already be set to "unknown" but in the case that it's empty (i.e. due - # to a typo in the code), then set it to unknown - if (NOT ARCH) - set(ARCH OJPH_ARCH_UNKNOWN) - endif() - - set(${output_var} "${ARCH}" PARENT_SCOPE) - +# This is to detect the target architecture. +# The detection relies on the compiler's "#error" preprocessor directive to emit the architecture. + +# This is inspired by https://github.com/axr/solar-cmake/blob/master/TargetArch.cmake +# which is inspired by +# https://qt.gitorious.org/qt/qtbase/blobs/master/src/corelib/global/qprocessordetection.h + +set(archdetect_c_code " +#if defined(__arm__) || defined(__TARGET_ARCH_ARM) \ + || defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) + #error cmake_ARCH OJPH_ARCH_ARM +#elif defined(__i386) || defined(__i386__) || defined(_M_IX86) + #error cmake_ARCH OJPH_ARCH_I386 +#elif defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(_M_X64) + #error cmake_ARCH OJPH_ARCH_X86_64 +#elif defined(__ia64) || defined(__ia64__) || defined(_M_IA64) + #error cmake_ARCH OJPH_ARCH_IA64 +#elif defined(__ppc__) || defined(__ppc) || defined(__powerpc__) \\ + || defined(_ARCH_COM) || defined(_ARCH_PWR) || defined(_ARCH_PPC) \\ + || defined(_M_MPPC) || defined(_M_PPC) + #if defined(__ppc64__) || defined(__powerpc64__) || defined(__64BIT__) + #error cmake_ARCH OJPH_ARCH_PPC64 + #else + #error cmake_ARCH OJPH_ARCH_PPC + #endif +#endif + +#error cmake_ARCH OJPH_ARCH_UNKNOWN +") + +function(target_architecture output_var) + + file(WRITE "${CMAKE_BINARY_DIR}/arch.c" "${archdetect_c_code}") + + enable_language(C) + + # Detect the architecture in a rather creative way... + # This compiles a small C program which is a series of ifdefs that selects a + # particular #error preprocessor directive whose message string contains the + # target architecture. The program will always fail to compile (both because + # file is not a valid C program, and obviously because of the presence of the + # #error preprocessor directives... but by exploiting the preprocessor in this + # way, we can detect the correct target architecture even when cross-compiling, + # since the program itself never needs to be run (only the compiler/preprocessor) + try_run( + run_result_unused + compile_result_unused + "${CMAKE_BINARY_DIR}" + "${CMAKE_BINARY_DIR}/arch.c" + COMPILE_OUTPUT_VARIABLE ARCH + ) + + # Parse the architecture name from the compiler output + string(REGEX MATCH "cmake_ARCH ([a-zA-Z0-9_]+)" ARCH "${ARCH}") + + # Get rid of the value marker leaving just the architecture name + string(REPLACE "cmake_ARCH " "" ARCH "${ARCH}") + + # If we are compiling with an unknown architecture this variable should + # already be set to "unknown" but in the case that it's empty (i.e. due + # to a typo in the code), then set it to unknown + if (NOT ARCH) + set(ARCH OJPH_ARCH_UNKNOWN) + endif() + + set(${output_var} "${ARCH}" PARENT_SCOPE) + endfunction() \ No newline at end of file diff --git a/share/util/vendor_openjph.sh b/share/util/vendor_openjph.sh index 2909f63bd2..4a26bc5443 100755 --- a/share/util/vendor_openjph.sh +++ b/share/util/vendor_openjph.sh @@ -107,15 +107,6 @@ a\ endif() }' OpenJPH/src/core/CMakeLists.txt -# Headers live under "common" in older OpenJPH releases but are included via -# "openjph/ojph_arch.h". Rename the directory if needed. -if [ -d OpenJPH/src/core/common ]; then - mv OpenJPH/src/core/common OpenJPH/src/core/openjph - "${sed_cmd[@]}" 's,/common/,/openjph/,' OpenJPH/ojph_version.cmake - "${sed_cmd[@]}" 's,/common,/openjph,' OpenJPH/src/core/CMakeLists.txt - "${sed_cmd[@]}" 's,common/,openjph/,' OpenJPH/src/core/CMakeLists.txt -fi - if [[ -n "${master_sha:-}" ]]; then echo "#define OPENJPH_VERSION_SHA ${master_sha}" >> OpenJPH/src/core/openjph/ojph_version.h fi diff --git a/src/bin/exrmetrics/CMakeLists.txt b/src/bin/exrmetrics/CMakeLists.txt index d57c8c582f..220f2788f1 100644 --- a/src/bin/exrmetrics/CMakeLists.txt +++ b/src/bin/exrmetrics/CMakeLists.txt @@ -1,4 +1,4 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) Contributors to the OpenEXR Project. -add_openexr_bin_program(exrmetrics SOURCES main.cpp exrmetrics.cpp) +add_openexr_bin_program(exrmetrics SOURCES main.cpp exrmetrics.cpp distortionUtils.cpp) diff --git a/src/bin/exrmetrics/distortionUtils.cpp b/src/bin/exrmetrics/distortionUtils.cpp new file mode 100644 index 0000000000..f5555ced6b --- /dev/null +++ b/src/bin/exrmetrics/distortionUtils.cpp @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) Contributors to the OpenEXR Project. + +#include "distortionUtils.h" + +#include +#include + +using IMATH_NAMESPACE::half; + +template <> +void +accumLogMSE ( + const half* orig, + const half* reread, + uint64_t pixelsInChannel, + double& sumSq, + uint64_t& count) +{ + const double LN_HALF_DENORM_MIN = std::log(HALF_DENORM_MIN); + for (uint64_t px = 0; px < pixelsInChannel; ++px) + { + double a = static_cast (orig[px]); + double b = static_cast (reread[px]); + if (!std::isfinite (a)) { continue; } + if (!std::isfinite (b)) { count = 0; sumSq = 0.0; return; } + + double diff = (a < 0 ? -1.0 : 1.0) * (std::log (std::abs (a) + HALF_DENORM_MIN) - LN_HALF_DENORM_MIN) - + (b < 0 ? -1.0 : 1.0) * (std::log (std::abs (b) + HALF_DENORM_MIN) - LN_HALF_DENORM_MIN); + sumSq += diff * diff; + ++count; +} +} + +template <> +void +accumLogMSE ( + const float* orig, + const float* reread, + uint64_t pixelsInChannel, + double& sumSq, + uint64_t& count) +{ + const double ln_eps = std::log (std::numeric_limits::denorm_min ()); + constexpr double eps = std::numeric_limits::denorm_min (); + for (uint64_t px = 0; px < pixelsInChannel; ++px) + { + double a = static_cast (orig[px]); + double b = static_cast (reread[px]); + if (!std::isfinite (a)) { continue; } + if (!std::isfinite (b)) { count = 0; sumSq = 0.0; return; } + double diff = (a < 0 ? -1.0 : 1.0) * (std::log (std::abs (a) + eps) - ln_eps) - + (b < 0 ? -1.0 : 1.0) * (std::log (std::abs (b) + eps) - ln_eps); + sumSq += diff * diff; + ++count; + } +} diff --git a/src/bin/exrmetrics/distortionUtils.h b/src/bin/exrmetrics/distortionUtils.h new file mode 100644 index 0000000000..463965bc7e --- /dev/null +++ b/src/bin/exrmetrics/distortionUtils.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) Contributors to the OpenEXR Project. + +#ifndef INCLUDED_DISTORTION_UTILS_H +#define INCLUDED_DISTORTION_UTILS_H + +#include +#include + +template +void accumLogMSE ( + const T* orig, + const T* reread, + uint64_t pixelsInChannel, + double& sumSq, + uint64_t& count); + +template <> +void accumLogMSE ( + const IMATH_NAMESPACE::half* orig, + const IMATH_NAMESPACE::half* reread, + uint64_t pixelsInChannel, + double& sumSq, + uint64_t& count); + +template <> +void accumLogMSE ( + const float* orig, + const float* reread, + uint64_t pixelsInChannel, + double& sumSq, + uint64_t& count); + +#endif diff --git a/src/bin/exrmetrics/exrmetrics.cpp b/src/bin/exrmetrics/exrmetrics.cpp index 42d31a58f3..abc39c5aef 100644 --- a/src/bin/exrmetrics/exrmetrics.cpp +++ b/src/bin/exrmetrics/exrmetrics.cpp @@ -5,6 +5,7 @@ // #include "exrmetrics.h" +#include "distortionUtils.h" #include "ImfChannelList.h" #include "ImfDeepFrameBuffer.h" @@ -23,8 +24,12 @@ #include "ImfTiledMisc.h" #include "ImfTiledOutputPart.h" +#include + #include +#include #include +#include #include #include #include @@ -32,6 +37,7 @@ using namespace OPENEXR_IMF_NAMESPACE; using IMATH_NAMESPACE::Box2i; +using IMATH_NAMESPACE::half; using std::cerr; using namespace std::chrono; @@ -988,7 +994,8 @@ exrmetrics ( bool write, bool reread, PixelMode pixelMode, - bool verbose) + bool verbose, + bool computeDistortion) { if (verbose) @@ -1042,6 +1049,10 @@ exrmetrics ( outHeaders[p].zipCompressionLevel () = level; compressionSet = true; break; + case HTJ2KL256_COMPRESSION: + outHeaders[p].lossyHTJ2KQuality () = level; + compressionSet = true; + break; // case ZSTD_COMPRESSION : // outHeader.zstdCompressionLevel()=level; // break; @@ -1081,7 +1092,7 @@ exrmetrics ( if (!isinf (level) && level >= -1 && !compressionSet) { throw runtime_error ( - "-l option only works for DWAA/DWAB,ZIP/ZIPS or ZSTD compression"); + "-l option only works for DWAA/DWAB, HTJ2KL256,ZIP/ZIPS or ZSTD compression"); } vector parts (part == -1 ? in.parts () : 1); @@ -1171,11 +1182,111 @@ exrmetrics ( else { metrics.outputFileSize = fileSize; } } + // + // compute distortion vs. re-read data + // + if (computeDistortion && write && reread) + { + for (size_t p = 0; p < parts.size (); ++p) + { + metrics.stats[p].distortionCount = 0; + metrics.stats[p].distortion = std::numeric_limits::quiet_NaN (); + string partType = outHeaders[p].type (); + if (partType != SCANLINEIMAGE && partType != TILEDIMAGE) continue; + + // skip parts with mixed channel types or with no channels + { + auto chBegin = outHeaders[p].channels ().begin (); + auto chEnd = outHeaders[p].channels ().end (); + if (chBegin == chEnd) continue; + PixelType firstType = chBegin.channel ().type; + bool allSameType = true; + for (auto i = chBegin; i != chEnd; ++i) + { + if (i.channel ().type != firstType) + { + allSameType = false; + break; + } + } + if (!allSameType) continue; + } + + Box2i dw = outHeaders[p].dataWindow (); + uint64_t width = dw.max.x + 1 - dw.min.x; + uint64_t height = dw.max.y + 1 - dw.min.y; + + double sumSq = 0.0; + uint64_t count = 0; + int channelIndex = 0; + + for (ChannelList::ConstIterator i = + outHeaders[p].channels ().begin (); + i != outHeaders[p].channels ().end (); + ++i, ++channelIndex) + { + if (i.channel ().type != HALF && i.channel ().type != FLOAT) + continue; + + uint64_t pixelsInChannel = + (width / i.channel ().xSampling) * + (height / i.channel ().ySampling); + + const char* origData = nullptr; + const char* rereadData = nullptr; + + if (partType == SCANLINEIMAGE) + { + origData = + parts[p].readBuf.scanlinePixelData[channelIndex].data (); + rereadData = + parts[p].rereadBuf.scanlinePixelData[channelIndex].data (); + } + else + { + if (parts[p].readBuf.tilePixelData.empty () || + parts[p].rereadBuf.tilePixelData.empty ()) + continue; + origData = + parts[p].readBuf.tilePixelData[0][channelIndex].data (); + rereadData = + parts[p].rereadBuf.tilePixelData[0][channelIndex].data (); + } + + if (i.channel ().type == HALF) + { + metrics.stats[p].metricKind = LOG_MSE_HALF; + accumLogMSE ( + reinterpret_cast (origData), + reinterpret_cast (rereadData), + pixelsInChannel, + sumSq, + count); + } + else if (i.channel ().type == FLOAT) + { + metrics.stats[p].metricKind = LOG_MSE_FLOAT; + accumLogMSE ( + reinterpret_cast (origData), + reinterpret_cast (rereadData), + pixelsInChannel, + sumSq, + count); + } + } + + metrics.stats[p].distortionCount = count; + metrics.stats[p].distortion = + count > 0 ? sumSq / count + : std::numeric_limits::quiet_NaN (); + } + } + // // sum across all parts // - metrics.totalStats = metrics.stats[0]; + metrics.totalStats = metrics.stats[0]; for (size_t i = 1; i < metrics.stats.size (); ++i) { accumulate (metrics.totalStats.readPerf, metrics.stats[i].readPerf); diff --git a/src/bin/exrmetrics/exrmetrics.h b/src/bin/exrmetrics/exrmetrics.h index b42946572a..489eb7161a 100644 --- a/src/bin/exrmetrics/exrmetrics.h +++ b/src/bin/exrmetrics/exrmetrics.h @@ -15,6 +15,7 @@ #include "ImfCompression.h" +#include #include #include @@ -43,6 +44,13 @@ struct partSizeData std::string partType = ""; }; +enum DistortionMetric +{ + DISTORTION_METRIC_NONE, + LOG_MSE_HALF, + LOG_MSE_FLOAT, +}; + struct partStats { std::vector @@ -57,6 +65,10 @@ struct partStats rereadPerf; // for deep, times reading the sample count, otherwise times reading the entire data uint64_t sizeOnDisk = 0; // record compressed size of part on disk. + DistortionMetric metricKind = DISTORTION_METRIC_NONE; // kind of distortion metric used + double distortion = std::numeric_limits::quiet_NaN (); // distortion computed using the distortion metric + uint64_t distortionCount = 0; // number of samples used to compute the distortion + partSizeData sizeData; }; @@ -78,6 +90,7 @@ fileMetrics exrmetrics ( bool write, bool reread, PixelMode pixelMode, - bool verbose); + bool verbose, + bool computeDistortion = false); #endif diff --git a/src/bin/exrmetrics/main.cpp b/src/bin/exrmetrics/main.cpp index 117813d36d..866889d8ea 100644 --- a/src/bin/exrmetrics/main.cpp +++ b/src/bin/exrmetrics/main.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -64,7 +65,7 @@ usageMessage (ostream& stream, const char* program_name, bool verbose = false) " -t n Use a pool of n worker threads for processing files.\n" " Default is single threaded (no thread pool)\n" "\n" - " -l level set DWA or ZIP compression level\n" + " -l level set compression level for DWA, ZIP and lossy HTJ2K\n" "\n" " -z,--compression list list of compression methods to test\n" " (" @@ -91,6 +92,10 @@ usageMessage (ostream& stream, const char* program_name, bool verbose = false) " --csv print output in csv mode. If passes>1, show median timing\n" " default is JSON mode\n" " --passes num write and re-read file num times (default 1)\n" + " --distortion compute LogMSE per part, comparing original vs. re-read after compression.\n" + " Parts must have uniform half or float channel types. Samples that are non-finite\n" + " in the original are skipped. Samples that are finite in the original and not finite\n" + " in the re-read result in Nan." "\n" " -h, --help print this message\n" " -v output progress messages\n" @@ -121,6 +126,7 @@ struct options bool outputPartSizeOnDisk = false; bool verbose = false; bool csv = false; + bool computeDistortion = false; std::vector pixelModes; std::vector compressions; @@ -259,7 +265,8 @@ jsonStats ( int timing, bool partSize, bool raw, - bool stats) + bool stats, + bool computeDistortion) { static const char* lastFileName = nullptr; @@ -386,25 +393,33 @@ jsonStats ( printPartStats ( out, run.metrics.totalStats, " ", timing,false, raw, stats); } - if (timing && run.metrics.stats.size () > 1) + if (timing && run.metrics.stats.size () > 1 || computeDistortion) { out << ",\n"; out << " \"parts\":\n"; out << " [\n"; - //first print total statistics, then print all part data, unless there's only one part for (size_t part = 0; part < run.metrics.stats.size (); ++part) { out << " {\n"; - out << " \"part\": " << part << ",\n"; - - printPartStats ( - out, - run.metrics.stats[part], - " ", - timing, - partSize, - raw, - stats); + out << " \"part\": " << part; + if (computeDistortion) + { + out << ",\n"; + out << " \"" << "log_mse" + << "\": " << run.metrics.stats[part].distortion; + } + if (timing) + { + out << ",\n"; + printPartStats ( + out, + run.metrics.stats[part], + " ", + timing, + partSize, + raw, + stats); + } out << "\n }"; if (part < run.metrics.stats.size () - 1) { out << ','; } out << endl; @@ -425,7 +440,7 @@ jsonStats ( } void -csvStats (ostream& out, list& data, bool outputSizeData, int timing) +csvStats (ostream& out, list& data, bool outputSizeData, int timing, bool computeDistortion) { out << "file name"; if (outputSizeData) @@ -434,6 +449,7 @@ csvStats (ostream& out, list& data, bool outputSizeData, int timing) } out << ",compression,pixel mode"; if (outputSizeData) { out << ",output size"; } + if (computeDistortion) { out << ",distortion"; } if (timing & options::TIME_READ) { out << ",count read time"; @@ -470,6 +486,20 @@ csvStats (ostream& out, list& data, bool outputSizeData, int timing) out << ',' << compName << ',' << modeName (run.mode); if (outputSizeData) { out << ',' << run.metrics.outputFileSize; } + if (computeDistortion) + out << ','; + for (size_t p = 0; p < run.metrics.stats.size (); ++p) + { + if (p > 0) { out << '|'; } + switch (run.metrics.stats[p].metricKind) { + case LOG_MSE_HALF: + case LOG_MSE_FLOAT: + out << "log_mse:" << run.metrics.stats[p].distortion; + break; + default: + out << "---"; + } + } if (timing & options::TIME_READ) { if (run.metrics.totalStats.sizeData.isDeep) @@ -545,10 +575,13 @@ main (int argc, char** argv) opts.level, opts.passes, opts.outFile || opts.outputSizeData || - opts.timing & options::TIME_WRITE, - opts.timing & options::TIME_REREAD, + opts.timing & options::TIME_WRITE || + opts.computeDistortion, + opts.timing & options::TIME_REREAD || + opts.computeDistortion, mode, - opts.verbose); + opts.verbose, + opts.computeDistortion); data.push_back (d); } } @@ -571,12 +604,12 @@ main (int argc, char** argv) if (opts.csv) { - csvStats (cout, data, opts.outputSizeData, opts.timing); + csvStats (cout, data, opts.outputSizeData, opts.timing, opts.computeDistortion); } else { jsonStats ( - cout, data, opts.outputSizeData, opts.timing,showPartSizeOnDisk, true, true); + cout, data, opts.outputSizeData, opts.timing, showPartSizeOnDisk, true, true, opts.computeDistortion); } } @@ -907,6 +940,11 @@ options::parse (int argc, char* argv[]) outputPartSizeOnDisk = true; i += 1; } + else if (!strcmp (argv[i], "--distortion")) + { + computeDistortion = true; + i += 1; + } else if (!strcmp (argv[i], "-i")) { if (i > argc - 2) diff --git a/src/lib/OpenEXR/ImfCRgbaFile.h b/src/lib/OpenEXR/ImfCRgbaFile.h index 283954a5c4..75e8153275 100644 --- a/src/lib/OpenEXR/ImfCRgbaFile.h +++ b/src/lib/OpenEXR/ImfCRgbaFile.h @@ -87,7 +87,8 @@ typedef struct ImfRgba ImfRgba; #define IMF_DWAB_COMPRESSION 9 #define IMF_HTJ2K256_COMPRESSION 10 #define IMF_HTJ2K32_COMPRESSION 11 -#define IMF_NUM_COMPRESSION_METHODS 12 +#define IMF_HTJ2KL256_COMPRESSION 12 +#define IMF_NUM_COMPRESSION_METHODS 13 /* ** Channels; values must be the same as in Imf::RgbaChannels. diff --git a/src/lib/OpenEXR/ImfCompression.cpp b/src/lib/OpenEXR/ImfCompression.cpp index a3d802bc4b..0b38f4df63 100644 --- a/src/lib/OpenEXR/ImfCompression.cpp +++ b/src/lib/OpenEXR/ImfCompression.cpp @@ -178,16 +178,22 @@ static const CompressionDesc IdToDesc[] = { false), CompressionDesc ( "htj2k256", - "High-Throughput JPEG 2000 (256 lines)", + "High-Throughput JPEG 2000, lossless (256 lines)", 256, false, false), CompressionDesc ( "htj2k32", - "High-Throughput JPEG 2000 (32 lines)", + "High-Throughput JPEG 2000, lossless (32 lines)", 32, false, false), + CompressionDesc ( + "htj2kl256", + "High-Throughput JPEG 2000, lossy (256 lines)", + 256, + true, + false), }; // clang-format on @@ -206,6 +212,7 @@ static const std::map CompressionNameToId = { {"dwab", Compression::DWAB_COMPRESSION}, {"htj2k256", Compression::HTJ2K256_COMPRESSION}, {"htj2k32", Compression::HTJ2K32_COMPRESSION}, + {"htj2kl256", Compression::HTJ2KL256_COMPRESSION}, }; #define UNKNOWN_COMPRESSION_ID_MSG "INVALID COMPRESSION ID" diff --git a/src/lib/OpenEXR/ImfCompression.h b/src/lib/OpenEXR/ImfCompression.h index a25c202a2a..a46f122c01 100644 --- a/src/lib/OpenEXR/ImfCompression.h +++ b/src/lib/OpenEXR/ImfCompression.h @@ -51,9 +51,11 @@ enum IMF_EXPORT_ENUM Compression // wise and faster to decode full frames // than DWAA_COMPRESSION. - HTJ2K256_COMPRESSION = 10, // High-Throughput JPEG2000 (HTJ2K), 256 scanlines + HTJ2K256_COMPRESSION = 10, // High-Throughput JPEG2000 (HTJ2K), lossless, 256 scanlines - HTJ2K32_COMPRESSION = 11, // High-Throughput JPEG2000 (HTJ2K), 32 scanlines + HTJ2K32_COMPRESSION = 11, // High-Throughput JPEG2000 (HTJ2K), lossless, 32 scanlines + + HTJ2KL256_COMPRESSION = 12, // High-Throughput JPEG2000 (HTJ2K), lossy, 256 scanlines NUM_COMPRESSION_METHODS // number of different compression methods }; diff --git a/src/lib/OpenEXR/ImfCompressor.cpp b/src/lib/OpenEXR/ImfCompressor.cpp index 5ba51ed028..99719c45c8 100644 --- a/src/lib/OpenEXR/ImfCompressor.cpp +++ b/src/lib/OpenEXR/ImfCompressor.cpp @@ -54,6 +54,7 @@ Compressor::Compressor ( exr_set_zip_compression_level (_ctxt, 0, hdr.zipCompressionLevel ()); exr_set_dwa_compression_level (_ctxt, 0, hdr.dwaCompressionLevel ()); + exr_set_lossy_htj2k_quality (_ctxt, 0, hdr.lossyHTJ2KQuality ()); exr_compression_t hdrcomp; if (EXR_ERR_SUCCESS != exr_get_compression (_ctxt, 0, &hdrcomp)) @@ -337,6 +338,7 @@ newCompressor (Compression c, size_t maxScanLineSize, const Header& hdr) break; case HTJ2K256_COMPRESSION: + case HTJ2KL256_COMPRESSION: return new HTCompressor (hdr, static_cast (maxScanLineSize), 256); @@ -427,6 +429,7 @@ newTileCompressor ( case HTJ2K256_COMPRESSION: case HTJ2K32_COMPRESSION: + case HTJ2KL256_COMPRESSION: return new HTCompressor ( hdr, diff --git a/src/lib/OpenEXR/ImfContextInit.h b/src/lib/OpenEXR/ImfContextInit.h index ba7abff4c5..ef7d6bfd87 100644 --- a/src/lib/OpenEXR/ImfContextInit.h +++ b/src/lib/OpenEXR/ImfContextInit.h @@ -105,6 +105,12 @@ class IMF_EXPORT_TYPE ContextInitializer return *this; } + ContextInitializer& setLossyHTJ2KQuality (float jq) noexcept + { + _initializer.lossy_htj2k_quality = jq; + return *this; + } + ContextInitializer& strictHeaderValidation (bool onoff) noexcept { setFlag (EXR_CONTEXT_FLAG_STRICT_HEADER, onoff); diff --git a/src/lib/OpenEXR/ImfHeader.cpp b/src/lib/OpenEXR/ImfHeader.cpp index b13f2063b6..4cbf737b11 100644 --- a/src/lib/OpenEXR/ImfHeader.cpp +++ b/src/lib/OpenEXR/ImfHeader.cpp @@ -71,9 +71,11 @@ struct CompressionRecord { exr_get_default_zip_compression_level (&zip_level); exr_get_default_dwa_compression_quality (&dwa_level); + exr_get_default_lossy_htj2k_quality (&lossy_htj2k_quality); } int zip_level; float dwa_level; + float lossy_htj2k_quality; }; // NB: This is extra complicated than one would normally write to // handle scenario that seems to happen on MacOS/Windows (probably @@ -705,6 +707,18 @@ Header::dwaCompressionLevel () const return retrieveCompressionRecord (this).dwa_level; } +float& +Header::lossyHTJ2KQuality () +{ + return retrieveCompressionRecord (this).lossy_htj2k_quality; +} + +float +Header::lossyHTJ2KQuality () const +{ + return retrieveCompressionRecord (this).lossy_htj2k_quality; +} + void Header::setName (const string& name) { diff --git a/src/lib/OpenEXR/ImfHeader.h b/src/lib/OpenEXR/ImfHeader.h index 8bdd586734..0b0a386489 100644 --- a/src/lib/OpenEXR/ImfHeader.h +++ b/src/lib/OpenEXR/ImfHeader.h @@ -286,6 +286,10 @@ class IMF_EXPORT_TYPE Header float& dwaCompressionLevel (); IMF_EXPORT float dwaCompressionLevel () const; + IMF_EXPORT + float& lossyHTJ2KQuality (); + IMF_EXPORT + float lossyHTJ2KQuality () const; //----------------------------------------------------- // Access to required attributes for multipart files diff --git a/src/lib/OpenEXRCore/CMakeLists.txt b/src/lib/OpenEXRCore/CMakeLists.txt index 74f90fc52e..5e89abdc9b 100644 --- a/src/lib/OpenEXRCore/CMakeLists.txt +++ b/src/lib/OpenEXRCore/CMakeLists.txt @@ -134,23 +134,6 @@ if (DEFINED EXR_OPENJPH_LIB) target_link_libraries(OpenEXRCore PUBLIC ${EXR_OPENJPH_LIB}) endif() - if (openjph_VERSION AND openjph_VERSION VERSION_LESS "0.23") - # OpenJPH 0.22 and before incorrectly appends "openjph" to INTERFACE_INCLUDE_DIRECTORIES - # so OpenEXR's "#include " does not work. - # Strip the "openjph" from the setting in this case. This allows the - # #include statements in OpenEXRCore/internal_ht.cpp to work properly for all openjph versions. - get_target_property(OPENJPH_INCLUDE_DIR openjph INTERFACE_INCLUDE_DIRECTORIES) - if (NOT OPENJPH_INCLUDE_DIR) - message(FATAL_ERROR "failed to set openjph header directory, version ${openjph_VERSION}") - endif() - string(REGEX REPLACE "/openjph/?$" "" OPENJPH_PARENT_INCLUDE_DIR "${OPENJPH_INCLUDE_DIR}") - set_target_properties(openjph PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${OPENJPH_PARENT_INCLUDE_DIR}" - ) - unset(OPENJPH_INCLUDE_DIR) - unset(OPENJPH_PARENT_INCLUDE_DIR) - endif() - else() # Vendored OpenJPH (bundled into OpenEXRCore; not installed or exported) diff --git a/src/lib/OpenEXRCore/backward_compatibility.h b/src/lib/OpenEXRCore/backward_compatibility.h index 821bbd5b23..3f5ea50262 100644 --- a/src/lib/OpenEXRCore/backward_compatibility.h +++ b/src/lib/OpenEXRCore/backward_compatibility.h @@ -42,4 +42,25 @@ struct _exr_context_initializer_v2 float dwa_quality; }; +struct _exr_context_initializer_v3 +{ + size_t size; + exr_error_handler_cb_t error_handler_fn; + exr_memory_allocation_func_t alloc_fn; + exr_memory_free_func_t free_fn; + void* user_data; + exr_read_func_ptr_t read_fn; + exr_query_size_func_ptr_t size_fn; + exr_write_func_ptr_t write_fn; + exr_destroy_stream_func_ptr_t destroy_fn; + int max_image_width; + int max_image_height; + int max_tile_width; + int max_tile_height; + int zip_level; + float dwa_quality; + int flags; + float lossy_htj2k_quality; +}; + #endif /* OPENEXR_BACKWARD_COMPATIBILITY_H */ diff --git a/src/lib/OpenEXRCore/base.c b/src/lib/OpenEXRCore/base.c index dda032bf66..4c9d51b12b 100644 --- a/src/lib/OpenEXRCore/base.c +++ b/src/lib/OpenEXRCore/base.c @@ -210,3 +210,21 @@ exr_get_default_dwa_compression_quality (float* q) { if (q) *q = sDefaultDwaLevel; } + +/**************************************/ + +static float sDefaultJ2kQuality = 0.0003f; + +void +exr_set_default_lossy_htj2k_quality (float q) +{ + sDefaultJ2kQuality = q; +} + +/**************************************/ + +void +exr_get_default_lossy_htj2k_quality (float* q) +{ + if (q) *q = sDefaultJ2kQuality; +} diff --git a/src/lib/OpenEXRCore/compression.c b/src/lib/OpenEXRCore/compression.c index d64d071e80..92aa8592cb 100644 --- a/src/lib/OpenEXRCore/compression.c +++ b/src/lib/OpenEXRCore/compression.c @@ -247,8 +247,9 @@ int exr_compression_lines_per_chunk (exr_compression_t comptype) case EXR_COMPRESSION_B44A: case EXR_COMPRESSION_HTJ2K32: case EXR_COMPRESSION_DWAA: linePerChunk = 32; break; - case EXR_COMPRESSION_DWAB: linePerChunk = 256; break; - case EXR_COMPRESSION_HTJ2K256: linePerChunk = 256; break; + case EXR_COMPRESSION_DWAB: + case EXR_COMPRESSION_HTJ2K256: + case EXR_COMPRESSION_HTJ2KL256: linePerChunk = 256; break; case EXR_COMPRESSION_LAST_TYPE: default: /* ERROR CONDITION */ @@ -384,6 +385,7 @@ exr_compress_chunk (exr_encode_pipeline_t* encode) case EXR_COMPRESSION_DWAB: rv = internal_exr_apply_dwab (encode); break; case EXR_COMPRESSION_HTJ2K32: case EXR_COMPRESSION_HTJ2K256: + case EXR_COMPRESSION_HTJ2KL256: rv = internal_exr_apply_ht (encode); break; case EXR_COMPRESSION_LAST_TYPE: default: @@ -463,6 +465,7 @@ decompress_data ( break; case EXR_COMPRESSION_HTJ2K256: case EXR_COMPRESSION_HTJ2K32: + case EXR_COMPRESSION_HTJ2KL256: rv = internal_exr_undo_ht ( decode, packbufptr, packsz, unpackbufptr, unpacksz); break; diff --git a/src/lib/OpenEXRCore/context.c b/src/lib/OpenEXRCore/context.c index ae0e6a563a..8a2a91b4ad 100644 --- a/src/lib/OpenEXRCore/context.c +++ b/src/lib/OpenEXRCore/context.c @@ -134,6 +134,10 @@ fill_context_data (const exr_context_initializer_t* ctxtdata) { inits.flags = ctxtdata->flags; } + if (ctxtdata->size >= sizeof (struct _exr_context_initializer_v4)) + { + inits.lossy_htj2k_quality = ctxtdata->lossy_htj2k_quality; + } } internal_exr_update_default_handlers (&inits); diff --git a/src/lib/OpenEXRCore/internal_ht.cpp b/src/lib/OpenEXRCore/internal_ht.cpp index c4b2ce504c..c242904a3d 100644 --- a/src/lib/OpenEXRCore/internal_ht.cpp +++ b/src/lib/OpenEXRCore/internal_ht.cpp @@ -16,6 +16,7 @@ #include "openexr_decode.h" #include "openexr_encode.h" +#include "openexr_part.h" #include "internal_ht_common.h" /** @@ -297,12 +298,12 @@ ht_undo_impl ( } else { - int32_t* channel_pixels = (int32_t*) line_pixels; + uint32_t* channel_pixels = (uint32_t*) line_pixels; for (int32_t p = 0; p < decode->channels[file_c].width; p++) { - *channel_pixels++ = cur_line->i32[p]; + *channel_pixels++ = (uint32_t) cur_line->i32[p]; } } } @@ -338,12 +339,12 @@ ht_undo_impl ( } else { - int32_t* channel_pixels = - (int32_t*) (line_pixels + cs_to_file_ch[c].raster_line_offset); + uint32_t* channel_pixels = + (uint32_t*) (line_pixels + cs_to_file_ch[c].raster_line_offset); for (int32_t p = 0; p < decode->channels[file_c].width; p++) { - *channel_pixels++ = cur_line->i32[p]; + *channel_pixels++ = (uint32_t) cur_line->i32[p]; } } } @@ -429,13 +430,30 @@ ht_apply_impl (exr_encode_pipeline_t* encode) siz.set_image_offset (ojph::point (0, 0)); siz.set_image_extent (ojph::point (image_width, image_height)); + exr_compression_t comp = EXR_COMPRESSION_HTJ2K256; + exr_get_compression (encode->context, encode->part_index, &comp); + bool lossy = (comp == EXR_COMPRESSION_HTJ2KL256); + ojph::param_cod cod = cs.access_cod (); cod.set_color_transform (isRGB && !isPlanar); - cod.set_reversible (true); + cod.set_reversible (!lossy); cod.set_block_dims (128, 32); cod.set_num_decomposition (5); + if (lossy) + { + float lossy_htj2k_quality = -1.f; + exr_get_lossy_htj2k_quality ( + encode->context, encode->part_index, &lossy_htj2k_quality); + if (lossy_htj2k_quality <= 0.f) { + return EXR_ERR_INVALID_ARGUMENT; + } + + ojph::param_qcd qcd = cs.access_qcd (); + qcd.set_irrev_quant (lossy_htj2k_quality); + } + try { /* write the header */ diff --git a/src/lib/OpenEXRCore/internal_structs.c b/src/lib/OpenEXRCore/internal_structs.c index 291851fec7..98c2d6dcf6 100644 --- a/src/lib/OpenEXRCore/internal_structs.c +++ b/src/lib/OpenEXRCore/internal_structs.c @@ -227,6 +227,7 @@ internal_exr_add_part ( part->zip_compression_level = f->default_zip_level; part->dwa_compression_level = f->default_dwa_quality; + part->lossy_htj2k_quality = f->default_lossy_htj2k_quality; /* put it into the part table */ for (int p = 0; p < f->num_parts; ++p) @@ -369,10 +370,13 @@ internal_exr_alloc_context ( exr_get_default_zip_compression_level (&ret->default_zip_level); exr_get_default_dwa_compression_quality (&ret->default_dwa_quality); + exr_get_default_lossy_htj2k_quality (&ret->default_lossy_htj2k_quality); if (initializers->zip_level >= 0) ret->default_zip_level = initializers->zip_level; if (initializers->dwa_quality >= 0.f) ret->default_dwa_quality = initializers->dwa_quality; + if (initializers->lossy_htj2k_quality > 0.f) + ret->default_lossy_htj2k_quality = initializers->lossy_htj2k_quality; if (initializers->flags & EXR_CONTEXT_FLAG_STRICT_HEADER) ret->strict_header = 1; diff --git a/src/lib/OpenEXRCore/internal_structs.h b/src/lib/OpenEXRCore/internal_structs.h index 8940212b74..b0d2b913c5 100644 --- a/src/lib/OpenEXRCore/internal_structs.h +++ b/src/lib/OpenEXRCore/internal_structs.h @@ -125,6 +125,7 @@ struct _priv_exr_part_t int32_t zip_compression_level; float dwa_compression_level; + float lossy_htj2k_quality; int32_t num_tile_levels_x; int32_t num_tile_levels_y; @@ -207,6 +208,7 @@ struct _priv_exr_context_t int default_zip_level; float default_dwa_quality; + float default_lossy_htj2k_quality; void* real_user_data; void* user_data; diff --git a/src/lib/OpenEXRCore/openexr_attr.h b/src/lib/OpenEXRCore/openexr_attr.h index 8c51f96c43..5de8f5db70 100644 --- a/src/lib/OpenEXRCore/openexr_attr.h +++ b/src/lib/OpenEXRCore/openexr_attr.h @@ -47,6 +47,7 @@ typedef enum EXR_COMPRESSION_DWAB = 9, EXR_COMPRESSION_HTJ2K256 = 10, EXR_COMPRESSION_HTJ2K32 = 11, + EXR_COMPRESSION_HTJ2KL256 = 12, /**< High-Throughput JPEG 2000, lossy, 256 scanlines */ EXR_COMPRESSION_LAST_TYPE /**< Invalid value, provided for range checking. */ } exr_compression_t; diff --git a/src/lib/OpenEXRCore/openexr_base.h b/src/lib/OpenEXRCore/openexr_base.h index 8df304235b..5f9b864b16 100644 --- a/src/lib/OpenEXRCore/openexr_base.h +++ b/src/lib/OpenEXRCore/openexr_base.h @@ -136,6 +136,19 @@ EXR_EXPORT void exr_set_default_dwa_compression_quality (float q); */ EXR_EXPORT void exr_get_default_dwa_compression_quality (float* q); +/** @brief Assigns a default lossy HTJ2K compression quality. + * + * The value is used as the irreversible quantization delta (q-step) value + * + * This value may be controlled separately on each part, but this global control + * determines the initial value. + */ +EXR_EXPORT void exr_set_default_lossy_htj2k_quality (float q); + +/** @brief Retrieve the global default lossy HTJ2K compression quality. + */ +EXR_EXPORT void exr_get_default_lossy_htj2k_quality (float* q); + /** @} */ /** diff --git a/src/lib/OpenEXRCore/openexr_context.h b/src/lib/OpenEXRCore/openexr_context.h index d2dcdf0c87..b17e2640c1 100644 --- a/src/lib/OpenEXRCore/openexr_context.h +++ b/src/lib/OpenEXRCore/openexr_context.h @@ -190,7 +190,7 @@ typedef int64_t (*exr_write_func_ptr_t) ( * \endcode * */ -typedef struct _exr_context_initializer_v3 +typedef struct _exr_context_initializer_v4 { /** @brief Size member to tag initializer for version stability. * @@ -323,7 +323,16 @@ typedef struct _exr_context_initializer_v3 */ int flags; - uint8_t pad[4]; + /** Initialize the default HTJ2K compression quality. + * + * The value is passed directly to the irreversible quantization step via + * ojph::param_qcd::set_irrev_quant() when the compression + * type is HTJ2KL256. + * + * See exr_set_default_lossy_htj2k_quality() to set the default for all + * contexts. + */ + float lossy_htj2k_quality; } exr_context_initializer_t; /** @brief context flag which will enforce strict header validation @@ -353,7 +362,7 @@ typedef struct _exr_context_initializer_v3 /* clang-format off */ /** @brief Simple macro to initialize the context initializer with default values. */ #define EXR_DEFAULT_CONTEXT_INITIALIZER \ - { sizeof (exr_context_initializer_t), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, -1.f, 0, { 0, 0, 0, 0 } } + { sizeof (exr_context_initializer_t), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, -1.f, 0, 0.f } /* clang-format on */ /** @} */ /* context function pointer declarations */ diff --git a/src/lib/OpenEXRCore/openexr_part.h b/src/lib/OpenEXRCore/openexr_part.h index 3df0d73251..62063c98df 100644 --- a/src/lib/OpenEXRCore/openexr_part.h +++ b/src/lib/OpenEXRCore/openexr_part.h @@ -234,6 +234,32 @@ EXR_EXPORT exr_result_t exr_get_dwa_compression_level ( EXR_EXPORT exr_result_t exr_set_dwa_compression_level (exr_context_t ctxt, int part_index, float level); +/** @brief Retrieve the lossy HTJ2K compression quality used for the specified part. + * + * This only applies when the compression method is HTJ2KL256. + * + * The value is used as the irreversible quantization delta. + * + * This value is NOT persisted in the file, and only exists for the + * lifetime of the context, so will be at the default value when just + * reading a file. + */ +EXR_EXPORT exr_result_t exr_get_lossy_htj2k_quality ( + exr_const_context_t ctxt, int part_index, float* level); + +/** @brief Set the lossy HTJ2K compression quality for the specified part. + * + * This only applies when the compression method is HTJ2KL256. + * + * The value is used as the irreversible quantization delta. + * + * This value is NOT persisted in the file, and only exists for the + * lifetime of the context, so this value will be ignored when + * reading a file. + */ +EXR_EXPORT exr_result_t +exr_set_lossy_htj2k_quality (exr_context_t ctxt, int part_index, float level); + /**************************************/ /** @defgroup PartMetadata Functions to get and set metadata for a particular part. diff --git a/src/lib/OpenEXRCore/part.c b/src/lib/OpenEXRCore/part.c index 6b1ca6cbcd..02e4b98382 100644 --- a/src/lib/OpenEXRCore/part.c +++ b/src/lib/OpenEXRCore/part.c @@ -9,6 +9,7 @@ #include "internal_constants.h" #include "internal_structs.h" +#include #include /**************************************/ @@ -627,3 +628,43 @@ exr_set_dwa_compression_level (exr_context_t ctxt, int part_index, float level) return EXR_UNLOCK_AND_RETURN (rv); } + +/**************************************/ + +exr_result_t +exr_get_lossy_htj2k_quality ( + exr_const_context_t ctxt, int part_index, float* level) +{ + float l; + EXR_LOCK_WRITE_AND_DEFINE_PART (part_index); + l = part->lossy_htj2k_quality; + if (ctxt->mode == EXR_CONTEXT_WRITE) internal_exr_unlock (ctxt); + + if (!level) return ctxt->standard_error (ctxt, EXR_ERR_INVALID_ARGUMENT); + *level = l; + return EXR_ERR_SUCCESS; +} + +/**************************************/ + +exr_result_t +exr_set_lossy_htj2k_quality (exr_context_t ctxt, int part_index, float level) +{ + exr_result_t rv; + EXR_LOCK_AND_DEFINE_PART (part_index); + + if (ctxt->mode != EXR_CONTEXT_WRITE && ctxt->mode != EXR_CONTEXT_TEMPORARY) + return EXR_UNLOCK_AND_RETURN ( + ctxt->standard_error (ctxt, EXR_ERR_NOT_OPEN_WRITE)); + + if (!isfinite (level)) + return EXR_UNLOCK_AND_RETURN (ctxt->report_error ( + ctxt, + EXR_ERR_INVALID_ARGUMENT, + "Invalid j2k quality level specified")); + + part->lossy_htj2k_quality = level; + rv = EXR_ERR_SUCCESS; + + return EXR_UNLOCK_AND_RETURN (rv); +} diff --git a/src/test/OpenEXRTest/CMakeLists.txt b/src/test/OpenEXRTest/CMakeLists.txt index 7e70111e43..c211a48cfd 100644 --- a/src/test/OpenEXRTest/CMakeLists.txt +++ b/src/test/OpenEXRTest/CMakeLists.txt @@ -8,6 +8,8 @@ add_executable(OpenEXRTest compareB44.h compareDwa.cpp compareDwa.h + compareHTJ2KL256.cpp + compareHTJ2KL256.h compareFloat.cpp compareFloat.h main.cpp diff --git a/src/test/OpenEXRTest/compareHTJ2KL256.cpp b/src/test/OpenEXRTest/compareHTJ2KL256.cpp new file mode 100644 index 0000000000..c23b332663 --- /dev/null +++ b/src/test/OpenEXRTest/compareHTJ2KL256.cpp @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) Contributors to the OpenEXR Project. + +#ifdef NDEBUG +# undef NDEBUG +#endif + +#include +#include + +#include "compareHTJ2KL256.h" +#include "half.h" +#include + +using namespace OPENEXR_IMF_NAMESPACE; +using namespace std; + +bool +checkHTJ2KSample (double src, double tst) +{ + if (isnan (src) || isnan (tst)) + { + return true; + } + if (fabs (src) < 1e-5) + { + if (fabs (src - tst) > 1e-4) + { + return false; + } + } + else if (fabs ((src - tst) / src) > 0.5) + { + return false; + } + + return true; +} + +bool +checkHTJ2KSample (unsigned int src, unsigned int tst) +{ + int diff = src < tst ? tst - src : src - tst; + + if (src < 2000000) { + if (diff > 2000000) + { + return false; + } + } else { + if ((double) diff / src > 0.5) + { + return false; + } + } + + return true; +} + +bool +checkHTJ2KSample (half src, half tst) +{ + if ((src.bits () & 0x7c00) == 0x7c00 || + (tst.bits () & 0x7c00) == 0x7c00) + return true; + bool g = checkHTJ2KSample ((double) src, (double) tst); + if (!g) { + std::cerr << "src=" << src << ", tst=" << tst << std::endl; + } + return g; +} + +bool +checkHTJ2KSample (ImfHalf src, ImfHalf tst) +{ + half s, t; + s.setBits (src); + t.setBits (tst); + return checkHTJ2KSample (s, t); +} diff --git a/src/test/OpenEXRTest/compareHTJ2KL256.h b/src/test/OpenEXRTest/compareHTJ2KL256.h new file mode 100644 index 0000000000..19842e8f0b --- /dev/null +++ b/src/test/OpenEXRTest/compareHTJ2KL256.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) Contributors to the OpenEXR Project. + +#ifndef COMPARE_HTJ2KL256_H_INCLUDED +#define COMPARE_HTJ2KL256_H_INCLUDED + +#include "ImfArray.h" +#include "ImfNamespace.h" +#include "ImfRgba.h" +#include "half.h" +#include + +bool checkHTJ2KSample (double src, double tst); +bool checkHTJ2KSample (unsigned int src, unsigned int tst); +bool checkHTJ2KSample (half src, half tst); +bool checkHTJ2KSample (ImfHalf src, ImfHalf tst); + +#endif diff --git a/src/test/OpenEXRTest/testCRgba.cpp b/src/test/OpenEXRTest/testCRgba.cpp index 1bfc3534e6..9b1c78ce02 100644 --- a/src/test/OpenEXRTest/testCRgba.cpp +++ b/src/test/OpenEXRTest/testCRgba.cpp @@ -9,6 +9,7 @@ #include "compareB44.h" #include "compareDwa.h" +#include "compareHTJ2KL256.h" #include "IlmThread.h" #include "ImfArray.h" @@ -188,27 +189,40 @@ writeReadCRGBA ( } else { + int comp = ImfHeaderCompression (inputFileHeaderPtr); for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { if (channels & IMF_WRITE_R) - assert (p2[y][x].r == p1[y][x].r); + if (comp == IMF_HTJ2KL256_COMPRESSION) + assert (checkHTJ2KSample(p2[y][x].r, p1[y][x].r)); + else + assert (p2[y][x].r == p1[y][x].r); else assert (ImfHalfToFloat(p2[y][x].r) == 0.0); if (channels & IMF_WRITE_G) - assert (p2[y][x].g == p1[y][x].g); + if (comp == IMF_HTJ2KL256_COMPRESSION) + assert (checkHTJ2KSample(p2[y][x].g, p1[y][x].g)); + else + assert (p2[y][x].g == p1[y][x].g); else assert (ImfHalfToFloat(p2[y][x].g) == 0.0); if (channels & IMF_WRITE_B) - assert (p2[y][x].b == p1[y][x].b); + if (comp == IMF_HTJ2KL256_COMPRESSION) + assert (checkHTJ2KSample(p2[y][x].b, p1[y][x].b)); + else + assert (p2[y][x].b == p1[y][x].b); else assert (ImfHalfToFloat(p2[y][x].b) == 0.0); if (channels & IMF_WRITE_A) - assert (p2[y][x].a == p1[y][x].a); + if (comp == IMF_HTJ2KL256_COMPRESSION) + assert (checkHTJ2KSample(p2[y][x].a, p1[y][x].a)); + else + assert (p2[y][x].a == p1[y][x].a); else assert (ImfHalfToFloat(p2[y][x].a) == 1.0); } diff --git a/src/test/OpenEXRTest/testCompression.cpp b/src/test/OpenEXRTest/testCompression.cpp index 26daf20b28..d53a128ce3 100644 --- a/src/test/OpenEXRTest/testCompression.cpp +++ b/src/test/OpenEXRTest/testCompression.cpp @@ -8,6 +8,7 @@ #endif #include "compareB44.h" +#include "compareHTJ2KL256.h" #include "compareFloat.h" #include "ImfArray.h" @@ -409,16 +410,38 @@ writeRead ( assert (ii == in.header ().channels ().end ()); - for (int y = 0; y < h / ys; ++y) + // uint32 and float samples + if (comp != HTJ2KL256_COMPRESSION) { - for (int x = 0; x < w / xs; ++x) + for (int y = 0; y < h / ys; ++y) { - assert (array1.i[y][x] == array2.i[y][x]); - assert (equivalent (array1.f[y][x], array2.f[y][x], comp)); + for (int x = 0; x < w / xs; ++x) + { + assert (array1.i[y][x] == array2.i[y][x]); + assert (equivalent (array1.f[y][x], array2.f[y][x], comp)); + } + } + } - if (!isLossyCompression (comp)) + // single channel half samples + if (comp != HTJ2KL256_COMPRESSION && comp != B44_COMPRESSION && comp != B44A_COMPRESSION) + { + for (int y = 0; y < h / ys; ++y) + { + for (int x = 0; x < w / xs; ++x) { assert (array1.h[y][x].bits () == array2.h[y][x].bits ()); + } + } + } + + // rgba half samples + if (!isLossyCompression (comp)) + { + for (int y = 0; y < h / ys; ++y) + { + for (int x = 0; x < w / xs; ++x) + { for (int c = 0; c < 4; ++c) { assert ( @@ -448,12 +471,8 @@ writeRead ( compareB44 (w / xs, h / ys, ph3, array2.rgba[c]); } } - if (comp == DWAA_COMPRESSION || comp == DWAB_COMPRESSION) + else if (comp == DWAA_COMPRESSION || comp == DWAB_COMPRESSION) { - for (int y = 0; y < h / ys; ++y) - for (int x = 0; x < w / xs; ++x) - assert (array1.h[y][x].bits () == array2.h[y][x].bits ()); - for (int c = 0; c < 4; ++c) { for (int y = 0; y < h / ys; ++y) @@ -481,6 +500,24 @@ writeRead ( } } } + else if (comp == HTJ2KL256_COMPRESSION) + { + for (int y = 0; y < h / ys; ++y) + { + for (int x = 0; x < w / xs; ++x) + { + assert (checkHTJ2KSample (array1.i[y][x], array2.i[y][x])); + assert (checkHTJ2KSample (array1.f[y][x], array2.f[y][x])); + assert (checkHTJ2KSample (array1.h[y][x], array2.h[y][x])); + + for (int c = 0; c < 4; ++c) + { + assert (checkHTJ2KSample (array1.rgba[c][y][x], + array2.rgba[c][y][x])); + } + } + } + } } remove (fileName); diff --git a/src/test/OpenEXRTest/testCompressionApi.cpp b/src/test/OpenEXRTest/testCompressionApi.cpp index dde7ad7e3a..23a203ac8e 100644 --- a/src/test/OpenEXRTest/testCompressionApi.cpp +++ b/src/test/OpenEXRTest/testCompressionApi.cpp @@ -32,11 +32,11 @@ testCompressionApi (const string& tempDir) cout << "Testing compression API functions." << endl; // update this if you add a new compressor. - string codecList = "none/rle/zips/zip/piz/pxr24/b44/b44a/dwaa/dwab/htj2k256/htj2k32"; + string codecList = "none/rle/zips/zip/piz/pxr24/b44/b44a/dwaa/dwab/htj2k256/htj2k32/htj2kl256"; int numMethods = static_cast (NUM_COMPRESSION_METHODS); // update this if you add a new compressor. - assert (numMethods == 12); + assert (numMethods == 13); for (int i = 0; i < numMethods; i++) { @@ -114,8 +114,9 @@ testCompressionApi (const string& tempDir) {B44A_COMPRESSION, EXR_COMPRESSION_B44A, 32, true}, {DWAA_COMPRESSION, EXR_COMPRESSION_LAST_TYPE, 32, true}, {DWAB_COMPRESSION, EXR_COMPRESSION_LAST_TYPE, 256, true}, - {HTJ2K256_COMPRESSION, EXR_COMPRESSION_LAST_TYPE, 256, true}, - {HTJ2K32_COMPRESSION, EXR_COMPRESSION_LAST_TYPE, 32, true}, + {HTJ2K256_COMPRESSION, EXR_COMPRESSION_LAST_TYPE, 256, true}, + {HTJ2K32_COMPRESSION, EXR_COMPRESSION_LAST_TYPE, 32, true}, + {HTJ2KL256_COMPRESSION, EXR_COMPRESSION_LAST_TYPE, 256, true}, }; const size_t maxScanLineSize = 1024; diff --git a/src/test/OpenEXRTest/testConversion.cpp b/src/test/OpenEXRTest/testConversion.cpp index 31c728c947..92f07e38f3 100644 --- a/src/test/OpenEXRTest/testConversion.cpp +++ b/src/test/OpenEXRTest/testConversion.cpp @@ -336,7 +336,7 @@ testConversion (const std::string& tempDir) for (int comp = 0; comp < NUM_COMPRESSION_METHODS; ++comp) { - if (comp == B44_COMPRESSION || comp == B44A_COMPRESSION) + if (comp == B44_COMPRESSION || comp == B44A_COMPRESSION || comp == HTJ2KL256_COMPRESSION) { continue; } diff --git a/src/test/OpenEXRTest/testRgba.cpp b/src/test/OpenEXRTest/testRgba.cpp index baf89320d9..0d442868d4 100644 --- a/src/test/OpenEXRTest/testRgba.cpp +++ b/src/test/OpenEXRTest/testRgba.cpp @@ -9,6 +9,7 @@ #include "compareB44.h" #include "compareDwa.h" +#include "compareHTJ2KL256.h" #include "IlmThread.h" #include "ImfArray.h" @@ -152,27 +153,41 @@ writeReadRGBA ( } else { + int comp = in.compression (); + for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { if (channels & WRITE_R) - assert (p2[y][x].r == p1[y][x].r); + if (comp == HTJ2KL256_COMPRESSION) + assert (checkHTJ2KSample(p2[y][x].r, p1[y][x].r)); + else + assert (p2[y][x].r == p1[y][x].r); else assert (p2[y][x].r == 0); if (channels & WRITE_G) - assert (p2[y][x].g == p1[y][x].g); + if (comp == HTJ2KL256_COMPRESSION) + assert (checkHTJ2KSample(p2[y][x].g, p1[y][x].g)); + else + assert (p2[y][x].g == p1[y][x].g); else assert (p2[y][x].g == 0); if (channels & WRITE_B) - assert (p2[y][x].b == p1[y][x].b); + if (comp == HTJ2KL256_COMPRESSION) + assert (checkHTJ2KSample(p2[y][x].b, p1[y][x].b)); + else + assert (p2[y][x].b == p1[y][x].b); else assert (p2[y][x].b == 0); if (channels & WRITE_A) - assert (p2[y][x].a == p1[y][x].a); + if (comp == HTJ2KL256_COMPRESSION) + assert (checkHTJ2KSample(p2[y][x].a, p1[y][x].a)); + else + assert (p2[y][x].a == p1[y][x].a); else assert (p2[y][x].a == 1); } diff --git a/src/test/OpenEXRTest/testRgbaThreading.cpp b/src/test/OpenEXRTest/testRgbaThreading.cpp index 12377cbf28..c8b1558165 100644 --- a/src/test/OpenEXRTest/testRgbaThreading.cpp +++ b/src/test/OpenEXRTest/testRgbaThreading.cpp @@ -9,6 +9,7 @@ #include "compareB44.h" #include "compareDwa.h" +#include "compareHTJ2KL256.h" #include "IlmThread.h" #include "ImfArray.h" @@ -128,27 +129,40 @@ writeReadRGBA ( } else { + int comp = in.compression (); for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { if (channels & WRITE_R) - assert (p2[y][x].r == p1[y][x].r); + if (comp == HTJ2KL256_COMPRESSION) + assert (checkHTJ2KSample(p2[y][x].r, p1[y][x].r)); + else + assert (p2[y][x].r == p1[y][x].r); else assert (p2[y][x].r == 0); if (channels & WRITE_G) - assert (p2[y][x].g == p1[y][x].g); + if (comp == HTJ2KL256_COMPRESSION) + assert (checkHTJ2KSample(p2[y][x].g, p1[y][x].g)); + else + assert (p2[y][x].g == p1[y][x].g); else assert (p2[y][x].g == 0); if (channels & WRITE_B) - assert (p2[y][x].b == p1[y][x].b); + if (comp == HTJ2KL256_COMPRESSION) + assert (checkHTJ2KSample(p2[y][x].b, p1[y][x].b)); + else + assert (p2[y][x].b == p1[y][x].b); else assert (p2[y][x].b == 0); if (channels & WRITE_A) - assert (p2[y][x].a == p1[y][x].a); + if (comp == HTJ2KL256_COMPRESSION) + assert (checkHTJ2KSample(p2[y][x].a, p1[y][x].a)); + else + assert (p2[y][x].a == p1[y][x].a); else assert (p2[y][x].a == 1); } diff --git a/src/test/OpenEXRTest/testSharedFrameBuffer.cpp b/src/test/OpenEXRTest/testSharedFrameBuffer.cpp index 7728157fab..ad4390af58 100644 --- a/src/test/OpenEXRTest/testSharedFrameBuffer.cpp +++ b/src/test/OpenEXRTest/testSharedFrameBuffer.cpp @@ -9,6 +9,7 @@ #include "compareB44.h" #include "compareDwa.h" +#include "compareHTJ2KL256.h" #include "IlmThread.h" #include "IlmThreadSemaphore.h" @@ -218,27 +219,40 @@ writeReadRGBA ( } else { + int comp = in.compression (); for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { if (channels & WRITE_R) - assert (p2[y][x].r == p1[y][x].r); + if (comp == HTJ2KL256_COMPRESSION) + assert (checkHTJ2KSample(p2[y][x].r, p1[y][x].r)); + else + assert (p2[y][x].r == p1[y][x].r); else assert (p2[y][x].r == 0); if (channels & WRITE_G) - assert (p2[y][x].g == p1[y][x].g); + if (comp == HTJ2KL256_COMPRESSION) + assert (checkHTJ2KSample(p2[y][x].g, p1[y][x].g)); + else + assert (p2[y][x].g == p1[y][x].g); else assert (p2[y][x].g == 0); if (channels & WRITE_B) - assert (p2[y][x].b == p1[y][x].b); + if (comp == HTJ2KL256_COMPRESSION) + assert (checkHTJ2KSample(p2[y][x].b, p1[y][x].b)); + else + assert (p2[y][x].b == p1[y][x].b); else assert (p2[y][x].b == 0); if (channels & WRITE_A) - assert (p2[y][x].a == p1[y][x].a); + if (comp == HTJ2KL256_COMPRESSION) + assert (checkHTJ2KSample(p2[y][x].a, p1[y][x].a)); + else + assert (p2[y][x].a == p1[y][x].a); else assert (p2[y][x].a == 1); } diff --git a/src/test/OpenEXRTest/testTiledCompression.cpp b/src/test/OpenEXRTest/testTiledCompression.cpp index 8ff873148f..48638b6d5a 100644 --- a/src/test/OpenEXRTest/testTiledCompression.cpp +++ b/src/test/OpenEXRTest/testTiledCompression.cpp @@ -9,6 +9,7 @@ #include "compareB44.h" #include "compareDwa.h" +#include "compareHTJ2KL256.h" #include "compareFloat.h" #include "ImfArray.h" @@ -328,19 +329,25 @@ writeRead ( { for (int x = 0; x < w; ++x) { - assert (pi1[y][x] == pi2[y][x]); - - if (comp != B44_COMPRESSION && comp != B44A_COMPRESSION && - comp != DWAA_COMPRESSION && comp != DWAB_COMPRESSION) + if (comp == HTJ2KL256_COMPRESSION) { - assert (ph1[y][x] == ph2[y][x]); - } + assert (checkHTJ2KSample (ph1[y][x], ph2[y][x])); + assert (checkHTJ2KSample (pi1[y][x], pi2[y][x])); + assert (checkHTJ2KSample (pf1[y][x], pf2[y][x])); + } else { + assert (pi1[y][x] == pi2[y][x]); + + if (comp != B44_COMPRESSION && comp != B44A_COMPRESSION && + comp != DWAA_COMPRESSION && comp != DWAB_COMPRESSION) + { + assert (ph1[y][x] == ph2[y][x]); + } - assert (equivalent (pf1[y][x], pf2[y][x], comp)); + assert (equivalent (pf1[y][x], pf2[y][x], comp)); + } } } } - { cout << ", reading and comparing (tile-by-tile)" << flush; @@ -436,18 +443,26 @@ writeRead ( x < xSize && x2 <= win.max.x; ++x, x2++) { - assert (pi1[oY + y][oX + x] == pi2[y][x]); - - if (comp != B44_COMPRESSION && - comp != B44A_COMPRESSION && - comp != DWAA_COMPRESSION && - comp != DWAB_COMPRESSION) + if (comp == HTJ2KL256_COMPRESSION) { - assert (ph1[oY + y][oX + x] == ph2[y][x]); + assert (checkHTJ2KSample (ph1[oY + y][oX + x], ph2[y][x])); + assert (checkHTJ2KSample (pi1[oY + y][oX + x], pi2[y][x])); + assert (checkHTJ2KSample (pf1[oY + y][oX + x], pf2[y][x])); + } else { + assert (pi1[oY + y][oX + x] == pi2[y][x]); + + if (comp != B44_COMPRESSION && + comp != B44A_COMPRESSION && + comp != DWAA_COMPRESSION && + comp != DWAB_COMPRESSION && + comp != HTJ2KL256_COMPRESSION) + { + assert (ph1[oY + y][oX + x] == ph2[y][x]); + } + + assert ( + equivalent (pf1[oY + y][oX + x], pf2[y][x], comp)); } - - assert ( - equivalent (pf1[oY + y][oX + x], pf2[y][x], comp)); } } } diff --git a/src/test/OpenEXRTest/testTiledCopyPixels.cpp b/src/test/OpenEXRTest/testTiledCopyPixels.cpp index 673677481b..6b53ae4471 100644 --- a/src/test/OpenEXRTest/testTiledCopyPixels.cpp +++ b/src/test/OpenEXRTest/testTiledCopyPixels.cpp @@ -7,6 +7,8 @@ # undef NDEBUG #endif +#include "compareHTJ2KL256.h" + #include "ImfArray.h" #include "ImfChannelList.h" #include "ImfFrameBuffer.h" @@ -311,10 +313,16 @@ writeCopyReadMIP ( for (int y = 0; y < in1.levelHeight (l); ++y) for (int x = 0; x < in1.levelWidth (l); ++x) { - assert ((levels2[l])[y][x] == (levels1[l])[y][x]); + if (comp == HTJ2KL256_COMPRESSION) + { + assert (checkHTJ2KSample ((levels2[l])[y][x], (levels1[l])[y][x])); + assert (checkHTJ2KSample ((levels2[l])[y][x], (levels[l])[y][x])); + } else { + assert ((levels2[l])[y][x] == (levels1[l])[y][x]); - if (comp != B44_COMPRESSION && comp != B44A_COMPRESSION) - assert ((levels2[l])[y][x] == (levels[l])[y][x]); + if (comp != B44_COMPRESSION && comp != B44A_COMPRESSION) + assert ((levels2[l])[y][x] == (levels[l])[y][x]); + } } } @@ -486,13 +494,20 @@ writeCopyReadRIP ( for (int y = 0; y < in1.levelHeight (ly); ++y) for (int x = 0; x < in1.levelWidth (lx); ++x) { - assert ( - (levels2[ly][lx])[y][x] == (levels1[ly][lx])[y][x]); + if (comp == HTJ2KL256_COMPRESSION) + { + assert (checkHTJ2KSample ((levels2[ly][lx])[y][x], (levels1[ly][lx])[y][x])); + assert (checkHTJ2KSample ((levels2[ly][lx])[y][x], (levels[ly][lx])[y][x])); + } else { - if (comp != B44_COMPRESSION && comp != B44A_COMPRESSION) assert ( - (levels2[ly][lx])[y][x] == - (levels[ly][lx])[y][x]); + (levels2[ly][lx])[y][x] == (levels1[ly][lx])[y][x]); + + if (comp != B44_COMPRESSION && comp != B44A_COMPRESSION) + assert ( + (levels2[ly][lx])[y][x] == + (levels[ly][lx])[y][x]); + } } } diff --git a/src/test/OpenEXRTest/testTiledLineOrder.cpp b/src/test/OpenEXRTest/testTiledLineOrder.cpp index 82f8dc5193..b0841bb205 100644 --- a/src/test/OpenEXRTest/testTiledLineOrder.cpp +++ b/src/test/OpenEXRTest/testTiledLineOrder.cpp @@ -7,6 +7,8 @@ # undef NDEBUG #endif +#include "compareHTJ2KL256.h" + #include "IlmThread.h" #include "ImfArray.h" #include "ImfChannelList.h" @@ -193,7 +195,12 @@ writeCopyReadONE ( for (int y = 0; y < h; ++y) for (int x = 0; x < w; ++x) - assert (ph1[y][x] == ph2[y][x]); + if (comp == HTJ2KL256_COMPRESSION) + { + assert (checkHTJ2KSample (ph1[y][x], ph2[y][x])); + } else { + assert (ph1[y][x] == ph2[y][x]); + } } remove (fileName); @@ -390,7 +397,12 @@ writeCopyReadMIP ( for (int l = 0; l < numLevels; ++l) for (int y = 0; y < in.levelHeight (l); ++y) for (int x = 0; x < in.levelWidth (l); ++x) - assert ((levels2[l])[y][x] == (levels[l])[y][x]); + if (comp == HTJ2KL256_COMPRESSION) + { + assert (checkHTJ2KSample ((levels2[l])[y][x], (levels[l])[y][x])); + } else { + assert ((levels2[l])[y][x] == (levels[l])[y][x]); + } } remove (fileName); @@ -612,8 +624,13 @@ writeCopyReadRIP ( for (int lx = 0; lx < numXLevels; ++lx) for (int y = 0; y < in.levelHeight (ly); ++y) for (int x = 0; x < in.levelWidth (lx); ++x) - assert ( - (levels2[ly][lx])[y][x] == (levels[ly][lx])[y][x]); + if (comp == HTJ2KL256_COMPRESSION) + { + assert (checkHTJ2KSample ((levels2[ly][lx])[y][x], (levels[ly][lx])[y][x])); + } else { + assert ((levels2[ly][lx])[y][x] == (levels[ly][lx])[y][x]); + } + } remove (fileName); diff --git a/src/test/OpenEXRTest/testTiledRgba.cpp b/src/test/OpenEXRTest/testTiledRgba.cpp index 8f1c23620b..5cc7b24ef8 100644 --- a/src/test/OpenEXRTest/testTiledRgba.cpp +++ b/src/test/OpenEXRTest/testTiledRgba.cpp @@ -9,6 +9,7 @@ #include "compareB44.h" #include "compareDwa.h" +#include "compareHTJ2KL256.h" #include "IlmThread.h" #include "ImfArray.h" @@ -151,22 +152,34 @@ writeReadRGBAONE ( for (int x = 0; x < w; ++x) { if (channels & WRITE_R) - assert (p2[y][x].r == p1[y][x].r); + if (comp == HTJ2KL256_COMPRESSION) + assert (checkHTJ2KSample(p2[y][x].r, p1[y][x].r)); + else + assert (p2[y][x].r == p1[y][x].r); else assert (p2[y][x].r == 0); if (channels & WRITE_G) - assert (p2[y][x].g == p1[y][x].g); + if (comp == HTJ2KL256_COMPRESSION) + assert (checkHTJ2KSample(p2[y][x].g, p1[y][x].g)); + else + assert (p2[y][x].g == p1[y][x].g); else assert (p2[y][x].g == 0); if (channels & WRITE_B) - assert (p2[y][x].b == p1[y][x].b); + if (comp == HTJ2KL256_COMPRESSION) + assert (checkHTJ2KSample(p2[y][x].b, p1[y][x].b)); + else + assert (p2[y][x].b == p1[y][x].b); else assert (p2[y][x].b == 0); if (channels & WRITE_A) - assert (p2[y][x].a == p1[y][x].a); + if (comp == HTJ2KL256_COMPRESSION) + assert (checkHTJ2KSample(p2[y][x].a, p1[y][x].a)); + else + assert (p2[y][x].a == p1[y][x].a); else assert (p2[y][x].a == 1); } @@ -452,12 +465,13 @@ writeRead ( writeReadRGBAONE (filename.c_str (), W, H, WRITE_RGBA, comp, xSize, ySize); if (comp != B44_COMPRESSION && comp != B44A_COMPRESSION && - comp != DWAA_COMPRESSION && comp != DWAB_COMPRESSION) + comp != DWAA_COMPRESSION && comp != DWAB_COMPRESSION && + comp != HTJ2KL256_COMPRESSION) { // - // Skip mipmaps and ripmaps with B44 or DWA compression; writing - // an image with a single resolution level, above, should be enough - // to verify that B44 and DWA compression work with tiled files. + // Skip mipmaps and ripmaps with B44, DWA, or HTJ2KL256 compression; + // writing an image with a single resolution level, above, should be + // enough to verify that these compression types work with tiled files. // writeReadRGBAMIP ( diff --git a/src/wrappers/python/Imath.py b/src/wrappers/python/Imath.py index 0c9cf11da1..c84a8733ca 100644 --- a/src/wrappers/python/Imath.py +++ b/src/wrappers/python/Imath.py @@ -96,9 +96,11 @@ class Compression(Enumerated): DWAB_COMPRESSION = 9 HTJ2K256_COMPRESSION = 10 HTJ2K32_COMPRESSION = 11 + HTJ2KL256_COMPRESSION = 12 names = [ "NO_COMPRESSION", "RLE_COMPRESSION", "ZIPS_COMPRESSION", "ZIP_COMPRESSION", "PIZ_COMPRESSION", "PXR24_COMPRESSION", - "B44_COMPRESSION", "B44A_COMPRESSION", "DWAA_COMPRESSION", "DWAB_COMPRESSION", "HTJ2K256_COMPRESSION", "HTJ2K32_COMPRESSION" + "B44_COMPRESSION", "B44A_COMPRESSION", "DWAA_COMPRESSION", "DWAB_COMPRESSION", "HTJ2K256_COMPRESSION", "HTJ2K32_COMPRESSION", + "HTJ2KL256_COMPRESSION" ] class PixelType(Enumerated): diff --git a/src/wrappers/python/PyOpenEXR.cpp b/src/wrappers/python/PyOpenEXR.cpp index 934ff3d91b..add43ec8c7 100644 --- a/src/wrappers/python/PyOpenEXR.cpp +++ b/src/wrappers/python/PyOpenEXR.cpp @@ -2903,6 +2903,7 @@ PYBIND11_MODULE(OpenEXR, m) .value("DWAB_COMPRESSION", DWAB_COMPRESSION) .value("HTJ2K256_COMPRESSION", HTJ2K256_COMPRESSION) .value("HTJ2K32_COMPRESSION", HTJ2K32_COMPRESSION) + .value("HTJ2KL256_COMPRESSION", HTJ2KL256_COMPRESSION) .value("NUM_COMPRESSION_METHODS", NUM_COMPRESSION_METHODS) .export_values(); @@ -3383,7 +3384,8 @@ PYBIND11_MODULE(OpenEXR, m) " DWAA_COMPRESSION\n" " DWAB_COMPRESSION\n" " HTJ2K256_COMPRESSION\n" - " HTJ2K32_COMPRESSION") + " HTJ2K32_COMPRESSION\n" + " HTJ2KL256_COMPRESSION") .def_readwrite("header", &PyPart::header, "dict : The header metadata.") .def_readwrite("channels", &PyPart::channels,