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