Coverage Report

Created: 2025-07-23 09:13

/src/gdal/frmts/webp/webpdataset.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  GDAL WEBP Driver
4
 * Purpose:  Implement GDAL WEBP Support based on libwebp
5
 * Author:   Even Rouault, <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2011-2013, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "cpl_string.h"
14
#include "gdal_frmts.h"
15
#include "gdal_pam.h"
16
17
#include "webp_headers.h"
18
#include "webpdrivercore.h"
19
20
#include <limits>
21
22
/************************************************************************/
23
/* ==================================================================== */
24
/*                               WEBPDataset                            */
25
/* ==================================================================== */
26
/************************************************************************/
27
28
class WEBPRasterBand;
29
30
class WEBPDataset final : public GDALPamDataset
31
{
32
    friend class WEBPRasterBand;
33
34
    VSILFILE *fpImage;
35
    GByte *pabyUncompressed;
36
    int bHasBeenUncompressed;
37
    CPLErr eUncompressErrRet;
38
    CPLErr Uncompress();
39
40
    int bHasReadXMPMetadata;
41
42
    CPL_DISALLOW_COPY_ASSIGN(WEBPDataset)
43
44
  public:
45
    WEBPDataset();
46
    virtual ~WEBPDataset();
47
48
    virtual CPLErr IRasterIO(GDALRWFlag, int, int, int, int, void *, int, int,
49
                             GDALDataType, int, BANDMAP_TYPE,
50
                             GSpacing nPixelSpace, GSpacing nLineSpace,
51
                             GSpacing nBandSpace,
52
                             GDALRasterIOExtraArg *psExtraArg) override;
53
54
    virtual char **GetMetadataDomainList() override;
55
    virtual char **GetMetadata(const char *pszDomain = "") override;
56
57
    CPLStringList GetCompressionFormats(int nXOff, int nYOff, int nXSize,
58
                                        int nYSize, int nBandCount,
59
                                        const int *panBandList) override;
60
    CPLErr ReadCompressedData(const char *pszFormat, int nXOff, int nYOff,
61
                              int nXSize, int nYSize, int nBandCount,
62
                              const int *panBandList, void **ppBuffer,
63
                              size_t *pnBufferSize,
64
                              char **ppszDetailedFormat) override;
65
66
    static GDALPamDataset *OpenPAM(GDALOpenInfo *poOpenInfo);
67
    static GDALDataset *Open(GDALOpenInfo *);
68
    static GDALDataset *CreateCopy(const char *pszFilename,
69
                                   GDALDataset *poSrcDS, int bStrict,
70
                                   char **papszOptions,
71
                                   GDALProgressFunc pfnProgress,
72
                                   void *pProgressData);
73
};
74
75
/************************************************************************/
76
/* ==================================================================== */
77
/*                            WEBPRasterBand                            */
78
/* ==================================================================== */
79
/************************************************************************/
80
81
class WEBPRasterBand final : public GDALPamRasterBand
82
{
83
    friend class WEBPDataset;
84
85
  public:
86
    WEBPRasterBand(WEBPDataset *, int);
87
88
    virtual CPLErr IReadBlock(int, int, void *) override;
89
    virtual GDALColorInterp GetColorInterpretation() override;
90
};
91
92
/************************************************************************/
93
/*                          WEBPRasterBand()                            */
94
/************************************************************************/
95
96
WEBPRasterBand::WEBPRasterBand(WEBPDataset *poDSIn, int)
97
6
{
98
6
    poDS = poDSIn;
99
100
6
    eDataType = GDT_Byte;
101
102
6
    nBlockXSize = poDSIn->nRasterXSize;
103
6
    nBlockYSize = 1;
104
6
}
105
106
/************************************************************************/
107
/*                             IReadBlock()                             */
108
/************************************************************************/
109
110
CPLErr WEBPRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
111
                                  void *pImage)
112
0
{
113
0
    WEBPDataset *poGDS = cpl::down_cast<WEBPDataset *>(poDS);
114
115
0
    if (poGDS->Uncompress() != CE_None)
116
0
        return CE_Failure;
117
118
0
    GByte *pabyUncompressed =
119
0
        &poGDS->pabyUncompressed[nBlockYOff * nRasterXSize * poGDS->nBands +
120
0
                                 nBand - 1];
121
0
    for (int i = 0; i < nRasterXSize; i++)
122
0
        reinterpret_cast<GByte *>(pImage)[i] =
123
0
            pabyUncompressed[poGDS->nBands * i];
124
125
0
    return CE_None;
126
0
}
127
128
/************************************************************************/
129
/*                       GetColorInterpretation()                       */
130
/************************************************************************/
131
132
GDALColorInterp WEBPRasterBand::GetColorInterpretation()
133
134
0
{
135
0
    if (nBand == 1)
136
0
        return GCI_RedBand;
137
138
0
    else if (nBand == 2)
139
0
        return GCI_GreenBand;
140
141
0
    else if (nBand == 3)
142
0
        return GCI_BlueBand;
143
144
0
    return GCI_AlphaBand;
145
0
}
146
147
/************************************************************************/
148
/* ==================================================================== */
149
/*                             WEBPDataset                               */
150
/* ==================================================================== */
151
/************************************************************************/
152
153
/************************************************************************/
154
/*                            WEBPDataset()                              */
155
/************************************************************************/
156
157
WEBPDataset::WEBPDataset()
158
2
    : fpImage(nullptr), pabyUncompressed(nullptr), bHasBeenUncompressed(FALSE),
159
2
      eUncompressErrRet(CE_None), bHasReadXMPMetadata(FALSE)
160
2
{
161
2
}
162
163
/************************************************************************/
164
/*                           ~WEBPDataset()                             */
165
/************************************************************************/
166
167
WEBPDataset::~WEBPDataset()
168
169
2
{
170
2
    FlushCache(true);
171
2
    if (fpImage)
172
2
        VSIFCloseL(fpImage);
173
2
    VSIFree(pabyUncompressed);
174
2
}
175
176
/************************************************************************/
177
/*                      GetMetadataDomainList()                         */
178
/************************************************************************/
179
180
char **WEBPDataset::GetMetadataDomainList()
181
0
{
182
0
    return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
183
0
                                   TRUE, "xml:XMP", nullptr);
