libtiff: LERC codec: deal with issues with multi-band PlanarConfig=Contig and NaN values (e414a)

From e414a9d5791ecdd87bb72fa4f85dff3ed458cc87 Mon Sep 17 00:00:00 2001
From: Even Rouault <[EMAIL REDACTED]>
Date: Sun, 24 Mar 2024 18:14:00 +0100
Subject: [PATCH] LERC codec: deal with issues with multi-band
 PlanarConfig=Contig and NaN values

Fixes https://github.com/OSGeo/gdal/issues/9530.
Cf https://github.com/OSGeo/gdal/pull/9548 for the GDAL specific fix.

The LERC codec up-to-now didn't do any special processing when writing a strile
in a multi-band PlanarConfig=Contig file, resulting in corruptions. Now we
generate a mask buffer in that situation. Actually, if the NaN values are not
at the same location accross bands, we need to write one mask-per-band, which
requires liblerc >= 3.0, and in that situation change the LERC blob to
multi-band instead of multi-depth/dim, with liblerc >= 3.0. When all bands have
NaN values at the same location, we generate a single mask, which is compatible
of liblerc < 3.0, but requires at last this version of libtiff to be read. This
is the best solution we come with to fix the issue while having the maximum
backward compatibility.
---
 libtiff/tif_lerc.c | 507 +++++++++++++++++++++++++++++++++++++--------
 1 file changed, 420 insertions(+), 87 deletions(-)

diff --git a/libtiff/tif_lerc.c b/libtiff/tif_lerc.c
index b2a3eb8a..d9d8d334 100644
--- a/libtiff/tif_lerc.c
+++ b/libtiff/tif_lerc.c
@@ -71,6 +71,9 @@ typedef struct
     uint8_t *uncompressed_buffer;
     unsigned int uncompressed_offset;
 
+    uint8_t *uncompressed_buffer_multiband;
+    unsigned int uncompressed_buffer_multiband_alloc;
+
     unsigned int mask_size;
     uint8_t *mask_buffer;
 
@@ -168,7 +171,7 @@ static int GetLercDataType(TIFF *tif)
     return -1;
 }
 
