Add lock for image loading
[platform/core/uifw/dali-adaptor.git] / dali / internal / imaging / common / webp-loading.cpp
1 /*
2  * Copyright (c) 2021 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_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 typedef unsigned char WebPByteType;
44
45 namespace Dali
46 {
47 namespace Internal
48 {
49 namespace Adaptor
50 {
51 namespace
52 {
53 #if defined(DEBUG_ENABLED)
54 Debug::Filter* gWebPLoadingLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_GIF_LOADING");
55 #endif
56
57 constexpr size_t MAXIMUM_DOWNLOAD_IMAGE_SIZE = 50 * 1024 * 1024;
58
59 } // namespace
60
61 struct WebPLoading::Impl
62 {
63 public:
64   Impl(const std::string& url, bool isLocalResource)
65   : mUrl(url),
66     mLoadSucceeded(true),
67     mMutex()
68   {
69 #ifdef DALI_WEBP_ENABLED
70     if(ReadWebPInformation(isLocalResource))
71     {
72       WebPAnimDecoderOptions webPAnimDecoderOptions;
73       WebPAnimDecoderOptionsInit(&webPAnimDecoderOptions);
74       webPAnimDecoderOptions.color_mode = MODE_RGBA;
75       mWebPAnimDecoder                  = WebPAnimDecoderNew(&mWebPData, &webPAnimDecoderOptions);
76       WebPAnimDecoderGetInfo(mWebPAnimDecoder, &mWebPAnimInfo);
77       mTimeStamp.assign(mWebPAnimInfo.frame_count, 0);
78     }
79     else
80     {
81       mLoadSucceeded = false;
82     }
83 #endif
84   }
85
86   bool ReadWebPInformation(bool isLocalResource)
87   {
88 #ifdef DALI_WEBP_ENABLED
89     WebPDataInit(&mWebPData);
90     if(isLocalResource)
91     {
92       Internal::Platform::FileReader fileReader(mUrl);
93       FILE*                          fp = fileReader.GetFile();
94       if(fp == NULL)
95       {
96         return false;
97       }
98
99       if(fseek(fp, 0, SEEK_END) <= -1)
100       {
101         return false;
102       }
103
104       mWebPData.size = ftell(fp);
105       if((!fseek(fp, 0, SEEK_SET)))
106       {
107         unsigned char* WebPDataBuffer;
108         WebPDataBuffer  = reinterpret_cast<WebPByteType*>(malloc(sizeof(WebPByteType) * mWebPData.size));
109         mWebPData.size  = fread(WebPDataBuffer, sizeof(WebPByteType), mWebPData.size, fp);
110         mWebPData.bytes = WebPDataBuffer;
111       }
112       else
113       {
114         return false;
115       }
116     }
117     else
118     {
119       // remote file
120       bool                  succeeded;
121       Dali::Vector<uint8_t> dataBuffer;
122       size_t                dataSize;
123
124       succeeded = TizenPlatform::Network::DownloadRemoteFileIntoMemory(mUrl, dataBuffer, dataSize, MAXIMUM_DOWNLOAD_IMAGE_SIZE);
125       if(succeeded)
126       {
127         size_t blobSize = dataBuffer.Size();
128         if(blobSize > 0U)
129         {
130           // Open a file handle on the memory buffer:
131           Dali::Internal::Platform::FileReader fileReader(dataBuffer, blobSize);
132           FILE* const                          fp = fileReader.GetFile();
133           if(NULL != fp)
134           {
135             if((!fseek(fp, 0, SEEK_SET)))
136             {
137               unsigned char* WebPDataBuffer;
138               WebPDataBuffer  = reinterpret_cast<WebPByteType*>(malloc(sizeof(WebPByteType) * blobSize));
139               mWebPData.size  = fread(WebPDataBuffer, sizeof(WebPByteType), mWebPData.size, fp);
140               mWebPData.bytes = WebPDataBuffer;
141             }
142             else
143             {
144               DALI_LOG_ERROR("Error seeking within file\n");
145             }
146           }
147           else
148           {
149             DALI_LOG_ERROR("Error reading file\n");
150           }
151         }
152       }
153     }
154     return true;
155 #else
156     return false;
157 #endif
158   }
159
160   // Moveable but not copyable
161
162   Impl(const Impl&) = delete;
163   Impl& operator=(const Impl&) = delete;
164   Impl(Impl&&)                 = default;
165   Impl& operator=(Impl&&) = default;
166
167   ~Impl()
168   {
169 #ifdef DALI_WEBP_ENABLED
170     if(&mWebPData != NULL)
171     {
172       free((void*)mWebPData.bytes);
173       mWebPData.bytes = nullptr;
174       WebPDataInit(&mWebPData);
175     }
176     if(mWebPAnimDecoder)
177     {
178       WebPAnimDecoderDelete(mWebPAnimDecoder);
179     }
180 #endif
181   }
182
183   std::string           mUrl;
184   std::vector<uint32_t> mTimeStamp;
185   uint32_t              mLoadingFrame{0};
186   bool                  mLoadSucceeded;
187   Mutex                 mMutex;
188
189 #ifdef DALI_WEBP_ENABLED
190   WebPData         mWebPData{0};
191   WebPAnimDecoder* mWebPAnimDecoder{nullptr};
192   WebPAnimInfo     mWebPAnimInfo{0};
193 #endif
194 };
195
196 AnimatedImageLoadingPtr WebPLoading::New(const std::string& url, bool isLocalResource)
197 {
198 #ifndef DALI_WEBP_ENABLED
199   DALI_LOG_ERROR("The system does not support Animated WebP format.\n");
200 #endif
201   return AnimatedImageLoadingPtr(new WebPLoading(url, isLocalResource));
202 }
203
204 WebPLoading::WebPLoading(const std::string& url, bool isLocalResource)
205 : mImpl(new WebPLoading::Impl(url, isLocalResource))
206 {
207 }
208
209 WebPLoading::~WebPLoading()
210 {
211   delete mImpl;
212 }
213
214 bool WebPLoading::LoadNextNFrames(uint32_t frameStartIndex, int count, std::vector<Dali::PixelData>& pixelData)
215 {
216 #ifdef DALI_WEBP_ENABLED
217   Mutex::ScopedLock lock(mImpl->mMutex);
218   if(frameStartIndex >= mImpl->mWebPAnimInfo.frame_count || !mImpl->mLoadSucceeded)
219   {
220     return false;
221   }
222
223   DALI_LOG_INFO(gWebPLoadingLogFilter, Debug::Concise, "LoadNextNFrames( frameStartIndex:%d, count:%d )\n", frameStartIndex, count);
224
225   if(mImpl->mLoadingFrame > frameStartIndex)
226   {
227     mImpl->mLoadingFrame = 0;
228     WebPAnimDecoderReset(mImpl->mWebPAnimDecoder);
229   }
230
231   for(; mImpl->mLoadingFrame < frameStartIndex; ++mImpl->mLoadingFrame)
232   {
233     uint8_t* frameBuffer;
234     int      timestamp;
235     WebPAnimDecoderGetNext(mImpl->mWebPAnimDecoder, &frameBuffer, &timestamp);
236     mImpl->mTimeStamp[mImpl->mLoadingFrame] = timestamp;
237   }
238
239   for(int i = 0; i < count; ++i)
240   {
241     const int bufferSize = mImpl->mWebPAnimInfo.canvas_width * mImpl->mWebPAnimInfo.canvas_height * sizeof(uint32_t);
242     uint8_t*  frameBuffer;
243     int       timestamp;
244     WebPAnimDecoderGetNext(mImpl->mWebPAnimDecoder, &frameBuffer, &timestamp);
245
246     auto pixelBuffer = new uint8_t[bufferSize];
247     memcpy(pixelBuffer, frameBuffer, bufferSize);
248     mImpl->mTimeStamp[mImpl->mLoadingFrame] = timestamp;
249
250     if(pixelBuffer)
251     {
252       pixelData.push_back(Dali::PixelData::New(pixelBuffer, bufferSize, mImpl->mWebPAnimInfo.canvas_width, mImpl->mWebPAnimInfo.canvas_height, Dali::Pixel::RGBA8888, Dali::PixelData::DELETE_ARRAY));
253     }
254
255     mImpl->mLoadingFrame++;
256     if(mImpl->mLoadingFrame >= mImpl->mWebPAnimInfo.frame_count)
257     {
258       mImpl->mLoadingFrame = 0;
259       WebPAnimDecoderReset(mImpl->mWebPAnimDecoder);
260     }
261   }
262
263   return true;
264 #else
265   return false;
266 #endif
267 }
268
269 Dali::Devel::PixelBuffer WebPLoading::LoadFrame(uint32_t frameIndex)
270 {
271   Dali::Devel::PixelBuffer pixelBuffer;
272
273 #ifdef DALI_WEBP_ENABLED
274   Mutex::ScopedLock lock(mImpl->mMutex);
275   if(frameIndex >= mImpl->mWebPAnimInfo.frame_count || !mImpl->mLoadSucceeded)
276   {
277     return pixelBuffer;
278   }
279
280   DALI_LOG_INFO(gWebPLoadingLogFilter, Debug::Concise, "LoadFrame( frameIndex:%d )\n", frameIndex);
281
282   if(mImpl->mLoadingFrame > frameIndex)
283   {
284     mImpl->mLoadingFrame = 0;
285     WebPAnimDecoderReset(mImpl->mWebPAnimDecoder);
286   }
287
288   for(; mImpl->mLoadingFrame < frameIndex; ++mImpl->mLoadingFrame)
289   {
290     uint8_t* frameBuffer;
291     int      timestamp;
292     WebPAnimDecoderGetNext(mImpl->mWebPAnimDecoder, &frameBuffer, &timestamp);
293     mImpl->mTimeStamp[mImpl->mLoadingFrame] = timestamp;
294   }
295
296   const int bufferSize = mImpl->mWebPAnimInfo.canvas_width * mImpl->mWebPAnimInfo.canvas_height * sizeof(uint32_t);
297   uint8_t*  frameBuffer;
298   int       timestamp;
299   WebPAnimDecoderGetNext(mImpl->mWebPAnimDecoder, &frameBuffer, &timestamp);
300
301   pixelBuffer = Dali::Devel::PixelBuffer::New(mImpl->mWebPAnimInfo.canvas_width, mImpl->mWebPAnimInfo.canvas_height, Dali::Pixel::RGBA8888);
302   memcpy(pixelBuffer.GetBuffer(), frameBuffer, bufferSize);
303   mImpl->mTimeStamp[mImpl->mLoadingFrame] = timestamp;
304
305   mImpl->mLoadingFrame++;
306   if(mImpl->mLoadingFrame >= mImpl->mWebPAnimInfo.frame_count)
307   {
308     mImpl->mLoadingFrame = 0;
309     WebPAnimDecoderReset(mImpl->mWebPAnimDecoder);
310   }
311 #endif
312   return pixelBuffer;
313 }
314
315 ImageDimensions WebPLoading::GetImageSize() const
316 {
317 #ifdef DALI_WEBP_ENABLED
318   return ImageDimensions(mImpl->mWebPAnimInfo.canvas_width, mImpl->mWebPAnimInfo.canvas_height);
319 #else
320   return ImageDimensions();
321 #endif
322 }
323
324 uint32_t WebPLoading::GetImageCount() const
325 {
326 #ifdef DALI_WEBP_ENABLED
327   return mImpl->mWebPAnimInfo.frame_count;
328 #else
329   return 0u;
330 #endif
331 }
332
333 uint32_t WebPLoading::GetFrameInterval(uint32_t frameIndex) const
334 {
335   // If frameIndex is above the value of ImageCount or current frame is not loading yet, return 0u.
336   if(frameIndex >= GetImageCount() || (frameIndex > 0 && mImpl->mTimeStamp[frameIndex - 1] > mImpl->mTimeStamp[frameIndex]))
337   {
338     return 0u;
339   }
340   else
341   {
342     if(frameIndex > 0)
343     {
344       return mImpl->mTimeStamp[frameIndex] - mImpl->mTimeStamp[frameIndex - 1];
345     }
346     return mImpl->mTimeStamp[frameIndex];
347   }
348 }
349
350 std::string WebPLoading::GetUrl() const
351 {
352   return mImpl->mUrl;
353 }
354
355 bool WebPLoading::HasLoadingSucceeded() const
356 {
357   return mImpl->mLoadSucceeded;
358 }
359
360 } // namespace Adaptor
361
362 } // namespace Internal
363
364 } // namespace Dali