184
0
}
185
186
/************************************************************************/
187
/*                           GetMetadata()                              */
188
/************************************************************************/
189
190
char **WEBPDataset::GetMetadata(const char *pszDomain)
191
0
{
192
0
    if ((pszDomain != nullptr && EQUAL(pszDomain, "xml:XMP")) &&
193
0
        !bHasReadXMPMetadata)
194
0
    {
195
0
        bHasReadXMPMetadata = TRUE;
196
197
0
        VSIFSeekL(fpImage, 12, SEEK_SET);
198
199
0
        bool bFirst = true;
200
0
        while (true)
201
0
        {
202
0
            char szHeader[5];
203
0
            GUInt32 nChunkSize;
204
205
0
            if (VSIFReadL(szHeader, 1, 4, fpImage) != 4 ||
206
0
                VSIFReadL(&nChunkSize, 1, 4, fpImage) != 4)
207
0
                break;
208
209
0
            szHeader[4] = '\0';
210
0
            CPL_LSBPTR32(&nChunkSize);
211
212
0
            if (bFirst)
213
0
            {
214
0
                if (strcmp(szHeader, "VP8X") != 0 || nChunkSize < 10)
215
0
                    break;
216
217
0
                int l_nFlags;
218
0
                if (VSIFReadL(&l_nFlags, 1, 4, fpImage) != 4)
219
0
                    break;
220
0
                CPL_LSBPTR32(&l_nFlags);
221
0
                if ((l_nFlags & 8) == 0)
222
0
                    break;
223
224
0
                VSIFSeekL(fpImage, nChunkSize - 4, SEEK_CUR);
225
226
0
                bFirst = false;
227
0
            }
228
0
            else if (strcmp(szHeader, "META") == 0)
229
0
            {
230
0
                if (nChunkSize > 1024 * 1024)
231
0
                    break;
232
233
0
                char *pszXMP =
234
0
                    reinterpret_cast<char *>(VSIMalloc(nChunkSize + 1));
235
0
                if (pszXMP == nullptr)
236
0
                    break;
237
238
0
                if (static_cast<GUInt32>(VSIFReadL(pszXMP, 1, nChunkSize,
239
0
                                                   fpImage)) != nChunkSize)
240
0
                {
241
0
                    VSIFree(pszXMP);
242
0
                    break;
243
0
                }
244
0
                pszXMP[nChunkSize] = '\0';
245
246
                /* Avoid setting the PAM dirty bit just for that */
247
0
                const int nOldPamFlags = nPamFlags;
248
249
0
                char *apszMDList[2] = {pszXMP, nullptr};
250
0
                SetMetadata(apszMDList, "xml:XMP");
251
252
                // cppcheck-suppress redundantAssignment
253
0
                nPamFlags = nOldPamFlags;
254
255
0
                VSIFree(pszXMP);
256
0
                break;
257
0
            }
258
0
            else
259
0
                VSIFSeekL(fpImage, nChunkSize, SEEK_CUR);
260
0
        }
261
0
    }
262
263
0
    return GDALPamDataset::GetMetadata(pszDomain);
264
0
}
265
266
/************************************************************************/
267
/*                            Uncompress()                              */
268
/************************************************************************/
269
270
CPLErr WEBPDataset::Uncompress()
271
1
{
272
1
    if (bHasBeenUncompressed)
273
0
        return eUncompressErrRet;
274
275
1
    bHasBeenUncompressed = TRUE;
276
1
    eUncompressErrRet = CE_Failure;
277
278
    // To avoid excessive memory allocation attempts
279
    // Normally WebP images are no larger than 16383x16383*4 ~= 1 GB
280
1
    if (nRasterXSize > INT_MAX / (nRasterYSize * nBands))
281
0
    {
282
0
        CPLError(CE_Failure, CPLE_NotSupported, "Too large image");
283
0
        return CE_Failure;
284
0
    }
285
286
1
    pabyUncompressed = reinterpret_cast<GByte *>(
287
1
        VSIMalloc3(nRasterXSize, nRasterYSize, nBands));
288
1
    if (pabyUncompressed == nullptr)
289
0
        return CE_Failure;
290
291
1
    VSIFSeekL(fpImage, 0, SEEK_END);
292
1
    vsi_l_offset nSizeLarge = VSIFTellL(fpImage);
293
1
    if (nSizeLarge !=
294
1
        static_cast<vsi_l_offset>(static_cast<uint32_t>(nSizeLarge)))
295
0
        return CE_Failure;
296
1
    VSIFSeekL(fpImage, 0, SEEK_SET);
297
1
    uint32_t nSize = static_cast<uint32_t>(nSizeLarge);
298
1
    uint8_t *pabyCompressed = reinterpret_cast<uint8_t *>(VSIMalloc(nSize));
299
1
    if (pabyCompressed == nullptr)
300
0
        return CE_Failure;
301
1
    VSIFReadL(pabyCompressed, 1, nSize, fpImage);
302
1
    uint8_t *pRet;
303
304
1
    if (nBands == 4)
305
0
        pRet = WebPDecodeRGBAInto(pabyCompressed, static_cast<uint32_t>(nSize),
306
0
                                  static_cast<uint8_t *>(pabyUncompressed),
307
0
                                  static_cast<size_t>(nRasterXSize) *
308
0
                                      nRasterYSize * nBands,
309
0
                                  nRasterXSize * nBands);
310
1
    else
311
1
        pRet = WebPDecodeRGBInto(pabyCompressed, static_cast<uint32_t>(nSize),
312
1
                                 static_cast<uint8_t *>(pabyUncompressed),
313
1
                                 static_cast<size_t>(nRasterXSize) *
314
1
                                     nRasterYSize * nBands,
315
1
                                 nRasterXSize * nBands);
316
317
1
    VSIFree(pabyCompressed);
318
1
    if (pRet == nullptr)
319
0
    {
320
0
        CPLError(CE_Failure, CPLE_AppDefined, "WebPDecodeRGBInto() failed");
321
0
        return CE_Failure;
322
0
    }
323
1
    eUncompressErrRet = CE_None;
324
325
1
    return CE_None;
326
1
}
327
328
/************************************************************************/
329
/*                             IRasterIO()                              */
330
/************************************************************************/
331
332
CPLErr WEBPDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
333
                              int nXSize, int nYSize, void *pData,