-static int SetupUncompressedBuffer(TIFF *tif, LERCState *sp, const char *module)
+static int SetupBuffers(TIFF *tif, LERCState *sp, const char *module)
 {
     TIFFDirectory *td = &tif->tif_dir;
     uint64_t new_size_64;
@@ -202,8 +205,9 @@ static int SetupUncompressedBuffer(TIFF *tif, LERCState *sp, const char *module)
     sp->uncompressed_size = new_size;
 
     /* add some margin as we are going to use it also to store deflate/zstd
-     * compressed data */
-    new_alloc_64 = 100 + new_size_64 + new_size_64 / 3;
+     * compressed data. We also need extra margin when writing very small
+     * rasters with one mask per band. */
+    new_alloc_64 = 256 + new_size_64 + new_size_64 / 3;
 #ifdef ZSTD_SUPPORT
     {
         size_t zstd_max = ZSTD_compressBound((size_t)new_size_64);
@@ -243,11 +247,17 @@ static int SetupUncompressedBuffer(TIFF *tif, LERCState *sp, const char *module)
          td->td_sampleinfo[td->td_extrasamples - 1] == EXTRASAMPLE_UNASSALPHA &&
          GetLercDataType(tif) == 1) ||
         (td->td_sampleformat == SAMPLEFORMAT_IEEEFP &&
-         (td->td_planarconfig == PLANARCONFIG_SEPARATE ||
-          td->td_samplesperpixel == 1) &&
          (td->td_bitspersample == 32 || td->td_bitspersample == 64)))
     {
         unsigned int mask_size = sp->segment_width * sp->segment_height;
+#if LERC_AT_LEAST_VERSION(3, 0, 0)
+        if (td->td_sampleformat == SAMPLEFORMAT_IEEEFP &&
+            td->td_planarconfig == PLANARCONFIG_CONTIG)
+        {
+            /* We may need one mask per band */
+            mask_size *= td->td_samplesperpixel;
+        }
+#endif
         if (sp->mask_size < mask_size)
         {
             void *mask_buffer =
@@ -279,7 +289,7 @@ static int LERCPreDecode(TIFF *tif, uint16_t s)
     TIFFDirectory *td = &tif->tif_dir;
     LERCState *sp = LERCDecoderState(tif);
     int lerc_data_type;
-    unsigned int infoArray[8];
+    unsigned int infoArray[9];
     unsigned nomask_bands = td->td_samplesperpixel;
     int ndims;
     int use_mask = 0;
@@ -295,7 +305,7 @@ static int LERCPreDecode(TIFF *tif, uint16_t s)
     if (lerc_data_type < 0)
         return 0;
 
-    if (!SetupUncompressedBuffer(tif, sp, module))
+    if (!SetupBuffers(tif, sp, module))
         return 0;
 
     if (sp->additional_compression != LERC_ADD_COMPRESSION_NONE)
@@ -400,7 +410,7 @@ static int LERCPreDecode(TIFF *tif, uint16_t s)
     }
 
     lerc_ret =
-        lerc_getBlobInfo(lerc_data, lerc_data_size, infoArray, NULL, 8, 0);
+        lerc_getBlobInfo(lerc_data, lerc_data_size, infoArray, NULL, 9, 0);
     if (lerc_ret != 0)
     {
         TIFFErrorExtR(tif, module, "lerc_getBlobInfo() failed");
@@ -418,18 +428,16 @@ static int LERCPreDecode(TIFF *tif, uint16_t s)
         use_mask = 1;
         nomask_bands--;
     }
-    else if (td->td_sampleformat == SAMPLEFORMAT_IEEEFP &&
-             (td->td_planarconfig == PLANARCONFIG_SEPARATE ||
-              td->td_samplesperpixel == 1) &&
-             (td->td_bitspersample == 32 || td->td_bitspersample == 64))
+    else if (td->td_sampleformat == SAMPLEFORMAT_IEEEFP)
     {
         use_mask = 1;
     }
 
     ndims = td->td_planarconfig == PLANARCONFIG_CONTIG ? nomask_bands : 1;
 
-    /* Info returned in infoArray is { version, dataType, nDim, nCols,
-        nRows, nBands, nValidPixels, blobSize } */
+    /* Info returned in infoArray is { version, dataType, nDim/nDepth, nCols,
+        nRows, nBands, nValidPixels, blobSize,
+        and starting with liblerc 3.0 nRequestedMasks } */
     if (infoArray[0] != (unsigned)sp->lerc_version)
     {
         TIFFWarningExtR(tif, module,
@@ -442,12 +450,29 @@ static int LERCPreDecode(TIFF *tif, uint16_t s)
                       infoArray[1], lerc_data_type);
         return 0;
     }
-    if (infoArray[2] != (unsigned)ndims)
+
+    const unsigned nFoundDims = infoArray[2];
+#if LERC_AT_LEAST_VERSION(3, 0, 0)
+    if (td->td_sampleformat == SAMPLEFORMAT_IEEEFP &&
+        td->td_planarconfig == PLANARCONFIG_CONTIG &&
+        td->td_samplesperpixel > 1)
+    {
+        if (nFoundDims != 1 && nFoundDims != (unsigned)ndims)
+        {
+            TIFFErrorExtR(tif, module, "Unexpected nDim: %d. Expected: 1 or %d",
+                          nFoundDims, ndims);
+            return 0;
+        }
+    }
+    else
+#endif
+        if (nFoundDims != (unsigned)ndims)
     {
         TIFFErrorExtR(tif, module, "Unexpected nDim: %d. Expected: %d",
-                      infoArray[2], ndims);
+                      nFoundDims, ndims);
         return 0;
     }
+
     if (infoArray[3] != sp->segment_width)
     {
         TIFFErrorExtR(tif, module, "Unexpected nCols: %d. Expected: %du",
@@ -460,12 +485,38 @@ static int LERCPreDecode(TIFF *tif, uint16_t s)
                       infoArray[4], sp->segment_height);
         return 0;
     }
