diff --git a/src/jpeg2000.imageio/jpeg2000output.cpp b/src/jpeg2000.imageio/jpeg2000output.cpp index c8650904f6..06c3b687b5 100644 --- a/src/jpeg2000.imageio/jpeg2000output.cpp +++ b/src/jpeg2000.imageio/jpeg2000output.cpp @@ -66,9 +66,7 @@ class Jpeg2000Output final : public ImageOutput { bool m_convert_alpha; //< Do we deassociate alpha? std::vector m_tilebuffer; std::vector m_scratch; -#if 0 std::vector m_icc_profile; -#endif #ifdef USE_OPENJPH @@ -84,9 +82,7 @@ class Jpeg2000Output final : public ImageOutput { m_codec = NULL; m_stream = NULL; m_convert_alpha = true; -#if 0 m_icc_profile.clear(); -#endif ioproxy_clear(); } @@ -377,6 +373,13 @@ Jpeg2000Output::close() #endif if (m_image) { + // opj_image_destroy() calls free() on icc_profile_buf, but that memory + // is owned by m_icc_profile. Clear the OpenJPEG side's pointer first so + // it does not free memory it doesn't own (to prevent double-free). + if (m_image->icc_profile_buf) { + m_image->icc_profile_buf = NULL; + m_image->icc_profile_len = 0; + } opj_image_destroy(m_image); m_image = NULL; } @@ -477,22 +480,16 @@ Jpeg2000Output::create_jpeg2000_image() = m_compression_parameters.image_offset_y0 + (m_spec.height - 1) * m_compression_parameters.subsampling_dy + 1; -#if 0 - // FIXME: I seem to get crashes with OpenJpeg 2.x in the presence of ICC - // profiles. I have no idea why. It seems like losing the ability to - // write ICC profiles is the lesser evil compared to either restricting - // ourselves to OpenJpeg 1.5 or living with crashes. I'm at the limit of - // my knowledge of OpenJPEG, which frankly has a poor API and abysmal - // documentation. So I'll leave the repair of this for later. If - // someboody comes along that desperately needs JPEG2000 and ICC - // profiles, maybe they will be motivated enough to track down the - // problem. +#if OIIO_OPJ_VERSION >= 20504 + // OpenJPEG versions before 2.5.4 fail to write the ICC profile + // (assertion in opj_jp2_write_colr). + // // NOTE: openjpeg does not copy the profile and m_image outlives this // function, so the bytes are kept in a member rather than a local. m_icc_profile = get_colorspace_icc_profile(m_spec); if (m_icc_profile.size()) { - m_image->icc_profile_len - = decltype(m_image->icc_profile_len)(m_icc_profile.size()); + m_image->icc_profile_len = decltype(m_image->icc_profile_len)( + m_icc_profile.size()); m_image->icc_profile_buf = (unsigned char*)m_icc_profile.data(); } #endif diff --git a/testsuite/jpeg2000/ref/out-icc.txt b/testsuite/jpeg2000/ref/out-icc.txt new file mode 100644 index 0000000000..a65442b281 --- /dev/null +++ b/testsuite/jpeg2000/ref/out-icc.txt @@ -0,0 +1,26 @@ +iconvert ERROR: Tile part length size inconsistent with stream length +Failed to decode the codestream in the JP2 file +Comparing "../oiio-images/jpeg2000/broken/issue_3427.jp2" and "issue_3427.jp2" +idiff ERROR: Could not read ../oiio-images/jpeg2000/broken/issue_3427.jp2: + Invalid image file "../oiio-images/jpeg2000/broken/issue_3427.jp2": Tile part length size inconsistent with stream length +Failed to decode the codestream in the JP2 file +Invalid image file "../oiio-images/jpeg2000/broken/issue_3427.jp2": Tile part length size inconsistent with stream length +Failed to decode the codestream in the JP2 file +ICCProfile of tahoe-icc.jp2 + ICCProfile: 0, 0, 2, 48, 65, 68, 66, 69, 2, 16, 0, 0, 109, 110, 116, 114, ... [560 x uint8] + ICCProfile:attributes: "Reflective, Glossy, Positive, Color" + ICCProfile:cmm_type: 1094992453 + ICCProfile:color_space: "RGB" + ICCProfile:copyright: "Copyright 1999 Adobe Systems Incorporated" + ICCProfile:creation_date: "1999:06:03 00:00:00" + ICCProfile:creator_signature: "41444245" + ICCProfile:device_class: "Display device profile" + ICCProfile:flags: "Not Embedded, Independent" + ICCProfile:manufacturer: "6e6f6e65" + ICCProfile:model: "0" + ICCProfile:platform_signature: "Apple Computer, Inc." + ICCProfile:profile_connection_space: "XYZ" + ICCProfile:profile_description: "Adobe RGB (1998)" + ICCProfile:profile_size: 560 + ICCProfile:profile_version: "2.1.0" + ICCProfile:rendering_intent: "Perceptual" diff --git a/testsuite/jpeg2000/ref/test-jp2.icc b/testsuite/jpeg2000/ref/test-jp2.icc new file mode 100644 index 0000000000..fe2cab55e6 Binary files /dev/null and b/testsuite/jpeg2000/ref/test-jp2.icc differ diff --git a/testsuite/jpeg2000/run.py b/testsuite/jpeg2000/run.py index 0c6e1ebe08..d91767fb8b 100755 --- a/testsuite/jpeg2000/run.py +++ b/testsuite/jpeg2000/run.py @@ -16,3 +16,29 @@ for f in files: command += rw_command (imagedir, f, printinfo=False) +# Try to parse library_list to find the OpenJPEG version. If found, return it as +# a single number, e.g. 2.5.3 -> 20503. Return -1 if not found. +def get_openjp_ver(): + liblist = (subprocess.check_output([oiio_app('oiiotool').strip(), + '--echo', '{getattribute(library_list)}']) + .strip().decode('utf-8')) + for elem in liblist.split(";"): + if elem.startswith("jpeg2000"): + try: + major, minor, build = (int(x) for x in elem.split()[-1].split(".")) + return (major * 10000) + (minor * 100) + build + except Exception: + pass + return -1 + +# Test for writing jpeg2000 with icc profile -- this test case is only added for +# OpenJPEG >= 2.5.4 because ICC profile writing only became reliable from that version. +if get_openjp_ver() >= 20504: + command += oiiotool("../common/tahoe-tiny.tif --iccread ref/test-jp2.icc -o tahoe-icc.jp2") + command += oiiotool("--echo \"ICCProfile of tahoe-icc.jp2\" --info -v tahoe-icc.jp2 | grep \"ICCProfile\"") + command += oiiotool("tahoe-icc.jp2 --iccwrite test-jp2.icc") + + outputs = [ + "test-jp2.icc", + "out.txt" + ]