334
                              int nBufXSize, int nBufYSize,
335
                              GDALDataType eBufType, int nBandCount,
336
                              BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
337
                              GSpacing nLineSpace, GSpacing nBandSpace,
338
                              GDALRasterIOExtraArg *psExtraArg)
339
340
1
{
341
1
    if ((eRWFlag == GF_Read) && (nBandCount == nBands) && (nXOff == 0) &&
342
1
        (nYOff == 0) && (nXSize == nBufXSize) && (nXSize == nRasterXSize) &&
343
1
        (nYSize == nBufYSize) && (nYSize == nRasterYSize) &&
344
1
        (eBufType == GDT_Byte) && (pData != nullptr) &&
345
1
        IsAllBands(nBandCount, panBandMap))
346
1
    {
347
1
        if (Uncompress() != CE_None)
348
0
            return CE_Failure;
349
1
        if (nPixelSpace == nBands && nLineSpace == (nPixelSpace * nXSize) &&
350
1
            nBandSpace == 1)
351
1
        {
352
1
            memcpy(pData, pabyUncompressed,
353
1
                   static_cast<size_t>(nBands) * nXSize * nYSize);
354
1
        }
355
0
        else
356
0
        {
357
0
            for (int y = 0; y < nYSize; ++y)
358
0
            {
359
0
                GByte *pabyScanline = pabyUncompressed + y * nBands * nXSize;
360
0
                for (int x = 0; x < nXSize; ++x)
361
0
                {
362
0
                    for (int iBand = 0; iBand < nBands; iBand++)
363
0
                        reinterpret_cast<GByte *>(
364
0
                            pData)[(y * nLineSpace) + (x * nPixelSpace) +
365
0
                                   iBand * nBandSpace] =
366
0
                            pabyScanline[x * nBands + iBand];
367
0
                }
368
0
            }
369
0
        }
370
371
1
        return CE_None;
372
1
    }
373
374
0
    return GDALPamDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
375
0
                                     pData, nBufXSize, nBufYSize, eBufType,
376
0
                                     nBandCount, panBandMap, nPixelSpace,
377
0
                                     nLineSpace, nBandSpace, psExtraArg);
378
1
}
379
380
/************************************************************************/
381
/*                       GetCompressionFormats()                        */
382
/************************************************************************/
383
384
CPLStringList WEBPDataset::GetCompressionFormats(int nXOff, int nYOff,
385
                                                 int nXSize, int nYSize,
386
                                                 int nBandCount,
387
                                                 const int *panBandList)
388
0
{
389
0
    CPLStringList aosRet;
390
0
    if (nXOff == 0 && nYOff == 0 && nXSize == nRasterXSize &&
391
0
        nYSize == nRasterYSize && IsAllBands(nBandCount, panBandList))
392
0
    {
393
0
        aosRet.AddString("WEBP");
394
0
    }
395
0
    return aosRet;
396
0
}
397
398
/************************************************************************/
399
/*                       ReadCompressedData()                           */
400
/************************************************************************/
401
402
CPLErr WEBPDataset::ReadCompressedData(const char *pszFormat, int nXOff,
403
                                       int nYOff, int nXSize, int nYSize,
404
                                       int nBandCount, const int *panBandList,
405
                                       void **ppBuffer, size_t *pnBufferSize,
406
                                       char **ppszDetailedFormat)
