Refactoring Animated image loading
[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
46 typedef unsigned char WebPByteType;
47
48 namespace Dali
49 {
50 namespace Internal
51 {
52 namespace Adaptor
53 {
54 namespace
55 {
56 #if defined(DEBUG_ENABLED)
57 Debug::Filter* gWebPLoadingLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_GIF_LOADING");
58 #endif
59
60 static constexpr int32_t INITIAL_INDEX               = -1;
61 static constexpr size_t  MAXIMUM_DOWNLOAD_IMAGE_SIZE = 50 * 1024 * 1024;
62
63 } // namespace
64
65 struct WebPLoading::Impl
66 {
67 public:
68   Impl(const std::string& url, bool isLocalResource)
69   : mUrl(url),
70     mFrameCount(1u),
71     mMutex(),
72     mBuffer(nullptr),
73     mBufferSize(0u),
74     mImageSize(),
75     mLoadSucceeded(false),
76     mIsLocalResource(isLocalResource)
77   {
78   }
79
80   bool LoadWebPInformation()
81   {
82     // Block to do not load this file again.
83     Mutex::ScopedLock lock(mMutex);
84     if(DALI_UNLIKELY(mLoadSucceeded))
85     {
86       return mLoadSucceeded;
87     }
88
89 #ifndef DALI_WEBP_AVAILABLE
90     // If the system doesn't support webp, loading will be failed.
91     mFrameCount    = 0u;
92     mLoadSucceeded = false;
93     return mLoadSucceeded;
94 #endif
95
96     // mFrameCount will be 1 if the input image is non-animated image or animated image with single frame.
97     if(DALI_LIKELY(ReadWebPInformation()))
98     {
99 #ifdef DALI_ANIMATED_WEBP_ENABLED
100       WebPDataInit(&mWebPData);
101       mWebPData.size  = mBufferSize;
102       mWebPData.bytes = mBuffer;
103       WebPAnimDecoderOptions webPAnimDecoderOptions;
104       WebPAnimDecoderOptionsInit(&webPAnimDecoderOptions);
105       webPAnimDecoderOptions.color_mode = MODE_RGBA;
106       mWebPAnimDecoder                  = WebPAnimDecoderNew(&mWebPData, &webPAnimDecoderOptions);
107       WebPAnimDecoderGetInfo(mWebPAnimDecoder, &mWebPAnimInfo);
108       mTimeStamp.assign(mWebPAnimInfo.frame_count, 0);
109       mFrameCount = mWebPAnimInfo.frame_count;
110       mImageSize  = ImageDimensions(mWebPAnimInfo.canvas_width, mWebPAnimInfo.canvas_height);
111 #elif DALI_WEBP_AVAILABLE
112       int32_t imageWidth, imageHeight;
113       if(WebPGetInfo(mBuffer, mBufferSize, &imageWidth, &imageHeight))
114       {
115         mImageSize = ImageDimensions(imageWidth, imageHeight);
116       }
117 #endif
118       mLoadSucceeded = true;
119     }
120     else
121     {
122       mFrameCount    = 0u;
123       mLoadSucceeded = false;
124       DALI_LOG_ERROR("Image loading failed for: \"%s\".\n", mUrl.c_str());
125     }
126
127     return mLoadSucceeded;
128   }
129
130   bool ReadWebPInformation()
131   {
132     FILE* fp = nullptr;
133     if(mIsLocalResource)
134     {
135       Internal::Platform::FileReader fileReader(mUrl);
136       fp = fileReader.GetFile();
137       if(DALI_UNLIKELY(fp == nullptr))
138       {
139         return false;
140       }
141
142       if(DALI_UNLIKELY(fseek(fp, 0, SEEK_END) <= -1))
143       {
144         return false;
145       }
146
147       mBufferSize = ftell(fp);
148       if(DALI_LIKELY(!fseek(fp, 0, SEEK_SET)))
149       {
150         mBuffer     = reinterpret_cast<WebPByteType*>(malloc(sizeof(WebPByteType) * mBufferSize));
151         mBufferSize = fread(mBuffer, sizeof(WebPByteType), mBufferSize, fp);
152         return true;
153       }
154     }
155     else
156     {
157       // remote file
158       bool                  succeeded;
159       Dali::Vector<uint8_t> dataBuffer;
160       size_t                dataSize;
161
162       succeeded = TizenPlatform::Network::DownloadRemoteFileIntoMemory(mUrl, dataBuffer, dataSize, MAXIMUM_DOWNLOAD_IMAGE_SIZE);
163       if(DALI_LIKELY(succeeded))
164       {
165         mBufferSize = dataBuffer.Size();
166         if(DALI_LIKELY(mBufferSize > 0U))
167         {
168           // Open a file handle on the memory buffer:
169           Internal::Platform::FileReader fileReader(dataBuffer, mBufferSize);
170           fp = fileReader.GetFile();
171           if(DALI_LIKELY(fp != nullptr))
172           {
173             if(DALI_LIKELY(!fseek(fp, 0, SEEK_SET)))
174             {
175               mBuffer     = reinterpret_cast<WebPByteType*>(malloc(sizeof(WebPByteType) * mBufferSize));
176               mBufferSize = fread(mBuffer, sizeof(WebPByteType), mBufferSize, fp);
177               return true;
178             }
179           }
180         }
181       }
182     }
183     return false;
184   }
185
186   void ReleaseResource()
187   {
188 #ifdef DALI_ANIMATED_WEBP_ENABLED
189     if(&mWebPData != nullptr)
190     {
191       mWebPData.bytes = nullptr;
192       WebPDataInit(&mWebPData);
193     }
194     if(mWebPAnimDecoder != nullptr)
195     {
196       WebPAnimDecoderDelete(mWebPAnimDecoder);
197       mWebPAnimDecoder = nullptr;
198     }
199 #endif
200     if(mBuffer != nullptr)
201     {
202       free((void*)mBuffer);
203       mBuffer = nullptr;
204     }
205   }
206
207   // Moveable but not copyable
208
209   Impl(const Impl&) = delete;
210   Impl& operator=(const Impl&) = delete;
211   Impl(Impl&&)                 = default;
212   Impl& operator=(Impl&&) = default;
213
214   ~Impl()
215   {
216     ReleaseResource();
217   }
218
219   std::string           mUrl;
220   std::vector<uint32_t> mTimeStamp;
221   int32_t               mLatestLoadedFrame{INITIAL_INDEX};
222   uint32_t              mFrameCount;
223   Mutex                 mMutex;
224   // For the case the system doesn't support DALI_ANIMATED_WEBP_ENABLED
225   unsigned char*  mBuffer;
226   uint32_t        mBufferSize;
227   ImageDimensions mImageSize;
228   bool            mLoadSucceeded;
229   bool            mIsLocalResource;
230
231 #ifdef DALI_ANIMATED_WEBP_ENABLED
232   WebPData                 mWebPData{0};
233   WebPAnimDecoder*         mWebPAnimDecoder{nullptr};
234   WebPAnimInfo             mWebPAnimInfo{0};
235   Dali::Devel::PixelBuffer mPreLoadedFrame{};
236 #endif
237 };
238
239 AnimatedImageLoadingPtr WebPLoading::New(const std::string& url, bool isLocalResource)
240 {
241 #ifndef DALI_ANIMATED_WEBP_ENABLED
242   DALI_LOG_ERROR("The system does not support Animated WebP format.\n");
243 #endif
244   return AnimatedImageLoadingPtr(new WebPLoading(url, isLocalResource));
245 }
246
247 WebPLoading::WebPLoading(const std::string& url, bool isLocalResource)
248 : mImpl(new WebPLoading::Impl(url, isLocalResource))
249 {
250 }
251
252 WebPLoading::~WebPLoading()
253 {
254   delete mImpl;
255 }
256
257 bool WebPLoading::LoadNextNFrames(uint32_t frameStartIndex, int count, std::vector<Dali::PixelData>& pixelData)
258 {
259   for(int i = 0; i < count; ++i)
260   {
261     Dali::Devel::PixelBuffer pixelBuffer = LoadFrame((frameStartIndex + i) % mImpl->mFrameCount);
262     if(DALI_UNLIKELY(!pixelBuffer))
263     {
264       return false;
265     }
266
267     Dali::PixelData imageData = Devel::PixelBuffer::Convert(pixelBuffer);
268     pixelData.push_back(imageData);
269   }
270   if(DALI_UNLIKELY(pixelData.size() != static_cast<uint32_t>(count)))
271   {
272     return false;
273   }
274   return true;
275 }
276
277 Dali::Devel::PixelBuffer WebPLoading::LoadFrame(uint32_t frameIndex)
278 {
279   Dali::Devel::PixelBuffer pixelBuffer;
280
281   // If WebP file is still not loaded, Load the information.
282   if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
283   {
284     if(DALI_UNLIKELY(!mImpl->LoadWebPInformation()))
285     {
286       return pixelBuffer;
287     }
288   }
289
290   // WebPDecodeRGBA is faster than to use demux API for loading non-animated image.
291   // If frame count is 1, use WebPDecodeRGBA api.
292 #ifdef DALI_WEBP_AVAILABLE
293   if(mImpl->mFrameCount == 1)
294   {
295     int32_t width, height;
296     if(DALI_UNLIKELY(!WebPGetInfo(mImpl->mBuffer, mImpl->mBufferSize, &width, &height)))
297     {
298       return pixelBuffer;
299     }
300
301     WebPBitstreamFeatures features;
302     if(DALI_UNLIKELY(VP8_STATUS_NOT_ENOUGH_DATA == WebPGetFeatures(mImpl->mBuffer, mImpl->mBufferSize, &features)))
303     {
304       return pixelBuffer;
305     }
306
307     uint32_t      channelNumber = (features.has_alpha) ? 4 : 3;
308     Pixel::Format pixelFormat   = (channelNumber == 4) ? Pixel::RGBA8888 : Pixel::RGB888;
309     pixelBuffer                 = Dali::Devel::PixelBuffer::New(width, height, pixelFormat);
310     uint8_t* frameBuffer        = nullptr;
311     if(channelNumber == 4)
312     {
313       frameBuffer = WebPDecodeRGBA(mImpl->mBuffer, mImpl->mBufferSize, &width, &height);
314     }
315     else
316     {
317       frameBuffer = WebPDecodeRGB(mImpl->mBuffer, mImpl->mBufferSize, &width, &height);
318     }
319
320     if(frameBuffer != nullptr)
321     {
322       const int32_t imageBufferSize = width * height * sizeof(uint8_t) * channelNumber;
323       memcpy(pixelBuffer.GetBuffer(), frameBuffer, imageBufferSize);
324       free((void*)frameBuffer);
325     }
326     mImpl->ReleaseResource();
327     return pixelBuffer;
328   }
329 #endif
330
331 #ifdef DALI_ANIMATED_WEBP_ENABLED
332   Mutex::ScopedLock lock(mImpl->mMutex);
333   if(DALI_UNLIKELY(frameIndex >= mImpl->mWebPAnimInfo.frame_count || !mImpl->mLoadSucceeded))
334   {
335     return pixelBuffer;
336   }
337
338   DALI_LOG_INFO(gWebPLoadingLogFilter, Debug::Concise, "LoadFrame( frameIndex:%d )\n", frameIndex);
339
340   if(mImpl->mPreLoadedFrame && mImpl->mLatestLoadedFrame == static_cast<int32_t>(frameIndex))
341   {
342     pixelBuffer = mImpl->mPreLoadedFrame;
343   }
344   else
345   {
346     pixelBuffer = DecodeFrame(frameIndex);
347   }
348   mImpl->mPreLoadedFrame.Reset();
349
350   // If time stamp of next frame is unknown, load a frame more to know it.
351   if(frameIndex + 1 < mImpl->mWebPAnimInfo.frame_count && mImpl->mTimeStamp[frameIndex + 1] == 0u)
352   {
353     mImpl->mPreLoadedFrame = DecodeFrame(frameIndex + 1);
354   }
355
356 #endif
357   return pixelBuffer;
358 }
359
360 Dali::Devel::PixelBuffer WebPLoading::DecodeFrame(uint32_t frameIndex)
361 {
362   Dali::Devel::PixelBuffer pixelBuffer;
363 #ifdef DALI_ANIMATED_WEBP_ENABLED
364   if(mImpl->mLatestLoadedFrame > static_cast<int32_t>(frameIndex))
365   {
366     mImpl->mLatestLoadedFrame = INITIAL_INDEX;
367     WebPAnimDecoderReset(mImpl->mWebPAnimDecoder);
368   }
369
370   uint8_t* frameBuffer;
371   int32_t timestamp;
372   for(; mImpl->mLatestLoadedFrame < static_cast<int32_t>(frameIndex);)
373   {
374     WebPAnimDecoderGetNext(mImpl->mWebPAnimDecoder, &frameBuffer, &timestamp);
375     mImpl->mTimeStamp[++mImpl->mLatestLoadedFrame] = timestamp;
376   }
377   const int bufferSize = mImpl->mWebPAnimInfo.canvas_width * mImpl->mWebPAnimInfo.canvas_height * sizeof(uint32_t);
378   pixelBuffer = Dali::Devel::PixelBuffer::New(mImpl->mWebPAnimInfo.canvas_width, mImpl->mWebPAnimInfo.canvas_height, Dali::Pixel::RGBA8888);
379   memcpy(pixelBuffer.GetBuffer(), frameBuffer, bufferSize);
380
381 #endif
382   return pixelBuffer;
383 }
384
385 ImageDimensions WebPLoading::GetImageSize() const
386 {
387   if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
388   {
389     mImpl->LoadWebPInformation();
390   }
391   return mImpl->mImageSize;
392 }
393
394 uint32_t WebPLoading::GetImageCount() const
395 {
396   if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
397   {
398     mImpl->LoadWebPInformation();
399   }
400   return mImpl->mFrameCount;
401 }
402
403 uint32_t WebPLoading::GetFrameInterval(uint32_t frameIndex) const
404 {
405   if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
406   {
407     DALI_LOG_ERROR("WebP file is still not loaded, this frame interval could not be correct value.\n");
408   }
409   if(frameIndex >= GetImageCount())
410   {
411     DALI_LOG_ERROR("Input frameIndex exceeded frame count of the WebP.");
412     return 0u;
413   }
414   else
415   {
416     int32_t interval = 0u;
417     if(GetImageCount() == 1u)
418     {
419       return 0u;
420     }
421     else if(frameIndex + 1 == GetImageCount())
422     {
423       // For the interval between last frame and first frame, use last interval again.
424       interval = mImpl->mTimeStamp[frameIndex] - mImpl->mTimeStamp[frameIndex - 1];
425     }
426     else
427     {
428       interval = mImpl->mTimeStamp[frameIndex + 1] - mImpl->mTimeStamp[frameIndex];
429     }
430
431     if(DALI_UNLIKELY(interval < 0))
432     {
433       DALI_LOG_ERROR("This interval value is not correct, because the frame still hasn't ever been decoded.");
434       return 0u;
435     }
436     return static_cast<uint32_t>(interval);
437   }
438 }
439
440 std::string WebPLoading::GetUrl() const
441 {
442   return mImpl->mUrl;
443 }
444
445 bool WebPLoading::HasLoadingSucceeded() const
446 {
447   return mImpl->mLoadSucceeded;
448 }
449
450 } // namespace Adaptor
451
452 } // namespace Internal
453
454 } // namespace Dali