diff --git a/autotest/gdrivers/jpegxl.py b/autotest/gdrivers/jpegxl.py index 27f21373acb8..4983e012b119 100755 --- a/autotest/gdrivers/jpegxl.py +++ b/autotest/gdrivers/jpegxl.py @@ -552,6 +552,24 @@ def test_jpegxl_write_five_bands(): gdal.GetDriverByName("JPEGXL").Delete(outfilename) +def test_jpegxl_write_five_bands_lossy(): + + drv = gdal.GetDriverByName("JPEGXL") + if drv.GetMetadataItem("JXL_ENCODER_SUPPORT_EXTRA_CHANNELS") is None: + pytest.skip() + + src_ds = gdal.Open("data/jpegxl/five_bands.jxl") + outfilename = "/vsimem/out.jxl" + gdal.Translate(outfilename, src_ds, options="-of JPEGXL -co DISTANCE=3 -ot Byte") + ds = gdal.Open(outfilename) + for i in range(5): + assert ds.GetRasterBand(i + 1).ComputeRasterMinMax() == pytest.approx( + (10.0 * (i + 1), 10.0 * (i + 1)), abs=1 + ) + ds = None + gdal.GetDriverByName("JPEGXL").Delete(outfilename) + + def test_jpegxl_createcopy_errors(): outfilename = "/vsimem/out.jxl" diff --git a/doc/source/drivers/raster/jpegxl.rst b/doc/source/drivers/raster/jpegxl.rst index 4ed85841b58f..f1453995c310 100644 --- a/doc/source/drivers/raster/jpegxl.rst +++ b/doc/source/drivers/raster/jpegxl.rst @@ -97,6 +97,8 @@ The following creation options are available: If set to NO, or in AUTO mode if the source dataset does not use JPEG compression, the regular conversion code path is taken, resulting in a lossless or lossy copy depending on the LOSSLESS setting. + AUTO mode defaults to NO, if EFFORT, DISTANCE, ALPHA_DISTANCE or QUALITY + options are used. - .. co:: EFFORT :choices: 1-9 diff --git a/frmts/jpegxl/jpegxl.cpp b/frmts/jpegxl/jpegxl.cpp index 0ac0f3e1925d..46befd797728 100644 --- a/frmts/jpegxl/jpegxl.cpp +++ b/frmts/jpegxl/jpegxl.cpp @@ -1798,16 +1798,35 @@ GDALDataset *JPEGXLDataset::CreateCopy(const char *pszFilename, } } + constexpr int DEFAULT_EFFORT = 5; + const int nEffort = atoi(CSLFetchNameValueDef( + papszOptions, "EFFORT", CPLSPrintf("%d", DEFAULT_EFFORT))); + const char *pszDistance = CSLFetchNameValue(papszOptions, "DISTANCE"); + const char *pszQuality = CSLFetchNameValue(papszOptions, "QUALITY"); + const char *pszAlphaDistance = + CSLFetchNameValue(papszOptions, "ALPHA_DISTANCE"); const char *pszLossLessCopy = CSLFetchNameValueDef(papszOptions, "LOSSLESS_COPY", "AUTO"); - if (EQUAL(pszLossLessCopy, "AUTO") || CPLTestBool(pszLossLessCopy)) + if ((EQUAL(pszLossLessCopy, "AUTO") && !pszDistance && !pszQuality && + !pszAlphaDistance && nEffort == DEFAULT_EFFORT) || + (!EQUAL(pszLossLessCopy, "AUTO") && CPLTestBool(pszLossLessCopy))) { void *pJPEGXLContent = nullptr; size_t nJPEGXLContent = 0; - if (poSrcDS->ReadCompressedData( - "JXL", 0, 0, poSrcDS->GetRasterXSize(), - poSrcDS->GetRasterYSize(), poSrcDS->GetRasterCount(), nullptr, - &pJPEGXLContent, &nJPEGXLContent, nullptr) == CE_None) + const bool bSrcIsJXL = + (poSrcDS->ReadCompressedData( + "JXL", 0, 0, poSrcDS->GetRasterXSize(), + poSrcDS->GetRasterYSize(), poSrcDS->GetRasterCount(), nullptr, + &pJPEGXLContent, &nJPEGXLContent, nullptr) == CE_None); + if (bSrcIsJXL && (pszDistance || pszQuality || pszAlphaDistance || + nEffort != DEFAULT_EFFORT)) + { + CPLError(CE_Failure, CPLE_NotSupported, + "LOSSLESS_COPY=YES not supported when EFFORT, QUALITY, " + "DISTANCE or ALPHA_DISTANCE are specified"); + return nullptr; + } + else if (bSrcIsJXL) { CPLDebug("JPEGXL", "Lossless copy from source dataset"); GByte abySizeAndBoxName[8]; @@ -2062,10 +2081,6 @@ GDALDataset *JPEGXLDataset::CreateCopy(const char *pszFilename, } const char *pszLossLess = CSLFetchNameValue(papszOptions, "LOSSLESS"); - const char *pszDistance = CSLFetchNameValue(papszOptions, "DISTANCE"); - const char *pszQuality = CSLFetchNameValue(papszOptions, "QUALITY"); - const char *pszAlphaDistance = - CSLFetchNameValue(papszOptions, "ALPHA_DISTANCE"); const bool bLossless = (pszLossLess == nullptr && pszDistance == nullptr && pszQuality == nullptr) || @@ -2325,7 +2340,6 @@ GDALDataset *JPEGXLDataset::CreateCopy(const char *pszFilename, } } - const int nEffort = atoi(CSLFetchNameValueDef(papszOptions, "EFFORT", "5")); #ifdef HAVE_JxlEncoderFrameSettingsSetOption if (JxlEncoderFrameSettingsSetOption(opts, JXL_ENC_FRAME_SETTING_EFFORT, nEffort) != JXL_ENC_SUCCESS) @@ -2572,6 +2586,17 @@ GDALDataset *JPEGXLDataset::CreateCopy(const char *pszFilename, return nullptr; } } + else if (!bLossless) + { + // By default libjxl applies lossless encoding for extra channels + if (JXL_ENC_SUCCESS != + JxlEncoderSetExtraChannelDistance(opts, nIndex, fDistance)) + { + CPLError(CE_Failure, CPLE_AppDefined, + "JxlEncoderSetExtraChannelDistance failed"); + return nullptr; + } + } #endif } }