-    if (infoArray[5] != 1)
+
+    const unsigned nFoundBands = infoArray[5];
+    if (td->td_sampleformat == SAMPLEFORMAT_IEEEFP &&
+        td->td_planarconfig == PLANARCONFIG_CONTIG &&
+        td->td_samplesperpixel > 1 && nFoundDims == 1)
+    {
+#if !LERC_AT_LEAST_VERSION(3, 0, 0)
+        if (nFoundBands == td->td_samplesperpixel)
+        {
+            TIFFErrorExtR(
+                tif, module,
+                "Unexpected nBands: %d. This file may have been generated with "
+                "a liblerc version >= 3.0, with one mask per band, and is not "
+                "supported by this older version of liblerc",
+                nFoundBands);
+            return 0;
+        }
+#endif
+        if (nFoundBands != td->td_samplesperpixel)
+        {
+            TIFFErrorExtR(tif, module, "Unexpected nBands: %d. Expected: %d",
+                          nFoundBands, td->td_samplesperpixel);
+            return 0;
+        }
+    }
+    else if (nFoundBands != 1)
     {
         TIFFErrorExtR(tif, module, "Unexpected nBands: %d. Expected: %d",
-                      infoArray[5], 1);
+                      nFoundBands, 1);
         return 0;
     }