407
0
{
408
0
    if (nXOff == 0 && nYOff == 0 && nXSize == nRasterXSize &&
409
0
        nYSize == nRasterYSize && IsAllBands(nBandCount, panBandList))
410
0
    {
411
0
        const CPLStringList aosTokens(CSLTokenizeString2(pszFormat, ";", 0));
412
0
        if (aosTokens.size() != 1)
413
0
            return CE_Failure;
414
415
0
        if (EQUAL(aosTokens[0], "WEBP"))
416
0
        {
417
0
            if (ppszDetailedFormat)
418
0
                *ppszDetailedFormat = VSIStrdup("WEBP");
419
0
            VSIFSeekL(fpImage, 0, SEEK_END);
420
0
            const auto nFileSize = VSIFTellL(fpImage);
421
0
            if (nFileSize > std::numeric_limits<uint32_t>::max())
422
0
                return CE_Failure;
423
0
            auto nSize = static_cast<uint32_t>(nFileSize);
424
0
            if (ppBuffer)
425
0
            {
426
0
                if (!pnBufferSize)
427
0
                    return CE_Failure;
428
0
                bool bFreeOnError = false;
429
0
                if (*ppBuffer)
430
0
                {
431
0
                    if (*pnBufferSize < nSize)
432
0
                        return CE_Failure;
433
0
                }
434
0
                else
435
0
                {
436
0
                    *ppBuffer = VSI_MALLOC_VERBOSE(nSize);
437
0
                    if (*ppBuffer == nullptr)
438
0
                        return CE_Failure;
439
0
                    bFreeOnError = true;
440
0
                }
441
0
                VSIFSeekL(fpImage, 0, SEEK_SET);
442
0
                if (VSIFReadL(*ppBuffer, nSize, 1, fpImage) != 1)
443
0
                {
444
0
                    if (bFreeOnError)
445
0
                    {
446
0
                        VSIFree(*ppBuffer);
447
0
                        *ppBuffer = nullptr;
448
0
                    }
449
0
                    return CE_Failure;
450
0
                }
451
452
                // Remove META box
453
0
                if (nSize > 12 && memcmp(*ppBuffer, "RIFF", 4) == 0)
454
0
                {
455
0
                    size_t nPos = 12;
456
0
                    GByte *pabyData = static_cast<GByte *>(*ppBuffer);
457
0
                    while (nPos <= nSize - 8)
458
0
                    {
459
0
                        char szBoxName[5] = {0, 0, 0, 0, 0};
460
0
                        memcpy(szBoxName, pabyData + nPos, 4);
461
0
                        uint32_t nChunkSize;
462
0
                        memcpy(&nChunkSize, pabyData + nPos + 4, 4);
463
0
                        CPL_LSBPTR32(&nChunkSize);
464
0
                        if (nChunkSize % 2)  // Payload padding if needed
465
0
                            nChunkSize++;
466
0
                        if (nChunkSize > nSize - (nPos + 8))
467
0
                            break;
468
0
                        if (memcmp(szBoxName, "META", 4) == 0)
469
0
                        {
470
0
                            CPLDebug("WEBP",
471
0
                                     "Remove existing %s box from "
472
0
                                     "source compressed data",
473
0
                                     szBoxName);
474
0
                            if (nPos + 8 + nChunkSize < nSize)
475
0
                            {
476
0
                                memmove(pabyData + nPos,
477
0
                                        pabyData + nPos + 8 + nChunkSize,
478
0
                                        nSize - (nPos + 8 + nChunkSize));
479
0
                            }
480
0
                            nSize -= 8 + nChunkSize;
481
0
                        }
482
0
                        else
483
0
                        {
484
0
                            nPos += 8 + nChunkSize;
485
0
                        }
486
0
                    }
487
488
                    // Patch size of RIFF
489
0
                    uint32_t nSize32 = nSize - 8;
490
0
                    CPL_LSBPTR32(&nSize32);
491
0
                    memcpy(pabyData + 4, &nSize32, 4);
492
0
                }
493
0
            }
494
0
            if (pnBufferSize)
495
0
                *pnBufferSize = nSize;
496
0
            return CE_None;
497
0
        }
498
0
    }
499
0
    return CE_Failure;
500
0
}
501
502
/************************************************************************/
503
/*                          OpenPAM()                                   */
504
/************************************************************************/
505
506
GDALPamDataset *WEBPDataset::OpenPAM(GDALOpenInfo *poOpenInfo)
507
508
6
{
509
6
    if (!WEBPDriverIdentify(poOpenInfo) || poOpenInfo->fpL == nullptr)
510
0
        return nullptr;
511
512
6
    int nWidth, nHeight;
513
6
    if (!WebPGetInfo(reinterpret_cast<const uint8_t *>(poOpenInfo->pabyHeader),
514
6
                     static_cast<uint32_t>(poOpenInfo->nHeaderBytes), &nWidth,
515
6
                     &nHeight))
516
4
        return nullptr;
517
518
2
    int nBands = 3;
519
520
2
    auto poDS = std::make_unique<WEBPDataset>();
521
522
2
#if WEBP_DECODER_ABI_VERSION >= 0x0002
523
2
    WebPDecoderConfig config;
524
2
    if (!WebPInitDecoderConfig(&config))
525
0
        return nullptr;
526
527
2
    const bool bOK =
528
2
        WebPGetFeatures(poOpenInfo->pabyHeader, poOpenInfo->nHeaderBytes,
529
2
                        &config.input) == VP8_STATUS_OK;
530
531
    // Cf commit https://github.com/webmproject/libwebp/commit/86c0031eb2c24f78d4dcfc5dab752ebc9f511607#diff-859d219dccb3163cc11cd538effed461ff0145135070abfe70bd263f16408023
532
    // Added in webp 0.4.0
533
2
#if WEBP_DECODER_ABI_VERSION >= 0x0202
534
2
    poDS->GDALDataset::SetMetadataItem(
535
2
        "COMPRESSION_REVERSIBILITY",
536
2
        config.input.format == 2 ? "LOSSLESS" : "LOSSY", "IMAGE_STRUCTURE");
537
2
#endif
538
539
2
    if (config.input.has_alpha ||
540
2
        CPLTestBool(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
541
2
                                         "FORCE_4BANDS", "NO")))
542
0
        nBands = 4;
543
544
2
    WebPFreeDecBuffer(&config.output);
545
546
2
    if (!bOK)
547
0
        return nullptr;
548
549
2
#endif
550
551
2
    if (poOpenInfo->eAccess == GA_Update)
552
0
    {
553
0
        ReportUpdateNotSupportedByDriver("WEBP");
554
0
        return nullptr;
555
0
    }
556
557
    /* -------------------------------------------------------------------- */
558
    /*      Create a corresponding GDALDataset.                             */
559
    /* -------------------------------------------------------------------- */
560
2
    poDS->nRasterXSize = nWidth;
561
2
    poDS->nRasterYSize = nHeight;
562
2
    poDS->fpImage = poOpenInfo->fpL;
563
2
    poOpenInfo->fpL = nullptr;
564
565
    /* -------------------------------------------------------------------- */
566
    /*      Create band information objects.                                */
567
    /* -------------------------------------------------------------------- */
568
8
    for (int iBand = 0; iBand < nBands; iBand++)
569
6
        poDS->SetBand(iBand + 1, new WEBPRasterBand(poDS.get(), iBand + 1));
570
571
    /* -------------------------------------------------------------------- */
572
    /*      Initialize any PAM information.                                 */
573
    /* -------------------------------------------------------------------- */
574
2
    poDS->SetDescription(poOpenInfo->pszFilename);
575
576
2
    poDS->TryLoadXML(poOpenInfo->GetSiblingFiles());
577
578
    /* -------------------------------------------------------------------- */
579
    /*      Open overviews.                                                 */
580
    /* -------------------------------------------------------------------- */
581
2
    poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename,
582
2
                                poOpenInfo->GetSiblingFiles());
