Refactoring webp-loading and loader-webp
[platform/core/uifw/dali-adaptor.git] / dali / internal / imaging / common / webp-loading.cpp
1 /*
2  * Copyright (c) 2022 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali/internal/imaging/common/webp-loading.h>
20
21 // EXTERNAL INCLUDES
22 #ifdef DALI_WEBP_AVAILABLE
23 #include <webp/decode.h>
24 #include <webp/demux.h>
25
26 #if WEBP_DEMUX_ABI_VERSION > 0x0101
27 #define DALI_ANIMATED_WEBP_ENABLED 1
28 #endif
29
30 #endif
31 #include <dali/integration-api/debug.h>
32 #include <dali/public-api/images/pixel-data.h>
33
34 #include <dali/devel-api/threading/mutex.h>
35 #include <dali/internal/imaging/common/file-download.h>
36 #include <dali/internal/system/common/file-reader.h>
37 #include <fcntl.h>
38 #include <sys/stat.h>
39 #include <sys/types.h>
40 #include <unistd.h>
41 #include <cstring>
42
43 // INTERNAL INCLUDES
44 #include <dali/devel-api/adaptor-framework/image-loading.h>
45 #include <dali/internal/imaging/common/pixel-buffer-impl.h>
46
47 typedef uint8_t WebPByteType;
48
49 namespace Dali
50 {
51 namespace Internal
52 {
53 namespace Adaptor
54 {
55 namespace
56 {
57 #if defined(DEBUG_ENABLED)
58 Debug::Filter* gWebPLoadingLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_GIF_LOADING");
59 #endif
60
61 static constexpr int32_t INITIAL_INDEX               = -1;
62 static constexpr size_t  MAXIMUM_DOWNLOAD_IMAGE_SIZE = 50 * 1024 * 1024;
63
64 } // namespace
65
66 struct WebPLoading::Impl
67 {
68 public:
69   Impl(const std::string& url, bool isLocalResource)
70   : mFile(nullptr),
71     mUrl(url),
72     mFrameCount(1u),
73     mMutex(),
74     mBuffer(nullptr),
75     mBufferSize(0u),
76     mImageSize(),
77     mLoadSucceeded(false),
78     mIsAnimatedImage(false),
79     mIsLocalResource(isLocalResource)
80   {
81   }
82
83   Impl(FILE* const fp)
84   : mFile(fp),
85     mUrl(),
86     mFrameCount(1u),
87     mMutex(),
88     mBuffer(nullptr),
89     mBufferSize(0u),
90     mImageSize(),
91     mLoadSucceeded(false),
92     mIsAnimatedImage(false),
93     mIsLocalResource(true)
94   {
95   }
96
97   bool LoadWebPInformation()
98   {
99     // Block to do not load this file again.
100     Mutex::ScopedLock lock(mMutex);
101     if(DALI_UNLIKELY(mLoadSucceeded))
102     {
103       return mLoadSucceeded;
104     }
105
106 #ifndef DALI_WEBP_AVAILABLE
107     // If the system doesn't support webp, loading will be failed.
108     mFrameCount    = 0u;
109     mLoadSucceeded = false;
110     return mLoadSucceeded;
111 #endif
112
113     // mFrameCount will be 1 if the input image is non-animated image or animated image with single frame.
114     if(DALI_LIKELY(ReadWebPInformation()))
115     {
116 #ifdef DALI_ANIMATED_WEBP_ENABLED
117       if(mIsAnimatedImage)
118       {
119         WebPDataInit(&mWebPData);
120         mWebPData.size  = mBufferSize;
121         mWebPData.bytes = mBuffer;
122         WebPAnimDecoderOptions webPAnimDecoderOptions;
123         WebPAnimDecoderOptionsInit(&webPAnimDecoderOptions);
124         webPAnimDecoderOptions.color_mode = MODE_RGBA;
125         mWebPAnimDecoder                  = WebPAnimDecoderNew(&mWebPData, &webPAnimDecoderOptions);
126         WebPAnimDecoderGetInfo(mWebPAnimDecoder, &mWebPAnimInfo);
127         mTimeStamp.assign(mWebPAnimInfo.frame_count, 0);
128         mFrameCount = mWebPAnimInfo.frame_count;
129         mImageSize  = ImageDimensions(mWebPAnimInfo.canvas_width, mWebPAnimInfo.canvas_height);
130       }
131 #endif
132 #ifdef DALI_WEBP_AVAILABLE
133       if(!mIsAnimatedImage)
134       {
135         int32_t imageWidth, imageHeight;
136         if(WebPGetInfo(mBuffer, mBufferSize, &imageWidth, &imageHeight))
137         {
138           mImageSize = ImageDimensions(imageWidth, imageHeight);
139         }
140       }
141 #endif
142       mLoadSucceeded = true;
143     }
144     else
145     {
146       mFrameCount    = 0u;
147       mLoadSucceeded = false;
148       DALI_LOG_ERROR("Image loading failed for: \"%s\".\n", mUrl.c_str());
149     }
150
151     return mLoadSucceeded;
152   }
153
154   bool ReadWebPInformation()
155   {
156     mBufferSize = 0;
157
158     FILE*                                           fp = mFile;
159     std::unique_ptr<Internal::Platform::FileReader> fileReader;
160     Dali::Vector<uint8_t>                           dataBuffer;
161     if(fp == nullptr)
162     {
163       if(mIsLocalResource)
164       {
165         fileReader = std::make_unique<Internal::Platform::FileReader>(mUrl);
166       }
167       else
168       {
169         size_t dataSize;
170         if(DALI_LIKELY(TizenPlatform::Network::DownloadRemoteFileIntoMemory(mUrl, dataBuffer, dataSize, MAXIMUM_DOWNLOAD_IMAGE_SIZE)))
171         {
172           mBufferSize = dataBuffer.Size();
173           if(DALI_LIKELY(mBufferSize > 0U))
174           {
175             // Open a file handle on the memory buffer:
176             fileReader = std::make_unique<Internal::Platform::FileReader>(dataBuffer, mBufferSize);
177           }
178         }
179       }
180
181       fp = fileReader->GetFile();
182     }
183
184     if(DALI_LIKELY(fp != nullptr))
185     {
186       if(DALI_LIKELY(!fseek(fp, 12, SEEK_SET)))
187       {
188         uint8_t mHeaderBuffer[5];
189         fread(mHeaderBuffer, sizeof(WebPByteType), 5, fp);
190         unsigned char VP8X[4]    = {'V', 'P', '8', 'X'};
191         bool          isExtended = true;
192         for(uint32_t i = 0; i < 4; ++i)
193         {
194           if(mHeaderBuffer[i] != VP8X[i])
195           {
196             isExtended = false;
197             break;
198           }
199         }
200         if(isExtended)
201         {
202           unsigned char extension = mHeaderBuffer[4];
203           mIsAnimatedImage        = ((extension >> 1) & 1) ? true : false;
204         }
205       }
206
207       if(mBufferSize == 0)
208       {
209         if(DALI_UNLIKELY(fseek(fp, 0, SEEK_END) <= -1))
210         {
211           return false;
212         }
213         mBufferSize = ftell(fp);
214       }
215
216       if(DALI_LIKELY(!fseek(fp, 0, SEEK_SET)))
217       {
218         mBuffer     = reinterpret_cast<WebPByteType*>(malloc(sizeof(WebPByteType) * mBufferSize));
219         mBufferSize = fread(mBuffer, sizeof(WebPByteType), mBufferSize, fp);
220         return true;
221       }
222     }
223     return false;
224   }
225
226   void ReleaseResource()
227   {
228 #ifdef DALI_ANIMATED_WEBP_ENABLED
229     if(mIsAnimatedImage)
230     {
231       if(&mWebPData != nullptr)
232       {
233         mWebPData.bytes = nullptr;
234         WebPDataInit(&mWebPData);
235       }
236       if(mWebPAnimDecoder != nullptr)
237       {
238         WebPAnimDecoderDelete(mWebPAnimDecoder);
239         mWebPAnimDecoder = nullptr;
240       }
241     }
242 #endif
243     if(mBuffer != nullptr)
244     {
245       free((void*)mBuffer);
246       mBuffer = nullptr;
247     }
248   }
249
250   // Moveable but not copyable
251
252   Impl(const Impl&) = delete;
253   Impl& operator=(const Impl&) = delete;
254   Impl(Impl&&)                 = default;
255   Impl& operator=(Impl&&) = default;
256
257   ~Impl()
258   {
259     ReleaseResource();
260   }
261
262   FILE*                 mFile;
263   std::string           mUrl;
264   std::vector<uint32_t> mTimeStamp;
265   int32_t               mLatestLoadedFrame{INITIAL_INDEX};
266   uint32_t              mFrameCount;
267   Mutex                 mMutex;
268   // For the case the system doesn't support DALI_ANIMATED_WEBP_ENABLED
269   unsigned char*  mBuffer;
270   uint32_t        mBufferSize;
271   ImageDimensions mImageSize;
272   bool            mLoadSucceeded;
273   bool            mIsAnimatedImage;
274   bool            mIsLocalResource;
275
276 #ifdef DALI_ANIMATED_WEBP_ENABLED
277   WebPData                 mWebPData{0};
278   WebPAnimDecoder*         mWebPAnimDecoder{nullptr};
279   WebPAnimInfo             mWebPAnimInfo{0};
280   Dali::Devel::PixelBuffer mPreLoadedFrame{};
281 #endif
282 };
283
284 AnimatedImageLoadingPtr WebPLoading::New(const std::string& url, bool isLocalResource)
285 {
286 #ifndef DALI_ANIMATED_WEBP_ENABLED
287   DALI_LOG_ERROR("The system does not support Animated WebP format.\n");
288 #endif
289   return AnimatedImageLoadingPtr(new WebPLoading(url, isLocalResource));
290 }
291
292 AnimatedImageLoadingPtr WebPLoading::New(FILE* const fp)
293 {
294 #ifndef DALI_ANIMATED_WEBP_ENABLED
295   DALI_LOG_ERROR("The system does not support Animated WebP format.\n");
296 #endif
297   return AnimatedImageLoadingPtr(new WebPLoading(fp));
298 }
299
300 WebPLoading::WebPLoading(const std::string& url, bool isLocalResource)
301 : mImpl(new WebPLoading::Impl(url, isLocalResource))
302 {
303 }
304
305 WebPLoading::WebPLoading(FILE* const fp)
306 : mImpl(new WebPLoading::Impl(fp))
307 {
308 }
309
310 WebPLoading::~WebPLoading()
311 {
312   delete mImpl;
313 }
314
315 Dali::Devel::PixelBuffer WebPLoading::LoadFrame(uint32_t frameIndex)
316 {
317   Dali::Devel::PixelBuffer pixelBuffer;
318
319   // If WebP file is still not loaded, Load the information.
320   if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
321   {
322     if(DALI_UNLIKELY(!mImpl->LoadWebPInformation()))
323     {
324       mImpl->ReleaseResource();
325       return pixelBuffer;
326     }
327   }
328
329   // WebPDecodeRGBA is faster than to use demux API for loading non-animated image.
330   // If frame count is 1, use WebPDecodeRGBA api.
331 #ifdef DALI_WEBP_AVAILABLE
332   if(!mImpl->mIsAnimatedImage)
333   {
334     int32_t width, height;
335     if(DALI_LIKELY(WebPGetInfo(mImpl->mBuffer, mImpl->mBufferSize, &width, &height)))
336     {
337       WebPBitstreamFeatures features;
338       if(DALI_LIKELY(VP8_STATUS_NOT_ENOUGH_DATA != WebPGetFeatures(mImpl->mBuffer, mImpl->mBufferSize, &features)))
339       {
340         uint32_t channelNumber = (features.has_alpha) ? 4 : 3;
341         uint8_t* frameBuffer   = nullptr;
342         if(channelNumber == 4)
343         {
344           frameBuffer = WebPDecodeRGBA(mImpl->mBuffer, mImpl->mBufferSize, &width, &height);
345         }
346         else
347         {
348           frameBuffer = WebPDecodeRGB(mImpl->mBuffer, mImpl->mBufferSize, &width, &height);
349         }
350
351         if(frameBuffer != nullptr)
352         {
353           Pixel::Format                     pixelFormat = (channelNumber == 4) ? Pixel::RGBA8888 : Pixel::RGB888;
354           int32_t                           bufferSize  = width * height * Dali::Pixel::GetBytesPerPixel(pixelFormat);
355           Internal::Adaptor::PixelBufferPtr internal =
356             Internal::Adaptor::PixelBuffer::New(frameBuffer, bufferSize, width, height, width, pixelFormat);
357           pixelBuffer = Devel::PixelBuffer(internal.Get());
358         }
359       }
360     }
361     // The single frame resource should be released after loading.
362     mImpl->ReleaseResource();
363   }
364 #endif
365
366 #ifdef DALI_ANIMATED_WEBP_ENABLED
367   if(mImpl->mIsAnimatedImage && mImpl->mBuffer != nullptr)
368   {
369     Mutex::ScopedLock lock(mImpl->mMutex);
370     if(DALI_LIKELY(frameIndex < mImpl->mWebPAnimInfo.frame_count && mImpl->mLoadSucceeded))
371     {
372       DALI_LOG_INFO(gWebPLoadingLogFilter, Debug::Concise, "LoadFrame( frameIndex:%d )\n", frameIndex);
373
374       if(mImpl->mPreLoadedFrame && mImpl->mLatestLoadedFrame == static_cast<int32_t>(frameIndex))
375       {
376         pixelBuffer = mImpl->mPreLoadedFrame;
377       }
378       else
379       {
380         pixelBuffer = DecodeFrame(frameIndex);
381       }
382       mImpl->mPreLoadedFrame.Reset();
383
384       // If time stamp of next frame is unknown, load a frame more to know it.
385       if(frameIndex + 1 < mImpl->mWebPAnimInfo.frame_count && mImpl->mTimeStamp[frameIndex + 1] == 0u)
386       {
387         mImpl->mPreLoadedFrame = DecodeFrame(frameIndex + 1);
388       }
389     }
390     else
391     {
392       mImpl->ReleaseResource();
393     }
394   }
395 #endif
396   return pixelBuffer;
397 }
398
399 Dali::Devel::PixelBuffer WebPLoading::DecodeFrame(uint32_t frameIndex)
400 {
401   Dali::Devel::PixelBuffer pixelBuffer;
402 #ifdef DALI_ANIMATED_WEBP_ENABLED
403   if(mImpl->mLatestLoadedFrame > static_cast<int32_t>(frameIndex))
404   {
405     mImpl->mLatestLoadedFrame = INITIAL_INDEX;
406     WebPAnimDecoderReset(mImpl->mWebPAnimDecoder);
407   }
408
409   uint8_t* frameBuffer = nullptr;
410   int32_t  timestamp   = 0u;
411   for(; mImpl->mLatestLoadedFrame < static_cast<int32_t>(frameIndex);)
412   {
413     WebPAnimDecoderGetNext(mImpl->mWebPAnimDecoder, &frameBuffer, &timestamp);
414     mImpl->mTimeStamp[++mImpl->mLatestLoadedFrame] = timestamp;
415   }
416
417   if(frameBuffer != nullptr)
418   {
419     const int bufferSize = mImpl->mWebPAnimInfo.canvas_width * mImpl->mWebPAnimInfo.canvas_height * sizeof(uint32_t);
420     pixelBuffer          = Dali::Devel::PixelBuffer::New(mImpl->mWebPAnimInfo.canvas_width, mImpl->mWebPAnimInfo.canvas_height, Dali::Pixel::RGBA8888);
421     memcpy(pixelBuffer.GetBuffer(), frameBuffer, bufferSize);
422   }
423
424 #endif
425   return pixelBuffer;
426 }
427
428 ImageDimensions WebPLoading::GetImageSize() const
429 {
430   if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
431   {
432     mImpl->LoadWebPInformation();
433   }
434   return mImpl->mImageSize;
435 }
436
437 uint32_t WebPLoading::GetImageCount() const
438 {
439   if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
440   {
441     mImpl->LoadWebPInformation();
442   }
443   return mImpl->mFrameCount;
444 }
445
446 uint32_t WebPLoading::GetFrameInterval(uint32_t frameIndex) const
447 {
448   if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
449   {
450     DALI_LOG_ERROR("WebP file is still not loaded, this frame interval could not be correct value.\n");
451   }
452   if(frameIndex >= GetImageCount())
453   {
454     DALI_LOG_ERROR("Input frameIndex exceeded frame count of the WebP.");
455     return 0u;
456   }
457   else
458   {
459     int32_t interval = 0u;
460     if(GetImageCount() == 1u)
461     {
462       return 0u;
463     }
464     else if(frameIndex + 1 == GetImageCount())
465     {
466       // For the interval between last frame and first frame, use last interval again.
467       interval = mImpl->mTimeStamp[frameIndex] - mImpl->mTimeStamp[frameIndex - 1];
468     }
469     else
470     {
471       interval = mImpl->mTimeStamp[frameIndex + 1] - mImpl->mTimeStamp[frameIndex];
472     }
473
474     if(DALI_UNLIKELY(interval < 0))
475     {
476       DALI_LOG_ERROR("This interval value is not correct, because the frame still hasn't ever been decoded.");
477       return 0u;
478     }
479     return static_cast<uint32_t>(interval);
480   }
481 }
482
483 std::string WebPLoading::GetUrl() const
484 {
485   return mImpl->mUrl;
486 }
487
488 bool WebPLoading::HasLoadingSucceeded() const
489 {
490   return mImpl->mLoadSucceeded;
491 }
492
493 } // namespace Adaptor
494
495 } // namespace Internal
496
497 } // namespace Dali