+
     if (infoArray[7] != lerc_data_size)
     {
         TIFFErrorExtR(tif, module, "Unexpected blobSize: %d. Expected: %u",
@@ -473,13 +524,75 @@ static int LERCPreDecode(TIFF *tif, uint16_t s)
         return 0;
     }
 
-    lerc_ret = lerc_decode(lerc_data, lerc_data_size,
+    int nRequestedMasks = use_mask ? 1 : 0;
+#if LERC_AT_LEAST_VERSION(3, 0, 0)
+    const int nFoundMasks = infoArray[8];
+    if (td->td_sampleformat == SAMPLEFORMAT_IEEEFP &&
+        td->td_planarconfig == PLANARCONFIG_CONTIG &&
+        td->td_samplesperpixel > 1 && nFoundDims == 1)
+    {
+        if (nFoundMasks != 0 && nFoundMasks != td->td_samplesperpixel)
+        {
+            TIFFErrorExtR(tif, module,
+                          "Unexpected nFoundMasks: %d. Expected: 0 or %d",
+                          nFoundMasks, td->td_samplesperpixel);
+            return 0;
+        }
+        nRequestedMasks = nFoundMasks;
+    }
+    else
+    {
+        if (nFoundMasks != 0 && nFoundMasks != 1)
+        {
+            TIFFErrorExtR(tif, module,
+                          "Unexpected nFoundMasks: %d. Expected: 0 or 1",
+                          nFoundMasks);
+            return 0;
+        }
+    }
+    if (td->td_sampleformat == SAMPLEFORMAT_IEEEFP && nFoundMasks == 0)
+    {
+        nRequestedMasks = 0;
+        use_mask = 0;
+    }
+#endif
+
+    const unsigned nb_pixels = sp->segment_width * sp->segment_height;
+
 #if LERC_AT_LEAST_VERSION(3, 0, 0)
-                           use_mask ? 1 : 0,
+    if (nRequestedMasks > 1)
+    {
+        unsigned int num_bytes_needed =
+            nb_pixels * td->td_samplesperpixel * (td->td_bitspersample / 8);
+        if (sp->uncompressed_buffer_multiband_alloc < num_bytes_needed)
+        {
+            _TIFFfreeExt(tif, sp->uncompressed_buffer_multiband);
+            sp->uncompressed_buffer_multiband =
+                _TIFFmallocExt(tif, num_bytes_needed);
+            if (!sp->uncompressed_buffer_multiband)
+            {
+                sp->uncompressed_buffer_multiband_alloc = 0;
+                return 0;
+            }
+            sp->uncompressed_buffer_multiband_alloc = num_bytes_needed;
+        }
+        lerc_ret = lerc_decode(lerc_data, lerc_data_size, nRequestedMasks,
+                               sp->mask_buffer, nFoundDims, sp->segment_width,
+                               sp->segment_height, nFoundBands, lerc_data_type,
+                               sp->uncompressed_buffer_multiband);
+    }
+    else
 #endif
-                           use_mask ? sp->mask_buffer : NULL, ndims,
-                           sp->segment_width, sp->segment_height, 1,
-                           lerc_data_type, sp->uncompressed_buffer);
+    {
+        lerc_ret =
+            lerc_decode(lerc_data, lerc_data_size,
+#if LERC_AT_LEAST_VERSION(3, 0, 0)
+                        nRequestedMasks,
+#endif
+                        use_mask ? sp->mask_buffer : NULL, nFoundDims,
+                        sp->segment_width, sp->segment_height, nFoundBands,
+                        lerc_data_type, sp->uncompressed_buffer);
+    }
     if (lerc_ret != 0)
     {
         TIFFErrorExtR(tif, module, "lerc_decode() failed");
@@ -515,7 +628,6 @@ static int LERCPreDecode(TIFF *tif, uint16_t s)
     }
     else if (use_mask && td->td_sampleformat == SAMPLEFORMAT_IEEEFP)
     {
-        const unsigned nb_pixels = sp->segment_width * sp->segment_height;
         unsigned i;
 #if WORDS_BIGENDIAN
         const unsigned char nan_bytes[] = {0x7f, 0xc0, 0, 0};
@@ -525,23 +637,104 @@ static int LERCPreDecode(TIFF *tif, uint16_t s)
         float nan_float32;
         memcpy(&nan_float32, nan_bytes, 4);
 
-        if (td->td_bitspersample == 32)
+        if (td->td_planarconfig == PLANARCONFIG_SEPARATE ||
+            td->td_samplesperpixel == 1)
         {
-            for (i = 0; i < nb_pixels; i++)
+            if (td->td_bitspersample == 32)
+            {
+                for (i = 0; i < nb_pixels; i++)
+                {
+                    if (sp->mask_buffer[i] == 0)
+                        ((float *)sp->uncompressed_buffer)[i] = nan_float32;
+                }
+            }
+            else
+            {
+                const double nan_float64 = nan_float32;
+                for (i = 0; i < nb_pixels; i++)
+                {
+                    if (sp->mask_buffer[i] == 0)
+                        ((double *)sp->uncompressed_buffer)[i] = nan_float64;
+                }
+            }
+        }
+        else if (nRequestedMasks == 1)
+        {
+            assert(nFoundDims == td->td_samplesperpixel);
+            assert(nFoundBands == 1);
+
+            unsigned k = 0;
+            if (td->td_bitspersample == 32)
+            {
+                for (i = 0; i < nb_pixels; i++)
+                {
+                    for (int j = 0; j < td->td_samplesperpixel; j++)
+                    {
+                        if (sp->mask_buffer[i] == 0)
+                            ((float *)sp->uncompressed_buffer)[k] = nan_float32;
+                        ++k;
+                    }
+                }
+            }
+            else
             {
-                if (sp->mask_buffer[i] == 0)
-                    ((float *)sp->uncompressed_buffer)[i] = nan_float32;
+                const double nan_float64 = nan_float32;
+                for (i = 0; i < nb_pixels; i++)
+                {
+                    for (int j = 0; j < td->td_samplesperpixel; j++)
+                    {
+                        if (sp->mask_buffer[i] == 0)
+                            ((double *)sp->uncompressed_buffer)[k] =
+                                nan_float64;
+                        ++k;
+                    }
+                }
             }
         }
+#if LERC_AT_LEAST_VERSION(3, 0, 0)
         else
         {
-            const double nan_float64 = nan_float32;
-            for (i = 0; i < nb_pixels; i++)
+            assert(nRequestedMasks == td->td_samplesperpixel);
+            assert(nFoundDims == 1);
+            assert(nFoundBands == td->td_samplesperpixel);
+
+            unsigned k = 0;
+            if (td->td_bitspersample == 32)
             {
-                if (sp->mask_buffer[i] == 0)
-                    ((double *)sp->uncompressed_buffer)[i] = nan_float64;
+                for (i = 0; i < nb_pixels; i++)
+                {
+                    for (int j = 0; j < td->td_samplesperpixel; j++)
+                    {
+                        if (sp->mask_buffer[i + j * nb_pixels] == 0)
+                            ((float *)sp->uncompressed_buffer)[k] = nan_float32;
+                        else
+                            ((float *)sp->uncompressed_buffer)[k] =
+                                ((float *)sp->uncompressed_buffer_multiband)
+                                    [i + j * nb_pixels];
+                        ++k;
+                    }
+                }
+            }
+            else
+            {
+                const double nan_float64 = nan_float32;
+                for (i = 0; i < nb_pixels; i++)
+                {
+                    for (int j = 0; j < td->td_samplesperpixel; j++)
+                    {
+                        if (sp->mask_buffer[i + j * nb_pixels] == 0)
+                            ((double *)sp->uncompressed_buffer)[k] =
+                                nan_float64;
+                        else
+                            ((double *)sp->uncompressed_buffer)[k] =
+                                ((double *)sp->uncompressed_buffer_multiband)
+                                    [i + j * nb_pixels];
+                        ++k;
+                    }
+                }
             }
         }
+#endif
     }
 
     return 1;
@@ -611,7 +804,7 @@ static int LERCPreEncode(TIFF *tif, uint16_t s)
     if (lerc_data_type < 0)
         return 0;
 
-    if (!SetupUncompressedBuffer(tif, sp, module))
+    if (!SetupBuffers(tif, sp, module))
         return 0;
 
     return 1;
@@ -650,7 +843,6 @@ static int LERCPostEncode(TIFF *tif)
     lerc_status lerc_ret;
     static const char module[] = "LERCPostEncode";
     LERCState *sp = LERCEncoderState(tif);
-    unsigned int numBytes = 0;
     unsigned int numBytesWritten = 0;
     TIFFDirectory *td = &tif->tif_dir;
     int use_mask = 0;
@@ -662,6 +854,9 @@ static int LERCPostEncode(TIFF *tif)
         return 0;
     }
 
