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