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