+    int mask_count = 1;
+    const unsigned nb_pixels = sp->segment_width * sp->segment_height;
+
     /* Extract alpha mask (if containing only 0 and 255 values, */
     /* and compact array of regular bands */
     if (td->td_planarconfig == PLANARCONFIG_CONTIG && td->td_extrasamples > 0 &&
@@ -673,7 +868,6 @@ static int LERCPostEncode(TIFF *tif)
         const unsigned src_stride =
             td->td_samplesperpixel * (td->td_bitspersample / 8);
         unsigned i = 0;
-        const unsigned nb_pixels = sp->segment_width * sp->segment_height;
 
         use_mask = 1;
         for (i = 0; i < nb_pixels; i++)
@@ -710,109 +904,247 @@ static int LERCPostEncode(TIFF *tif)
         }
     }
     else if (td->td_sampleformat == SAMPLEFORMAT_IEEEFP &&
-             (td->td_planarconfig == PLANARCONFIG_SEPARATE ||
-              dst_nbands == 1) &&
              (td->td_bitspersample == 32 || td->td_bitspersample == 64))
     {
         /* Check for NaN values */
         unsigned i;
-        const unsigned nb_pixels = sp->segment_width * sp->segment_height;
         if (td->td_bitspersample == 32)
         {
-            for (i = 0; i < nb_pixels; i++)
+            if (td->td_planarconfig == PLANARCONFIG_CONTIG && dst_nbands > 1)
             {
-                const float val = ((float *)sp->uncompressed_buffer)[i];
-                if (val != val)
+                unsigned k = 0;
+                for (i = 0; i < nb_pixels; i++)
                 {
-                    use_mask = 1;
-                    break;
+                    int count_nan = 0;
+                    for (int j = 0; j < td->td_samplesperpixel; ++j)
+                    {
+                        const float val = ((float *)sp->uncompressed_buffer)[k];
+                        ++k;
+                        if (val != val)
+                        {
+                            ++count_nan;
+                        }
+                    }
+                    if (count_nan > 0)
+                    {
+                        use_mask = 1;
+                        if (count_nan < td->td_samplesperpixel)
+                        {
+                            mask_count = td->td_samplesperpixel;
+                            break;
+                        }
+                    }
+                }
+            }
+            else
+            {
+                for (i = 0; i < nb_pixels; i++)
+                {
+                    const float val = ((float *)sp->uncompressed_buffer)[i];
+                    if (val != val)
+                    {
+                        use_mask = 1;
+                        break;
+                    }
                 }
             }
         }
         else
         {
-            for (i = 0; i < nb_pixels; i++)
+            if (td->td_planarconfig == PLANARCONFIG_CONTIG && dst_nbands > 1)
             {
-                const double val = ((double *)sp->uncompressed_buffer)[i];
-                if (val != val)
+                unsigned k = 0;
+                for (i = 0; i < nb_pixels; i++)
+                {
+                    int count_nan = 0;
+                    for (int j = 0; j < td->td_samplesperpixel; ++j)
+                    {
+                        const double val =
+                            ((double *)sp->uncompressed_buffer)[k];
+                        ++k;
+                        if (val != val)
+                        {
+                            ++count_nan;
+                        }
+                    }
+                    if (count_nan > 0)
+                    {
+                        use_mask = 1;
+                        if (count_nan < td->td_samplesperpixel)
+                        {
+                            mask_count = td->td_samplesperpixel;
+                            break;
+                        }
+                    }
+                }
+            }
+            else
+            {
+                for (i = 0; i < nb_pixels; i++)
                 {
-                    use_mask = 1;
-                    break;
+                    const double val = ((double *)sp->uncompressed_buffer)[i];
+                    if (val != val)
+                    {
+                        use_mask = 1;
+                        break;
+                    }
                 }
             }
         }
 
         if (use_mask)
         {
-            if (td->td_bitspersample == 32)
+            if (mask_count > 1)
             {
-                for (i = 0; i < nb_pixels; i++)
+#if LERC_AT_LEAST_VERSION(3, 0, 0)
+                unsigned int num_bytes_needed =
+                    nb_pixels * dst_nbands * (td->td_bitspersample / 8);
+                if (sp->uncompressed_buffer_multiband_alloc < num_bytes_needed)
                 {
-                    const float val = ((float *)sp->uncompressed_buffer)[i];
-                    sp->mask_buffer[i] = (val == val) ? 255 : 0;
+                    _TIFFfreeExt(tif, sp->uncompressed_buffer_multiband);
+                    sp->uncompressed_buffer_multiband =
+                        _TIFFmallocExt(tif, num_bytes_needed);
+                    if (!sp->uncompressed_buffer_multiband)
+                    {
+                        sp->uncompressed_buffer_multiband_alloc = 0;
+                        return 0;
+                    }
+                    sp->uncompressed_buffer_multiband_alloc = num_bytes_needed;
+                }
+
+                unsigned k = 0;
+                if (td->td_bitspersample == 32)
+                {
+                    for (i = 0; i < nb_pixels; i++)
+                    {
+                        for (int j = 0; j < td->td_samplesperpixel; ++j)
+                        {
+                            const float val =
+                                ((float *)sp->uncompressed_buffer)[k];
+                            ((float *)sp->uncompressed_buffer_multiband)
+                                [i + j * nb_pixels] = val;
+                            ++k;
+                            sp->mask_buffer[i + j * nb_pixels] =
+                                (val == val) ? 255 : 0;
+                        }
+                    }
+                }
+                else
+                {
+                    for (i = 0; i < nb_pixels; i++)
+                    {
+                        for (int j = 0; j < td->td_samplesperpixel; ++j)
+                        {
+                            const double val =
+                                ((double *)sp->uncompressed_buffer)[k];
+                            ((double *)sp->uncompressed_buffer_multiband)
+                                [i + j * nb_pixels] = val;
+                            ++k;
+                            sp->mask_buffer[i + j * nb_pixels] =
+                                (val == val) ? 255 : 0;
+                        }
+                    }
+                }
+#else
+                TIFFErrorExtR(tif, module,
+                              "lerc_encode() would need to create one mask per "
+                              "sample, but this requires liblerc >= 3.0");
+                return 0;
+#endif
+            }
+            else if (td->td_planarconfig == PLANARCONFIG_CONTIG &&
+                     dst_nbands > 1)
+            {
+                if (td->td_bitspersample == 32)
+                {
+                    for (i = 0; i < nb_pixels; i++)
+                    {
+                        const float val =
+                            ((float *)sp->uncompressed_buffer)[i * dst_nbands];
+                        sp->mask_buffer[i] = (val == val) ? 255 : 0;
+                    }
+                }
+                else
+                {
+                    for (i = 0; i < nb_pixels; i++)
+                    {
+                        const double val =
+                            ((double *)sp->uncompressed_buffer)[i * dst_nbands];
+                        sp->mask_buffer[i] = (val == val) ? 255 : 0;
+                    }
                 }
             }
             else
             {
-                for (i = 0; i < nb_pixels; i++)
+                if (td->td_bitspersample == 32)
                 {
-                    const double val = ((double *)sp->uncompressed_buffer)[i];
-                    sp->mask_buffer[i] = (val == val) ? 255 : 0;
+                    for (i = 0; i < nb_pixels; i++)
+                    {
+                        const float val = ((float *)sp->uncompressed_buffer)[i];
+                        sp->mask_buffer[i] = (val == val) ? 255 : 0;
+                    }
+                }
+                else
+                {
+                    for (i = 0; i < nb_pixels; i++)
+                    {
+                        const double val =
+                            ((double *)sp->uncompressed_buffer)[i];
+                        sp->mask_buffer[i] = (val == val) ? 255 : 0;
+                    }
                 }
             }
         }
     }
 
