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