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