-#if 0
-        lerc_ret = lerc_computeCompressedSize(
-            sp->uncompressed_buffer,
-            sp->lerc_version,
-            GetLercDataType(tif),
-            td->td_planarconfig == PLANARCONFIG_CONTIG ?
-                dst_nbands : 1,
-            sp->segment_width,
-            sp->segment_height,
-            1,
-            use_mask ? sp->mask_buffer : NULL,
-            sp->maxzerror,
-            &numBytes);
-        if( lerc_ret != 0 )
-        {
-            TIFFErrorExtR(tif, module,
-                         "lerc_computeCompressedSize() failed");
-            return 0;
-        }
-#else
-    numBytes = sp->uncompressed_alloc;
+    unsigned int estimated_compressed_size = sp->uncompressed_alloc;
+#if LERC_AT_LEAST_VERSION(3, 0, 0)
+    if (mask_count > 1)
+    {
+        estimated_compressed_size += nb_pixels * mask_count / 8;
+    }
 #endif
 
-    if (sp->compressed_size < numBytes)
+    if (sp->compressed_size < estimated_compressed_size)
     {
         _TIFFfreeExt(tif, sp->compressed_buffer);
-        sp->compressed_buffer = _TIFFmallocExt(tif, numBytes);
+        sp->compressed_buffer = _TIFFmallocExt(tif, estimated_compressed_size);
         if (!sp->compressed_buffer)
         {
             sp->compressed_size = 0;
             return 0;
         }
-        sp->compressed_size = numBytes;
+        sp->compressed_size = estimated_compressed_size;
     }
 
