diff --git a/autotest/gcore/tiff_write.py b/autotest/gcore/tiff_write.py index baab96cfff06..142517ede08d 100755 --- a/autotest/gcore/tiff_write.py +++ b/autotest/gcore/tiff_write.py @@ -9784,6 +9784,23 @@ 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 + + ############################################################################### # 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..0419e33732cd 100644 --- a/frmts/gtiff/gtiffdataset_write.cpp +++ b/frmts/gtiff/gtiffdataset_write.cpp @@ -5267,23 +5267,33 @@ TIFF *GTiffDataset::CreateLL(const char *pszFilename, int nXSize, int nYSize, const struct { GDALDataType eDT; - int nBitsPerSample; + std::vector anBitsPerSample; } asSupportedDTBitsPerSample[] = { - {GDT_Byte, 8}, - {GDT_UInt16, 16}, - {GDT_Float32, 32}, + {GDT_Byte, {8}}, + {GDT_UInt16, {16}}, + {GDT_Float32, {16, 32}}, }; for (const auto &sSupportedDTBitsPerSample : asSupportedDTBitsPerSample) { if (eType == sSupportedDTBitsPerSample.eDT && - l_nBitsPerSample != sSupportedDTBitsPerSample.nBitsPerSample) + std::find(sSupportedDTBitsPerSample.anBitsPerSample.begin(), + sSupportedDTBitsPerSample.anBitsPerSample.end(), + l_nBitsPerSample) == + sSupportedDTBitsPerSample.anBitsPerSample.end()) { + std::string osBitsPerSample; + for (int nVal : sSupportedDTBitsPerSample.anBitsPerSample) + { + if (!osBitsPerSample.empty()) + osBitsPerSample += ", "; + osBitsPerSample += CPLSPrintf("%d", nVal); + } ReportError( pszFilename, CE_Failure, CPLE_NotSupported, "Bits per sample=%d not supported for JXL compression. " - "Only %d is supported for %s data type.", - l_nBitsPerSample, sSupportedDTBitsPerSample.nBitsPerSample, + "Only %s is supported for %s data type.", + l_nBitsPerSample, osBitsPerSample.c_str(), GDALGetDataTypeName(eType)); return nullptr; } 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 */