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