-    lerc_ret = lerc_encodeForVersion(
-        sp->uncompressed_buffer, sp->lerc_version, GetLercDataType(tif),
-        td->td_planarconfig == PLANARCONFIG_CONTIG ? dst_nbands : 1,
-        sp->segment_width, sp->segment_height, 1,
 #if LERC_AT_LEAST_VERSION(3, 0, 0)
-        use_mask ? 1 : 0,
+    if (mask_count > 1)
+    {
+        lerc_ret = lerc_encodeForVersion(
+            sp->uncompressed_buffer_multiband, sp->lerc_version,
+            GetLercDataType(tif), 1, sp->segment_width, sp->segment_height,
+            dst_nbands, dst_nbands, sp->mask_buffer, sp->maxzerror,
+            sp->compressed_buffer, sp->compressed_size, &numBytesWritten);
+    }
+    else
 #endif
-        use_mask ? sp->mask_buffer : NULL, sp->maxzerror, sp->compressed_buffer,
-        sp->compressed_size, &numBytesWritten);
+    {
+        lerc_ret = lerc_encodeForVersion(
+            sp->uncompressed_buffer, sp->lerc_version, GetLercDataType(tif),
+            td->td_planarconfig == PLANARCONFIG_CONTIG ? dst_nbands : 1,
+            sp->segment_width, sp->segment_height, 1,
+#if LERC_AT_LEAST_VERSION(3, 0, 0)
+            use_mask ? 1 : 0,
+#endif
+            use_mask ? sp->mask_buffer : NULL, sp->maxzerror,
+            sp->compressed_buffer, sp->compressed_size, &numBytesWritten);
+    }
     if (lerc_ret != 0)
     {
         TIFFErrorExtR(tif, module, "lerc_encode() failed");
         return 0;
     }
-    assert(numBytesWritten < numBytes);
+    assert(numBytesWritten < estimated_compressed_size);
 
     if (sp->additional_compression == LERC_ADD_COMPRESSION_DEFLATE)
     {
@@ -958,6 +1290,7 @@ static void LERCCleanup(TIFF *tif)
     tif->tif_tagmethods.vsetfield = sp->vsetparent;
 
     _TIFFfreeExt(tif, sp->uncompressed_buffer);
+    _TIFFfreeExt(tif, sp->uncompressed_buffer_multiband);
     _TIFFfreeExt(tif, sp->compressed_buffer);
     _TIFFfreeExt(tif, sp->mask_buffer);