583
584
2
    return poDS.release();
585
2
}
586
587
/************************************************************************/
588
/*                             Open()                                   */
589
/************************************************************************/
590
591
GDALDataset *WEBPDataset::Open(GDALOpenInfo *poOpenInfo)
592
593
6
{
594
6
    return OpenPAM(poOpenInfo);
595
6
}
596
597
/************************************************************************/
598
/*                              WebPUserData                            */
599
/************************************************************************/
600
601
typedef struct
602
{
603
    VSILFILE *fp;
604
    GDALProgressFunc pfnProgress;
605
    void *pProgressData;
606
} WebPUserData;
607
608
/************************************************************************/
609
/*                         WEBPDatasetWriter()                          */
610
/************************************************************************/
611
612
static int WEBPDatasetWriter(const uint8_t *data, size_t data_size,
613
                             const WebPPicture *const picture)
614
0
{
615
0
    WebPUserData *pUserData =
616
0
        reinterpret_cast<WebPUserData *>(picture->custom_ptr);
617
0
    return VSIFWriteL(data, 1, data_size, pUserData->fp) == data_size;
618
0
}
619
620
/************************************************************************/
621
/*                        WEBPDatasetProgressHook()                     */
622
/************************************************************************/
623
624
#if WEBP_ENCODER_ABI_VERSION >= 0x0100
625
static int WEBPDatasetProgressHook(int percent,
626
                                   const WebPPicture *const picture)
627
0
{
628
0
    WebPUserData *pUserData =
629
0
        reinterpret_cast<WebPUserData *>(picture->custom_ptr);
630
0
    return pUserData->pfnProgress(percent / 100.0, nullptr,
631
0
                                  pUserData->pProgressData);
632
0
}
633
#endif
634
635
/************************************************************************/
636
/*                              CreateCopy()                            */
637
/************************************************************************/
638
639
GDALDataset *WEBPDataset::CreateCopy(const char *pszFilename,
640
                                     GDALDataset *poSrcDS, int bStrict,
641
                                     char **papszOptions,
642
                                     GDALProgressFunc pfnProgress,
643
                                     void *pProgressData)
