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