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