644
645
0
{
646
0
    const char *pszLossLessCopy =
647
0
        CSLFetchNameValueDef(papszOptions, "LOSSLESS_COPY", "AUTO");
648
0
    if (EQUAL(pszLossLessCopy, "AUTO") || CPLTestBool(pszLossLessCopy))
649
0
    {
650
0
        void *pWEBPContent = nullptr;
651
0
        size_t nWEBPContent = 0;
652
0
        if (poSrcDS->ReadCompressedData(
653
0
                "WEBP", 0, 0, poSrcDS->GetRasterXSize(),
654
0
                poSrcDS->GetRasterYSize(), poSrcDS->GetRasterCount(), nullptr,
655
0
                &pWEBPContent, &nWEBPContent, nullptr) == CE_None)
656
0
        {
657
0
            CPLDebug("WEBP", "Lossless copy from source dataset");
658
0
            std::vector<GByte> abyData;
659
0
            try
660
0
            {
661
0
                abyData.assign(static_cast<const GByte *>(pWEBPContent),
662
0
                               static_cast<const GByte *>(pWEBPContent) +
663
0
                                   nWEBPContent);
664
665
0
                char **papszXMP = poSrcDS->GetMetadata("xml:XMP");
666
0
                if (papszXMP && papszXMP[0])
667
0
                {
668
0
                    GByte abyChunkHeader[8];
669
0
                    memcpy(abyChunkHeader, "META", 4);
670
0
                    const size_t nXMPSize = strlen(papszXMP[0]);
671
0
                    uint32_t nChunkSize = static_cast<uint32_t>(nXMPSize);
672
0
                    CPL_LSBPTR32(&nChunkSize);
673
0
                    memcpy(abyChunkHeader + 4, &nChunkSize, 4);
674
0
                    abyData.insert(abyData.end(), abyChunkHeader,
675
0
                                   abyChunkHeader + sizeof(abyChunkHeader));
676
0
                    abyData.insert(
677
0
                        abyData.end(), reinterpret_cast<GByte *>(papszXMP[0]),
678
0
                        reinterpret_cast<GByte *>(papszXMP[0]) + nXMPSize);
679
0
                    if ((abyData.size() % 2) != 0)  // Payload padding if needed
680
0
                        abyData.push_back(0);
681
682
                    // Patch size of RIFF
683
0
                    uint32_t nSize32 =
684
0
                        static_cast<uint32_t>(abyData.size()) - 8;
685
0
                    CPL_LSBPTR32(&nSize32);
686
0
                    memcpy(abyData.data() + 4, &nSize32, 4);
687
0
                }
688
0
            }
689
0
            catch (const std::exception &e)
690
0
            {
691
0
                CPLError(CE_Failure, CPLE_AppDefined, "Exception occurred: %s",
692
0
                         e.what());
693
0
                abyData.clear();
694
0
            }
695
0
            VSIFree(pWEBPContent);
696
697
0
            if (!abyData.empty())
698
0
            {
699
0
                VSILFILE *fpImage = VSIFOpenL(pszFilename, "wb");
700
0
                if (fpImage == nullptr)
701
0
                {
702
0
                    CPLError(CE_Failure, CPLE_OpenFailed,
703
0
                             "Unable to create jpeg file %s.", pszFilename);
704
705
0
                    return nullptr;
706
0
                }
707
0
                if (VSIFWriteL(abyData.data(), 1, abyData.size(), fpImage) !=
708
0
                    abyData.size())
709
0
                {
710
0
                    CPLError(CE_Failure, CPLE_FileIO,
711
0
                             "Failure writing data: %s", VSIStrerror(errno));
712
0
                    VSIFCloseL(fpImage);
713
0
                    return nullptr;
714
0
                }
715
0
                if (VSIFCloseL(fpImage) != 0)
716
0
                {
717
0
                    CPLError(CE_Failure, CPLE_FileIO,
718
0
                             "Failure writing data: %s", VSIStrerror(errno));
719
0
                    return nullptr;
720
0
                }
721
722
0
                pfnProgress(1.0, nullptr, pProgressData);
723
724
                // Re-open file and clone missing info to PAM
725
0
                GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
726
0
                auto poDS = OpenPAM(&oOpenInfo);
727
0
                if (poDS)
728
0
                {
729
0
                    poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
730
0
                }
731
732
0
                return poDS;
733
0
            }
734
0
        }
735
0
    }
736
737
0
    const bool bLossless = CPLFetchBool(papszOptions, "LOSSLESS", false);
738
0
    if (!bLossless &&
739
0
        (!EQUAL(pszLossLessCopy, "AUTO") && CPLTestBool(pszLossLessCopy)))
740
0
    {
741
0
        CPLError(CE_Failure, CPLE_AppDefined,
742
0
                 "LOSSLESS_COPY=YES requested but not possible");
743
0
        return nullptr;
744
0
    }
745
746
    /* -------------------------------------------------------------------- */
747
    /*      WEBP library initialization                                     */
748
    /* -------------------------------------------------------------------- */
749
750
0
    WebPPicture sPicture;
751
0
    if (!WebPPictureInit(&sPicture))
752
0
    {
753
0
        CPLError(CE_Failure, CPLE_AppDefined, "WebPPictureInit() failed");
754
0
        return nullptr;
755
0
    }
756
757
    /* -------------------------------------------------------------------- */
758
    /*      Some some rudimentary checks                                    */
759
    /* -------------------------------------------------------------------- */
760
761
0
    const int nXSize = poSrcDS->GetRasterXSize();
762
0
    const int nYSize = poSrcDS->GetRasterYSize();
763
0
    if (nXSize > 16383 || nYSize > 16383)
764
0
    {
765
0
        CPLError(CE_Failure, CPLE_NotSupported,
766
0
                 "WEBP maximum image dimensions are 16383 x 16383.");
767
768
0
        return nullptr;
769
0
    }
770
771
0
    const int nBands = poSrcDS->GetRasterCount();
772
0
    if (nBands != 3
773
0
#if WEBP_ENCODER_ABI_VERSION >= 0x0100
774
0
        && nBands != 4
775
0
#endif
776
0
    )
777
0
    {
778
0
        CPLError(CE_Failure, CPLE_NotSupported,
779
0
                 "WEBP driver doesn't support %d bands. Must be 3 (RGB) "
780
0
#if WEBP_ENCODER_ABI_VERSION >= 0x0100
781
0
                 "or 4 (RGBA) "
782
0
#endif
783
0
                 "bands.",
784
0
                 nBands);
785
786
0
        return nullptr;
787
0
    }
788
789
0
    const GDALDataType eDT = poSrcDS->GetRasterBand(1)->GetRasterDataType();
790
791
0
    if (eDT != GDT_Byte)
792
0
    {
793
0
        CPLError((bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
794
0
                 "WEBP driver doesn't support data type %s. "
795
0
                 "Only eight bit byte bands supported.",
796
0
                 GDALGetDataTypeName(
797
0
                     poSrcDS->GetRasterBand(1)->GetRasterDataType()));
798
799
0
        if (bStrict)
800
0
            return nullptr;
801
0
    }
802
803
    /* -------------------------------------------------------------------- */
804
    /*      What options has the user selected?                             */
805
    /* -------------------------------------------------------------------- */
806
0
    float fQuality = 75.0f;
807
0
    const char *pszQUALITY = CSLFetchNameValue(papszOptions, "QUALITY");
808
0
    if (pszQUALITY != nullptr)
809
0
    {
810
0
        fQuality = static_cast<float>(CPLAtof(pszQUALITY));
811
0
        if (fQuality < 0.0f || fQuality > 100.0f)
812
0
        {
813
0
            CPLError(CE_Failure, CPLE_IllegalArg, "%s=%s is not a legal value.",
814
0
                     "QUALITY", pszQUALITY);
815
0
            return nullptr;
816
0
        }
817
0
    }
818
819
0
    WebPPreset nPreset = WEBP_PRESET_DEFAULT;
820
0
    const char *pszPRESET =
821
0
        CSLFetchNameValueDef(papszOptions, "PRESET", "DEFAULT");
822
0
    if (EQUAL(pszPRESET, "DEFAULT"))
823
0
        nPreset = WEBP_PRESET_DEFAULT;
824
0
    else if (EQUAL(pszPRESET, "PICTURE"))
825
0
        nPreset = WEBP_PRESET_PICTURE;
826
0
    else if (EQUAL(pszPRESET, "PHOTO"))
827
0
        nPreset = WEBP_PRESET_PHOTO;
828
0
    else if (EQUAL(pszPRESET, "PICTURE"))
829
0
        nPreset = WEBP_PRESET_PICTURE;
830
0
    else if (EQUAL(pszPRESET, "DRAWING"))
831
0
        nPreset = WEBP_PRESET_DRAWING;
832
0
    else if (EQUAL(pszPRESET, "ICON"))
833
0
        nPreset = WEBP_PRESET_ICON;
834
0
    else if (EQUAL(pszPRESET, "TEXT"))
835
0
        nPreset = WEBP_PRESET_TEXT;
836
0
    else
837
0
    {
838
0
        CPLError(CE_Failure, CPLE_IllegalArg, "%s=%s is not a legal value.",
839
0
                 "PRESET", pszPRESET);
840
0
        return nullptr;
841
0
    }
842
843
0
    WebPConfig sConfig;
844
0
    if (!WebPConfigInitInternal(&sConfig, nPreset, fQuality,
845
0
                                WEBP_ENCODER_ABI_VERSION))
846
0
    {
847
0
        CPLError(CE_Failure, CPLE_AppDefined, "WebPConfigInit() failed");
848
0
        return nullptr;
849
0
    }
850
851
    // TODO: Get rid of this macro in a reasonable way.
852
0
#define FETCH_AND_SET_OPTION_INT(name, fieldname, minval, maxval)              \
853
0
    {                                                                          \
854
0
        const char *pszVal = CSLFetchNameValue(papszOptions, name);            \
855
0
        if (pszVal != nullptr)                                                 \
856
0
        {                                                                      \
857
0
            sConfig.fieldname = atoi(pszVal);                                  \
858
0
            if (sConfig.fieldname < minval || sConfig.fieldname > maxval)      \
859
0
            {                                                                  \
860
0
                CPLError(CE_Failure, CPLE_IllegalArg,                          \
861
0
                         "%s=%s is not a legal value.", name, pszVal);         \
862
0
                return nullptr;                                                \
863
0
            }                                                                  \
864
0
        }                                                                      \
865
0
    }
866
867
0
    FETCH_AND_SET_OPTION_INT("TARGETSIZE", target_size, 0, INT_MAX - 1);
868
869
0
    const char *pszPSNR = CSLFetchNameValue(papszOptions, "PSNR");
870
0
    if (pszPSNR)
871
0
    {
872
0
        sConfig.target_PSNR = static_cast<float>(CPLAtof(pszPSNR));
873
0
        if (sConfig.target_PSNR < 0)
874
0
        {
875
0
            CPLError(CE_Failure, CPLE_IllegalArg,
876
0
                     "PSNR=%s is not a legal value.", pszPSNR);
877
0
            return nullptr;
878
0
        }
879
0
    }
880
881
0
    FETCH_AND_SET_OPTION_INT("METHOD", method, 0, 6);
882
0
    FETCH_AND_SET_OPTION_INT("SEGMENTS", segments, 1, 4);
883
0
    FETCH_AND_SET_OPTION_INT("SNS_STRENGTH", sns_strength, 0, 100);
884
0
    FETCH_AND_SET_OPTION_INT("FILTER_STRENGTH", filter_strength, 0, 100);
885
0
    FETCH_AND_SET_OPTION_INT("FILTER_SHARPNESS", filter_sharpness, 0, 7);
886
0
    FETCH_AND_SET_OPTION_INT("FILTER_TYPE", filter_type, 0, 1);
887
0
    FETCH_AND_SET_OPTION_INT("AUTOFILTER", autofilter, 0, 1);
888
0
    FETCH_AND_SET_OPTION_INT("PASS", pass, 1, 10);
889
0
    FETCH_AND_SET_OPTION_INT("PREPROCESSING", preprocessing, 0, 1);
890
0
    FETCH_AND_SET_OPTION_INT("PARTITIONS", partitions, 0, 3);
891
0
#if WEBP_ENCODER_ABI_VERSION >= 0x0002
892
0
    FETCH_AND_SET_OPTION_INT("PARTITION_LIMIT", partition_limit, 0, 100);
893
0
#endif
894
0
#if WEBP_ENCODER_ABI_VERSION >= 0x0100
895
0
    sConfig.lossless = bLossless;
896
0
    if (sConfig.lossless)
897
0
        sPicture.use_argb = 1;
898
0
#endif
899
0
#if WEBP_ENCODER_ABI_VERSION >= 0x0209
900
0
    FETCH_AND_SET_OPTION_INT("EXACT", exact, 0, 1);
901
0
#endif
902
903
0
    if (!WebPValidateConfig(&sConfig))
904
0
    {
905
0
        CPLError(CE_Failure, CPLE_AppDefined, "WebPValidateConfig() failed");
906
0
        return nullptr;
907
0
    }
908
909
    /* -------------------------------------------------------------------- */
910
    /*      Allocate memory                                                 */
911
    /* -------------------------------------------------------------------- */
912
0
    GByte *pabyBuffer =
913
0
        static_cast<GByte *>(VSI_MALLOC3_VERBOSE(nBands, nXSize, nYSize));
914
0
    if (pabyBuffer == nullptr)
915
0
    {
916
0
        return nullptr;
917
0
    }
918
919
    /* -------------------------------------------------------------------- */
920
    /*      Create the dataset.                                             */
921
    /* -------------------------------------------------------------------- */
922
0
    VSILFILE *fpImage = VSIFOpenL(pszFilename, "wb");
923
0
    if (fpImage == nullptr)
924
0
    {
925
0
        CPLError(CE_Failure, CPLE_OpenFailed,
926
0
                 "Unable to create WEBP file %s.\n", pszFilename);
927
0
        VSIFree(pabyBuffer);
928
0
        return nullptr;
929
0
    }
930
931
0
    WebPUserData sUserData;
932
0
    sUserData.fp = fpImage;
933
0
    sUserData.pfnProgress = pfnProgress ? pfnProgress : GDALDummyProgress;
934
0
    sUserData.pProgressData = pProgressData;
935
936
    /* -------------------------------------------------------------------- */
937
    /*      WEBP library settings                                           */
938
    /* -------------------------------------------------------------------- */
939
940
0
    sPicture.width = nXSize;
941
0
    sPicture.height = nYSize;
942
0
    sPicture.writer = WEBPDatasetWriter;
943
0
    sPicture.custom_ptr = &sUserData;
944
0
#if WEBP_ENCODER_ABI_VERSION >= 0x0100
945
0
    sPicture.progress_hook = WEBPDatasetProgressHook;
946
0
#endif
947
0
    if (!WebPPictureAlloc(&sPicture))
948
0
    {
949
0
        CPLError(CE_Failure, CPLE_AppDefined, "WebPPictureAlloc() failed");
950
0
        VSIFree(pabyBuffer);
951
0
        VSIFCloseL(fpImage);
952
0
        return nullptr;
953
0
    }
954
955
    /* -------------------------------------------------------------------- */
956
    /*      Acquire source imagery.                                         */
957
    /* -------------------------------------------------------------------- */
958
0
    CPLErr eErr =
959
0
        poSrcDS->RasterIO(GF_Read, 0, 0, nXSize, nYSize, pabyBuffer, nXSize,
960
0
                          nYSize, GDT_Byte, nBands, nullptr, nBands,
961
0
                          static_cast<GSpacing>(nBands) * nXSize, 1, nullptr);
962
963
/* -------------------------------------------------------------------- */
964
/*      Import and write to file                                        */
965
/* -------------------------------------------------------------------- */
966
0
#if WEBP_ENCODER_ABI_VERSION >= 0x0100
967
0
    if (eErr == CE_None && nBands == 4)
968
0
    {
969
0
        if (!WebPPictureImportRGBA(&sPicture, pabyBuffer, nBands * nXSize))
970
0
        {
971
0
            CPLError(CE_Failure, CPLE_AppDefined,
972
0
                     "WebPPictureImportRGBA() failed");
973
0
            eErr = CE_Failure;
974
0
        }
975
0
    }
976
0
    else
977
0
#endif
978
0
        if (eErr == CE_None &&
979
0
            !WebPPictureImportRGB(&sPicture, pabyBuffer, nBands * nXSize))
980
0
    {
981
0
        CPLError(CE_Failure, CPLE_AppDefined, "WebPPictureImportRGB() failed");
982
0
        eErr = CE_Failure;
983
0
    }
984
985
0
    if (eErr == CE_None && !WebPEncode(&sConfig, &sPicture))
986
0
    {
987
0
#if WEBP_ENCODER_ABI_VERSION >= 0x0100
988
0
        const char *pszErrorMsg = nullptr;
989
0
        switch (sPicture.error_code)
990
0
        {
991
0
            case VP8_ENC_ERROR_OUT_OF_MEMORY:
992
0
                pszErrorMsg = "Out of memory";
993
0
                break;
994
0
            case VP8_ENC_ERROR_BITSTREAM_OUT_OF_MEMORY:
995
0
                pszErrorMsg = "Out of memory while flushing bits";
996
0
                break;
997
0
            case VP8_ENC_ERROR_NULL_PARAMETER:
998
0
                pszErrorMsg = "A pointer parameter is NULL";
999
0
                break;
1000
0
            case VP8_ENC_ERROR_INVALID_CONFIGURATION:
1001
0
                pszErrorMsg = "Configuration is invalid";
1002
0
                break;
1003
0
            case VP8_ENC_ERROR_BAD_DIMENSION:
1004
0
                pszErrorMsg = "Picture has invalid width/height";
1005
0
                break;
1006
0
            case VP8_ENC_ERROR_PARTITION0_OVERFLOW:
1007
0
                pszErrorMsg = "Partition is bigger than 512k. Try using less "
1008
0
                              "SEGMENTS, or increase PARTITION_LIMIT value";
1009
0
                break;
1010
0
            case VP8_ENC_ERROR_PARTITION_OVERFLOW:
1011
0
                pszErrorMsg = "Partition is bigger than 16M";
1012
0
                break;
1013
0
            case VP8_ENC_ERROR_BAD_WRITE:
1014
0
                pszErrorMsg = "Error while flushing bytes";
1015
0
                break;
1016
0
            case VP8_ENC_ERROR_FILE_TOO_BIG:
1017
0
                pszErrorMsg = "File is bigger than 4G";
1018
0
                break;
1019
0
            case VP8_ENC_ERROR_USER_ABORT:
1020
0
                pszErrorMsg = "User interrupted";
1021
0
                break;
1022
0
            default:
1023
0
                CPLError(CE_Failure, CPLE_AppDefined,
1024
0
                         "WebPEncode returned an unknown error code: %d",
1025
0
                         sPicture.error_code);
1026
0
                pszErrorMsg = "Unknown WebP error type.";
1027
0
                break;
1028
0
        }
1029
0
        CPLError(CE_Failure, CPLE_AppDefined, "WebPEncode() failed : %s",
1030
0
                 pszErrorMsg);
1031
#else
1032
        CPLError(CE_Failure, CPLE_AppDefined, "WebPEncode() failed");
1033
#endif
1034
0
        eErr = CE_Failure;
1035
0
    }
1036
1037
    /* -------------------------------------------------------------------- */
1038
    /*      Cleanup and close.                                              */
1039
    /* -------------------------------------------------------------------- */
1040
0
    CPLFree(pabyBuffer);
1041
1042
0
    WebPPictureFree(&sPicture);
1043
1044
0
    VSIFCloseL(fpImage);
1045
1046
0
    if (pfnProgress)
1047
0
        pfnProgress(1.0, "", pProgressData);
1048
1049
0
    if (eErr != CE_None)
1050
0
    {
1051
0
        VSIUnlink(pszFilename);
1052
0
        return nullptr;
1053
0
    }
1054
1055
    /* -------------------------------------------------------------------- */
1056
    /*      Re-open dataset, and copy any auxiliary pam information.        */
1057
    /* -------------------------------------------------------------------- */
1058
0
    GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
1059
1060
    /* If writing to stdout, we can't reopen it, so return */
1061
    /* a fake dataset to make the caller happy */
1062
0
    CPLPushErrorHandler(CPLQuietErrorHandler);
1063
0
    auto poDS = WEBPDataset::OpenPAM(&oOpenInfo);
1064
0
    CPLPopErrorHandler();
1065
0
    if (poDS)
1066
0
    {
1067
0
        poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT);
1068
0
        return poDS;
1069
0
    }
1070
1071
0
    return nullptr;
1072
0
}
1073
1074
/************************************************************************/
1075
/*                         GDALRegister_WEBP()                          */
1076
/************************************************************************/
1077
1078
void GDALRegister_WEBP()
1079
1080
22
{
1081
22
    if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
1082
0
        return;
1083
1084
22
    GDALDriver *poDriver = new GDALDriver();
1085
22
    WEBPDriverSetCommonMetadata(poDriver);
1086
1087
22
    poDriver->pfnOpen = WEBPDataset::Open;
1088
22
    poDriver->pfnCreateCopy = WEBPDataset::CreateCopy;
1089
1090
22
    GetGDALDriverManager()->RegisterDriver(poDriver);
1091
22
}