From 397ca1582cea9024e1f8411b1c81e4dcdd85ffe8 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sun, 27 Oct 2024 00:17:38 +0200 Subject: [PATCH] TIFF JXL: add support for Float16 and Compression=52546 which is JPEGXL from DNG 1.7 specification --- autotest/gcore/tiff_write.py | 33 ++++++++++++++++++++++++++++++ frmts/gtiff/geotiff.cpp | 7 +++++++ frmts/gtiff/gtiffdataset_write.cpp | 5 ++--- frmts/gtiff/libtiff/tiff.h | 1 + frmts/gtiff/tif_jxl.c | 21 +++++++++++++++---- frmts/gtiff/tif_jxl.h | 4 ++++ 6 files changed, 64 insertions(+), 7 deletions(-) diff --git a/autotest/gcore/tiff_write.py b/autotest/gcore/tiff_write.py index baab96cfff06..f803b95d24b8 100755 --- a/autotest/gcore/tiff_write.py +++ b/autotest/gcore/tiff_write.py @@ -9784,6 +9784,39 @@ def test_tiff_write_jpegxl_five_bands_lossless(tmp_vsimem): assert ds.GetRasterBand(i + 1).Checksum() == 4672 +############################################################################### + + +@pytest.mark.require_creation_option("GTiff", "JXL") +def test_tiff_write_jpegxl_float16(tmp_vsimem): + + outfilename = str(tmp_vsimem / "test_tiff_write_jpegxl_float16") + src_ds = gdal.Open("data/float16.tif") + gdal.GetDriverByName("GTiff").CreateCopy( + outfilename, src_ds, options=["COMPRESS=JXL", "JXL_LOSSLESS=YES"] + ) + ds = gdal.Open(outfilename) + assert ds.GetRasterBand(1).DataType == gdal.GDT_Float32 + assert ds.GetRasterBand(1).GetMetadataItem("NBITS", "IMAGE_STRUCTURE") == "16" + assert ds.GetRasterBand(1).Checksum() == 4672 + + +############################################################################### + + +@pytest.mark.require_creation_option("GTiff", "JXL") +@pytest.mark.parametrize("dt,nbits", [(gdal.GDT_Float64, None), (gdal.GDT_Byte, 1)]) +@gdaltest.enable_exceptions() +def test_tiff_write_jpegxl_errors(tmp_vsimem, dt, nbits): + + outfilename = str(tmp_vsimem / "test_tiff_write_jpegxl_errors") + with pytest.raises(Exception): + options = {"COMPRESS": "JXL"} + if nbits: + options["NBITS"] = str(nbits) + gdal.GetDriverByName("GTiff").Create(outfilename, 1, 1, 1, dt, options=options) + + ############################################################################### # Test creating overviews with NaN nodata diff --git a/frmts/gtiff/geotiff.cpp b/frmts/gtiff/geotiff.cpp index 854c9cf91b7b..877feb1dc7e7 100644 --- a/frmts/gtiff/geotiff.cpp +++ b/frmts/gtiff/geotiff.cpp @@ -983,6 +983,7 @@ static void GTiffTagExtender(TIFF *tif) static std::mutex oDeleteMutex; #ifdef HAVE_JXL static TIFFCodec *pJXLCodec = nullptr; +static TIFFCodec *pJXLCodecDNG17 = nullptr; #endif void GTiffOneTimeInit() @@ -1000,6 +1001,8 @@ void GTiffOneTimeInit() if (pJXLCodec == nullptr) { pJXLCodec = TIFFRegisterCODEC(COMPRESSION_JXL, "JXL", TIFFInitJXL); + pJXLCodecDNG17 = + TIFFRegisterCODEC(COMPRESSION_JXL_DNG_1_7, "JXL", TIFFInitJXL); } #endif @@ -1024,6 +1027,9 @@ static void GDALDeregister_GTiff(GDALDriver *) if (pJXLCodec) TIFFUnRegisterCODEC(pJXLCodec); pJXLCodec = nullptr; + if (pJXLCodecDNG17) + TIFFUnRegisterCODEC(pJXLCodecDNG17); + pJXLCodecDNG17 = nullptr; #endif } @@ -1058,6 +1064,7 @@ static const struct {COMPRESSION_LERC, "LERC_ZSTD", true}, COMPRESSION_ENTRY(WEBP, true), COMPRESSION_ENTRY(JXL, true), + COMPRESSION_ENTRY(JXL_DNG_1_7, true), // Compression methods in read-only COMPRESSION_ENTRY(OJPEG, false), diff --git a/frmts/gtiff/gtiffdataset_write.cpp b/frmts/gtiff/gtiffdataset_write.cpp index e9ee1ad95219..bac9d8eb3b92 100644 --- a/frmts/gtiff/gtiffdataset_write.cpp +++ b/frmts/gtiff/gtiffdataset_write.cpp @@ -5252,10 +5252,10 @@ TIFF *GTiffDataset::CreateLL(const char *pszFilename, int nXSize, int nYSize, } #ifdef HAVE_JXL - if (l_nCompression == COMPRESSION_JXL) + if (l_nCompression == COMPRESSION_JXL && eType != GDT_Float32) { // Reflects tif_jxl's GetJXLDataType() - if (eType != GDT_Byte && eType != GDT_UInt16 && eType != GDT_Float32) + if (eType != GDT_Byte && eType != GDT_UInt16) { ReportError(pszFilename, CE_Failure, CPLE_NotSupported, "Data type %s not supported for JXL compression. Only " @@ -5271,7 +5271,6 @@ TIFF *GTiffDataset::CreateLL(const char *pszFilename, int nXSize, int nYSize, } asSupportedDTBitsPerSample[] = { {GDT_Byte, 8}, {GDT_UInt16, 16}, - {GDT_Float32, 32}, }; for (const auto &sSupportedDTBitsPerSample : asSupportedDTBitsPerSample) diff --git a/frmts/gtiff/libtiff/tiff.h b/frmts/gtiff/libtiff/tiff.h index d8da33dc3837..980e8e8f52bb 100644 --- a/frmts/gtiff/libtiff/tiff.h +++ b/frmts/gtiff/libtiff/tiff.h @@ -216,6 +216,7 @@ typedef enum #define COMPRESSION_ZSTD 50000 /* ZSTD: WARNING not registered in Adobe-maintained registry */ #define COMPRESSION_WEBP 50001 /* WEBP: WARNING not registered in Adobe-maintained registry */ #define COMPRESSION_JXL 50002 /* JPEGXL: WARNING not registered in Adobe-maintained registry */ +#define COMPRESSION_JXL_DNG_1_7 52546 /* JPEGXL from DNG 1.7 specification */ #define TIFFTAG_PHOTOMETRIC 262 /* photometric interpretation */ #define PHOTOMETRIC_MINISWHITE 0 /* min value is white */ #define PHOTOMETRIC_MINISBLACK 1 /* min value is black */ diff --git a/frmts/gtiff/tif_jxl.c b/frmts/gtiff/tif_jxl.c index 940808bafe35..4634fad4558d 100644 --- a/frmts/gtiff/tif_jxl.c +++ b/frmts/gtiff/tif_jxl.c @@ -98,8 +98,16 @@ static int GetJXLDataType(TIFF *tif) return JXL_TYPE_FLOAT; } - TIFFErrorExtR(tif, module, - "Unsupported combination of SampleFormat and BitsPerSample"); + if (td->td_sampleformat == SAMPLEFORMAT_IEEEFP && + td->td_bitspersample == 16) + { + return JXL_TYPE_FLOAT16; + } + + TIFFErrorExtR( + tif, module, + "Unsupported combination of SampleFormat(=%d) and BitsPerSample(=%d)", + td->td_sampleformat, td->td_bitspersample); return -1; } @@ -113,6 +121,8 @@ static int GetJXLDataTypeSize(JxlDataType dtype) return 2; case JXL_TYPE_FLOAT: return 4; + case JXL_TYPE_FLOAT16: + return 2; default: return 0; } @@ -811,7 +821,10 @@ static int JXLPostEncode(TIFF *tif) basic_info.orientation = JXL_ORIENT_IDENTITY; if (td->td_sampleformat == SAMPLEFORMAT_IEEEFP) { - basic_info.exponent_bits_per_sample = 8; + if (td->td_bitspersample == 32) + basic_info.exponent_bits_per_sample = 8; + else + basic_info.exponent_bits_per_sample = 5; } else { @@ -1275,7 +1288,7 @@ int TIFFInitJXL(TIFF *tif, int scheme) JXLState *sp; (void)scheme; - assert(scheme == COMPRESSION_JXL); + assert(scheme == COMPRESSION_JXL || scheme == COMPRESSION_JXL_DNG_1_7); /* * Merge codec-specific tag information. diff --git a/frmts/gtiff/tif_jxl.h b/frmts/gtiff/tif_jxl.h index 6e80cd459cdb..2827322c7541 100644 --- a/frmts/gtiff/tif_jxl.h +++ b/frmts/gtiff/tif_jxl.h @@ -30,6 +30,10 @@ 50002 /* JPEGXL: WARNING not registered in Adobe-maintained registry */ #endif +#ifndef COMPRESSION_JXL_DNG_1_7 +#define COMPRESSION_JXL_DNG_1_7 52546 /* JPEGXL from DNG 1.7 specification */ +#endif + #ifndef TIFFTAG_JXL_LOSSYNESS /* Pseudo tags */