LCOV - code coverage report
Current view: top level - frmts/zarr - zarr_filters.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 97 185 52.4 %
Date: 2025-06-19 12:30:01 Functions: 8 8 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  Zarr driver
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "zarr.h"
      14             : 
      15             : #include "cpl_conv.h"
      16             : 
      17             : #include <limits>
      18             : 
      19             : /************************************************************************/
      20             : /*                       ZarrShuffleCompressor()                        */
      21             : /************************************************************************/
      22             : 
      23           1 : static bool ZarrShuffleCompressor(const void *input_data, size_t input_size,
      24             :                                   void **output_data, size_t *output_size,
      25             :                                   CSLConstList options,
      26             :                                   void * /* compressor_user_data */)
      27             : {
      28             :     // 4 is the default of the shuffle numcodecs:
      29             :     // https://numcodecs.readthedocs.io/en/v0.10.0/shuffle.html
      30           1 :     const int eltSize = atoi(CSLFetchNameValueDef(options, "ELEMENTSIZE", "4"));
      31           1 :     if (eltSize != 1 && eltSize != 2 && eltSize != 4 && eltSize != 8)
      32             :     {
      33           0 :         CPLError(CE_Failure, CPLE_AppDefined,
      34             :                  "Only ELEMENTSIZE=1,2,4,8 is supported");
      35           0 :         if (output_size)
      36           0 :             *output_size = 0;
      37           0 :         return false;
      38             :     }
      39           1 :     if ((input_size % eltSize) != 0)
      40             :     {
      41           0 :         CPLError(CE_Failure, CPLE_AppDefined,
      42             :                  "input_size should be a multiple of ELEMENTSIZE");
      43           0 :         if (output_size)
      44           0 :             *output_size = 0;
      45           0 :         return false;
      46             :     }
      47           1 :     if (output_data != nullptr && *output_data != nullptr &&
      48           1 :         output_size != nullptr && *output_size != 0)
      49             :     {
      50           1 :         if (*output_size < input_size)
      51             :         {
      52           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Too small output size");
      53           0 :             *output_size = input_size;
      54           0 :             return false;
      55             :         }
      56             : 
      57           1 :         const size_t nElts = input_size / eltSize;
      58             :         // Put at the front of the output buffer all the least significant
      59             :         // bytes of each word, then then 2nd least significant byte, etc.
      60           3 :         for (size_t i = 0; i < nElts; ++i)
      61             :         {
      62           6 :             for (int j = 0; j < eltSize; j++)
      63             :             {
      64           4 :                 (static_cast<uint8_t *>(*output_data))[j * nElts + i] =
      65           4 :                     (static_cast<const uint8_t *>(input_data))[i * eltSize + j];
      66             :             }
      67             :         }
      68             : 
      69           1 :         *output_size = input_size;
      70           1 :         return true;
      71             :     }
      72             : 
      73             : #ifdef not_needed
      74             :     if (output_data == nullptr && output_size != nullptr)
      75             :     {
      76             :         *output_size = input_size;
      77             :         return true;
      78             :     }
      79             : 
      80             :     if (output_data != nullptr && *output_data == nullptr &&
      81             :         output_size != nullptr)
      82             :     {
      83             :         *output_data = VSI_MALLOC_VERBOSE(input_size);
      84             :         *output_size = input_size;
      85             :         if (*output_data == nullptr)
      86             :             return false;
      87             :         bool ret = ZarrShuffleCompressor(input_data, input_size, output_data,
      88             :                                          output_size, options, nullptr);
      89             :         if (!ret)
      90             :         {
      91             :             VSIFree(*output_data);
      92             :             *output_data = nullptr;
      93             :         }
      94             :         return ret;
      95             :     }
      96             : #endif
      97             : 
      98           0 :     CPLError(CE_Failure, CPLE_AppDefined, "Invalid use of API");
      99           0 :     return false;
     100             : }
     101             : 
     102             : /************************************************************************/
     103             : /*                       ZarrShuffleDecompressor()                      */
     104             : /************************************************************************/
     105             : 
     106           2 : static bool ZarrShuffleDecompressor(const void *input_data, size_t input_size,
     107             :                                     void **output_data, size_t *output_size,
     108             :                                     CSLConstList options,
     109             :                                     void * /* compressor_user_data */)
     110             : {
     111             :     // 4 is the default of the shuffle numcodecs:
     112             :     // https://numcodecs.readthedocs.io/en/v0.10.0/shuffle.html
     113           2 :     const int eltSize = atoi(CSLFetchNameValueDef(options, "ELEMENTSIZE", "4"));
     114           2 :     if (eltSize != 1 && eltSize != 2 && eltSize != 4 && eltSize != 8)
     115             :     {
     116           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     117             :                  "Only ELEMENTSIZE=1,2,4,8 is supported");
     118           0 :         if (output_size)
     119           0 :             *output_size = 0;
     120           0 :         return false;
     121             :     }
     122           2 :     if ((input_size % eltSize) != 0)
     123             :     {
     124           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     125             :                  "input_size should be a multiple of ELEMENTSIZE");
     126           0 :         if (output_size)
     127           0 :             *output_size = 0;
     128           0 :         return false;
     129             :     }
     130           2 :     if (output_data != nullptr && *output_data != nullptr &&
     131           2 :         output_size != nullptr && *output_size != 0)
     132             :     {
     133           2 :         if (*output_size < input_size)
     134             :         {
     135           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Too small output size");
     136           0 :             *output_size = input_size;
     137           0 :             return false;
     138             :         }
     139             : 
     140             :         // Reverse of what is done in the compressor function.
     141           2 :         const size_t nElts = input_size / eltSize;
     142           6 :         for (size_t i = 0; i < nElts; ++i)
     143             :         {
     144          12 :             for (int j = 0; j < eltSize; j++)
     145             :             {
     146           8 :                 (static_cast<uint8_t *>(*output_data))[i * eltSize + j] =
     147           8 :                     (static_cast<const uint8_t *>(input_data))[j * nElts + i];
     148             :             }
     149             :         }
     150             : 
     151           2 :         *output_size = input_size;
     152           2 :         return true;
     153             :     }
     154             : 
     155             : #ifdef not_needed
     156             :     if (output_data == nullptr && output_size != nullptr)
     157             :     {
     158             :         *output_size = input_size;
     159             :         return true;
     160             :     }
     161             : 
     162             :     if (output_data != nullptr && *output_data == nullptr &&
     163             :         output_size != nullptr)
     164             :     {
     165             :         *output_data = VSI_MALLOC_VERBOSE(input_size);
     166             :         *output_size = input_size;
     167             :         if (*output_data == nullptr)
     168             :             return false;
     169             :         bool ret = ZarrShuffleDecompressor(input_data, input_size, output_data,
     170             :                                            output_size, options, nullptr);
     171             :         if (!ret)
     172             :         {
     173             :             VSIFree(*output_data);
     174             :             *output_data = nullptr;
     175             :         }
     176             :         return ret;
     177             :     }
     178             : #endif
     179             : 
     180           0 :     CPLError(CE_Failure, CPLE_AppDefined, "Invalid use of API");
     181           0 :     return false;
     182             : }
     183             : 
     184             : /************************************************************************/
     185             : /*                       ZarrGetShuffleCompressor()                     */
     186             : /************************************************************************/
     187             : 
     188           1 : const CPLCompressor *ZarrGetShuffleCompressor()
     189             : {
     190             :     static const CPLCompressor gShuffleCompressor = {
     191             :         /* nStructVersion = */ 1,
     192             :         /* pszId = */ "shuffle",
     193             :         CCT_FILTER,
     194             :         /* papszMetadata = */ nullptr,
     195             :         ZarrShuffleCompressor,
     196             :         /* user_data = */ nullptr};
     197             : 
     198           1 :     return &gShuffleCompressor;
     199             : }
     200             : 
     201             : /************************************************************************/
     202             : /*                     ZarrGetShuffleDecompressor()                     */
     203             : /************************************************************************/
     204             : 
     205           2 : const CPLCompressor *ZarrGetShuffleDecompressor()
     206             : {
     207             :     static const CPLCompressor gShuffleDecompressor = {
     208             :         /* nStructVersion = */ 1,
     209             :         /* pszId = */ "shuffle",
     210             :         CCT_FILTER,
     211             :         /* papszMetadata = */ nullptr,
     212             :         ZarrShuffleDecompressor,
     213             :         /* user_data = */ nullptr};
     214             : 
     215           2 :     return &gShuffleDecompressor;
     216             : }
     217             : 
     218             : /************************************************************************/
     219             : /*                       ZarrQuantizeDecompressor()                     */
     220             : /************************************************************************/
     221             : 
     222           2 : static bool ZarrQuantizeDecompressor(const void *input_data, size_t input_size,
     223             :                                      void **output_data, size_t *output_size,
     224             :                                      CSLConstList options,
     225             :                                      void * /* compressor_user_data */)
     226             : {
     227           2 :     const char *dtype = CSLFetchNameValue(options, "DTYPE");
     228           2 :     if (!dtype)
     229             :     {
     230           0 :         CPLError(CE_Failure, CPLE_AppDefined, "quantize: DTYPE missing");
     231           0 :         if (output_size)
     232           0 :             *output_size = 0;
     233           0 :         return false;
     234             :     }
     235           2 :     if (!EQUAL(dtype, "<f4") && !EQUAL(dtype, "<f8"))
     236             :     {
     237           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     238             :                  "quantize: Only DTYPE=<f4 or <f8 is supported. Not %s.",
     239             :                  dtype);
     240           0 :         if (output_size)
     241           0 :             *output_size = 0;
     242           0 :         return false;
     243             :     }
     244             : 
     245           2 :     const int outputEltSize = EQUAL(dtype, "<f4") ? 4 : 8;
     246           2 :     const GDALDataType eOutDT = EQUAL(dtype, "<f4") ? GDT_Float32 : GDT_Float64;
     247             : 
     248           2 :     const char *astype = CSLFetchNameValue(options, "ASTYPE");
     249           2 :     if (!astype)
     250             :     {
     251           0 :         CPLError(CE_Failure, CPLE_AppDefined, "quantize: ASTYPE missing");
     252           0 :         if (output_size)
     253           0 :             *output_size = 0;
     254           0 :         return false;
     255             :     }
     256           2 :     if (!EQUAL(astype, "<f4") && !EQUAL(astype, "<f8"))
     257             :     {
     258           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     259             :                  "quantize: Only ASTYPE=<f4 or <f8 is supported. Not %s.",
     260             :                  astype);
     261           0 :         if (output_size)
     262           0 :             *output_size = 0;
     263           0 :         return false;
     264             :     }
     265             : 
     266           2 :     const int inputEltSize = EQUAL(astype, "<f4") ? 4 : 8;
     267           2 :     const GDALDataType eInDT = EQUAL(astype, "<f4") ? GDT_Float32 : GDT_Float64;
     268             : 
     269           2 :     if ((input_size % inputEltSize) != 0)
     270             :     {
     271           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     272             :                  "input_size should be a multiple of sizeof(ASTYPE)");
     273           0 :         if (output_size)
     274           0 :             *output_size = 0;
     275           0 :         return false;
     276             :     }
     277             : 
     278           2 :     const size_t nElts = input_size / inputEltSize;
     279           2 :     const uint64_t required_output_size64 =
     280           2 :         static_cast<uint64_t>(nElts) * outputEltSize;
     281             :     if constexpr (SIZEOF_VOIDP < 8)
     282             :     {
     283             :         if (required_output_size64 >= std::numeric_limits<size_t>::max())
     284             :         {
     285             :             CPLError(CE_Failure, CPLE_AppDefined, "Too large input");
     286             :             if (output_size)
     287             :                 *output_size = 0;
     288             :             return false;
     289             :         }
     290             :     }
     291           2 :     const size_t required_output_size =
     292             :         static_cast<size_t>(required_output_size64);
     293             : 
     294           2 :     if (output_data != nullptr && *output_data != nullptr &&
     295           2 :         output_size != nullptr && *output_size != 0)
     296             :     {
     297           2 :         if (*output_size < required_output_size)
     298             :         {
     299           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Too small output size");
     300           0 :             *output_size = required_output_size;
     301           0 :             return false;
     302             :         }
     303             : 
     304             : #ifdef CPL_MSB
     305             :         std::vector<GByte> abyTmp;
     306             :         try
     307             :         {
     308             :             abyTmp.resize(input_size);
     309             :         }
     310             :         catch (const std::exception &)
     311             :         {
     312             :             CPLError(CE_Failure, CPLE_OutOfMemory,
     313             :                      "ZarrQuantizeDecompressor: Out of memory");
     314             :             return false;
     315             :         }
     316             :         memcpy(abyTmp.data(), input_data, input_size);
     317             :         GDALSwapWordsEx(abyTmp.data(), inputEltSize, nElts, inputEltSize);
     318             :         input_data = abyTmp.data();
     319             : #endif
     320             : 
     321           2 :         GDALCopyWords64(input_data, eInDT, inputEltSize, *output_data, eOutDT,
     322             :                         outputEltSize, nElts);
     323             : 
     324             : #ifdef CPL_MSB
     325             :         GDALSwapWordsEx(*output_data, outputEltSize, nElts, outputEltSize);
     326             : #endif
     327             : 
     328           2 :         *output_size = required_output_size;
     329           2 :         return true;
     330             :     }
     331             : 
     332             : #ifdef not_needed
     333             :     if (output_data == nullptr && output_size != nullptr)
     334             :     {
     335             :         *output_size = required_output_size;
     336             :         return true;
     337             :     }
     338             : 
     339             :     if (output_data != nullptr && *output_data == nullptr &&
     340             :         output_size != nullptr)
     341             :     {
     342             :         *output_data = VSI_MALLOC_VERBOSE(required_output_size);
     343             :         *output_size = required_output_size;
     344             :         if (*output_data == nullptr)
     345             :             return false;
     346             :         bool ret = ZarrQuantizeDecompressor(input_data, input_size, output_data,
     347             :                                             output_size, options, nullptr);
     348             :         if (!ret)
     349             :         {
     350             :             VSIFree(*output_data);
     351             :             *output_data = nullptr;
     352             :         }
     353             :         return ret;
     354             :     }
     355             : #endif
     356             : 
     357           0 :     CPLError(CE_Failure, CPLE_AppDefined, "Invalid use of API");
     358           0 :     return false;
     359             : }
     360             : 
     361             : /************************************************************************/
     362             : /*                     ZarrGetQuantizeDecompressor()                    */
     363             : /************************************************************************/
     364             : 
     365           2 : const CPLCompressor *ZarrGetQuantizeDecompressor()
     366             : {
     367             :     static const CPLCompressor gQuantizeDecompressor = {
     368             :         /* nStructVersion = */ 1,
     369             :         /* pszId = */ "quantize",
     370             :         CCT_FILTER,
     371             :         /* papszMetadata = */ nullptr,
     372             :         ZarrQuantizeDecompressor,
     373             :         /* user_data = */ nullptr};
     374             : 
     375           2 :     return &gQuantizeDecompressor;
     376             : }
     377             : 
     378             : /************************************************************************/
     379             : /*                 ZarrFixedScaleOffsetDecompressor()                   */
     380             : /************************************************************************/
     381             : 
     382           4 : static bool ZarrFixedScaleOffsetDecompressor(const void *input_data,
     383             :                                              size_t input_size,
     384             :                                              void **output_data,
     385             :                                              size_t *output_size,
     386             :                                              CSLConstList options,
     387             :                                              void * /* compressor_user_data */)
     388             : {
     389           4 :     const char *offset = CSLFetchNameValue(options, "OFFSET");
     390           4 :     if (!offset)
     391             :     {
     392           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     393             :                  "fixedscaleoffset: OFFSET missing");
     394           0 :         if (output_size)
     395           0 :             *output_size = 0;
     396           0 :         return false;
     397             :     }
     398           4 :     const double dfOffset = CPLAtof(offset);
     399             : 
     400           4 :     const char *scale = CSLFetchNameValue(options, "SCALE");
     401           4 :     if (!scale)
     402             :     {
     403           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     404             :                  "fixedscaleoffset: SCALE missing");
     405           0 :         if (output_size)
     406           0 :             *output_size = 0;
     407           0 :         return false;
     408             :     }
     409           4 :     const double dfScale = CPLAtof(scale);
     410           4 :     if (dfScale == 0)
     411             :     {
     412           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     413             :                  "fixedscaleoffset: SCALE = 0 is invalid");
     414           0 :         if (output_size)
     415           0 :             *output_size = 0;
     416           0 :         return false;
     417             :     }
     418             : 
     419           4 :     const char *dtype = CSLFetchNameValue(options, "DTYPE");
     420           4 :     if (!dtype)
     421             :     {
     422           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     423             :                  "fixedscaleoffset: DTYPE missing");
     424           0 :         if (output_size)
     425           0 :             *output_size = 0;
     426           0 :         return false;
     427             :     }
     428           4 :     if (!EQUAL(dtype, "<f4") && !EQUAL(dtype, "<f8"))
     429             :     {
     430           0 :         CPLError(
     431             :             CE_Failure, CPLE_AppDefined,
     432             :             "fixedscaleoffset: Only DTYPE=<f4 or <f8 is supported. Not %s.",
     433             :             dtype);
     434           0 :         if (output_size)
     435           0 :             *output_size = 0;
     436           0 :         return false;
     437             :     }
     438             : 
     439           4 :     const GDALDataType eOutDT = EQUAL(dtype, "<f4") ? GDT_Float32 : GDT_Float64;
     440           4 :     const int outputEltSize = GDALGetDataTypeSizeBytes(eOutDT);
     441             : 
     442           4 :     const char *astype = CSLFetchNameValue(options, "ASTYPE");
     443           4 :     if (!astype)
     444             :     {
     445           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     446             :                  "fixedscaleoffset: ASTYPE missing");
     447           0 :         if (output_size)
     448           0 :             *output_size = 0;
     449           0 :         return false;
     450             :     }
     451           4 :     if (!EQUAL(astype, "|u1") && !EQUAL(astype, "<u2") && !EQUAL(astype, "<u4"))
     452             :     {
     453           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     454             :                  "fixedscaleoffset: Only ASTYPE=|u1, <u2 or <f4 is supported. "
     455             :                  "Not %s.",
     456             :                  astype);
     457           0 :         if (output_size)
     458           0 :             *output_size = 0;
     459           0 :         return false;
     460             :     }
     461             : 
     462           4 :     const int inputEltSize = astype[2] - '0';
     463           6 :     const GDALDataType eInDT = inputEltSize == 1   ? GDT_Byte
     464           2 :                                : inputEltSize == 2 ? GDT_UInt16
     465             :                                                    : GDT_UInt32;
     466             : 
     467           4 :     if ((input_size % inputEltSize) != 0)
     468             :     {
     469           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     470             :                  "input_size should be a multiple of sizeof(ASTYPE)");
     471           0 :         if (output_size)
     472           0 :             *output_size = 0;
     473           0 :         return false;
     474             :     }
     475             : 
     476           4 :     const size_t nElts = input_size / inputEltSize;
     477           4 :     const uint64_t required_output_size64 =
     478           4 :         static_cast<uint64_t>(nElts) * outputEltSize;
     479             :     if constexpr (SIZEOF_VOIDP < 8)
     480             :     {
     481             :         if (required_output_size64 >= std::numeric_limits<size_t>::max())
     482             :         {
     483             :             CPLError(CE_Failure, CPLE_AppDefined, "Too large input");
     484             :             if (output_size)
     485             :                 *output_size = 0;
     486             :             return false;
     487             :         }
     488             :     }
     489           4 :     const size_t required_output_size =
     490             :         static_cast<size_t>(required_output_size64);
     491             : 
     492           4 :     if (output_data != nullptr && *output_data != nullptr &&
     493           4 :         output_size != nullptr && *output_size != 0)
     494             :     {
     495           4 :         if (*output_size < required_output_size)
     496             :         {
     497           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Too small output size");
     498           0 :             *output_size = required_output_size;
     499           0 :             return false;
     500             :         }
     501             : 
     502             : #ifdef CPL_MSB
     503             :         std::vector<GByte> abyTmp;
     504             :         try
     505             :         {
     506             :             abyTmp.resize(input_size);
     507             :         }
     508             :         catch (const std::exception &)
     509             :         {
     510             :             CPLError(CE_Failure, CPLE_OutOfMemory,
     511             :                      "ZarrFixedScaleOffsetDecompressor: Out of memory");
     512             :             return false;
     513             :         }
     514             :         memcpy(abyTmp.data(), input_data, input_size);
     515             :         GDALSwapWordsEx(abyTmp.data(), inputEltSize, nElts, inputEltSize);
     516             :         input_data = abyTmp.data();
     517             : #endif
     518             : 
     519           4 :         GDALCopyWords64(input_data, eInDT, inputEltSize, *output_data, eOutDT,
     520             :                         outputEltSize, nElts);
     521             : 
     522             :         // Cf https://numcodecs.readthedocs.io/en/v0.4.1/fixedscaleoffset.html
     523           4 :         if (eOutDT == GDT_Float32)
     524             :         {
     525           1 :             float *pafData = static_cast<float *>(*output_data);
     526          11 :             for (size_t i = 0; i < nElts; ++i)
     527             :             {
     528          10 :                 pafData[i] =
     529          10 :                     static_cast<float>(pafData[i] / dfScale + dfOffset);
     530             :             }
     531             :         }
     532             :         else
     533             :         {
     534           3 :             CPLAssert(eOutDT == GDT_Float64);
     535           3 :             double *padfData = static_cast<double *>(*output_data);
     536          33 :             for (size_t i = 0; i < nElts; ++i)
     537             :             {
     538          30 :                 padfData[i] = padfData[i] / dfScale + dfOffset;
     539             :             }
     540             :         }
     541             : 
     542             : #ifdef CPL_MSB
     543             :         GDALSwapWordsEx(*output_data, outputEltSize, nElts, outputEltSize);
     544             : #endif
     545             : 
     546           4 :         *output_size = required_output_size;
     547           4 :         return true;
     548             :     }
     549             : 
     550             : #ifdef not_needed
     551             :     if (output_data == nullptr && output_size != nullptr)
     552             :     {
     553             :         *output_size = required_output_size;
     554             :         return true;
     555             :     }
     556             : 
     557             :     if (output_data != nullptr && *output_data == nullptr &&
     558             :         output_size != nullptr)
     559             :     {
     560             :         *output_data = VSI_MALLOC_VERBOSE(required_output_size);
     561             :         *output_size = required_output_size;
     562             :         if (*output_data == nullptr)
     563             :             return false;
     564             :         bool ret = ZarrFixedScaleOffsetDecompressor(
     565             :             input_data, input_size, output_data, output_size, options, nullptr);
     566             :         if (!ret)
     567             :         {
     568             :             VSIFree(*output_data);
     569             :             *output_data = nullptr;
     570             :         }
     571             :         return ret;
     572             :     }
     573             : #endif
     574             : 
     575           0 :     CPLError(CE_Failure, CPLE_AppDefined, "Invalid use of API");
     576           0 :     return false;
     577             : }
     578             : 
     579             : /************************************************************************/
     580             : /*                  ZarrGetFixedScaleOffsetDecompressor()               */
     581             : /************************************************************************/
     582             : 
     583           4 : const CPLCompressor *ZarrGetFixedScaleOffsetDecompressor()
     584             : {
     585             :     static const CPLCompressor gFixedScaleOffsetDecompressor = {
     586             :         /* nStructVersion = */ 1,
     587             :         /* pszId = */ "fixedscaleoffset",
     588             :         CCT_FILTER,
     589             :         /* papszMetadata = */ nullptr,
     590             :         ZarrFixedScaleOffsetDecompressor,
     591             :         /* user_data = */ nullptr};
     592             : 
     593           4 :     return &gFixedScaleOffsetDecompressor;
     594             : }

Generated by: LCOV version 1.14