Make non-animated webp file can be loaded.
[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 == NULL)
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   // Moveable but not copyable
169
170   Impl(const Impl&) = delete;
171   Impl& operator=(const Impl&) = delete;
172   Impl(Impl&&)                 = default;
173   Impl& operator=(Impl&&) = default;
174
175   ~Impl()
176   {
177 #ifdef DALI_ANIMATED_WEBP_ENABLED
178     if(&mWebPData != NULL)
179     {
180       mWebPData.bytes = nullptr;
181       WebPDataInit(&mWebPData);
182     }
183     if(mWebPAnimDecoder)
184     {
185       WebPAnimDecoderDelete(mWebPAnimDecoder);
186     }
187 #endif
188     free((void*)mBuffer);
189     mBuffer = nullptr;
190   }
191
192   std::string           mUrl;
193   std::vector<uint32_t> mTimeStamp;
194   uint32_t              mLoadingFrame{0};
195   uint32_t              mFrameCount;
196   Mutex                 mMutex;
197   // For the case the system doesn't support DALI_ANIMATED_WEBP_ENABLED
198   unsigned char*  mBuffer;
199   uint32_t        mBufferSize;
200   ImageDimensions mImageSize;
201   bool            mLoadSucceeded;
202
203 #ifdef DALI_ANIMATED_WEBP_ENABLED
204   WebPData         mWebPData{0};
205   WebPAnimDecoder* mWebPAnimDecoder{nullptr};
206   WebPAnimInfo     mWebPAnimInfo{0};
207 #endif
208 };
209
210 AnimatedImageLoadingPtr WebPLoading::New(const std::string& url, bool isLocalResource)
211 {
212 #ifndef DALI_ANIMATED_WEBP_ENABLED
213   DALI_LOG_ERROR("The system does not support Animated WebP format.\n");
214 #endif
215   return AnimatedImageLoadingPtr(new WebPLoading(url, isLocalResource));
216 }
217
218 WebPLoading::WebPLoading(const std::string& url, bool isLocalResource)
219 : mImpl(new WebPLoading::Impl(url, isLocalResource))
220 {
221 }
222
223 WebPLoading::~WebPLoading()
224 {
225   delete mImpl;
226 }
227
228 bool WebPLoading::LoadNextNFrames(uint32_t frameStartIndex, int count, std::vector<Dali::PixelData>& pixelData)
229 {
230   for(int i = 0; i < count; ++i)
231   {
232     Dali::Devel::PixelBuffer pixelBuffer = LoadFrame((frameStartIndex + i) % mImpl->mFrameCount);
233     Dali::PixelData          imageData   = Devel::PixelBuffer::Convert(pixelBuffer);
234     pixelData.push_back(imageData);
235   }
236   if(pixelData.size() != static_cast<uint32_t>(count))
237   {
238     return false;
239   }
240   return true;
241 }
242
243 Dali::Devel::PixelBuffer WebPLoading::LoadFrame(uint32_t frameIndex)
244 {
245   Dali::Devel::PixelBuffer pixelBuffer;
246
247   // WebPDecodeRGBA is faster than to use demux API for loading non-animated image.
248   // If frame count is 1, use WebPDecodeRGBA api.
249 #ifdef DALI_WEBP_AVAILABLE
250   if(mImpl->mFrameCount == 1)
251   {
252     int32_t width, height;
253     if(!WebPGetInfo(mImpl->mBuffer, mImpl->mBufferSize, &width, &height))
254     {
255       return pixelBuffer;
256     }
257
258     WebPBitstreamFeatures features;
259     if(VP8_STATUS_NOT_ENOUGH_DATA == WebPGetFeatures(mImpl->mBuffer, mImpl->mBufferSize, &features))
260     {
261       return pixelBuffer;
262     }
263
264     uint32_t      channelNumber = (features.has_alpha) ? 4 : 3;
265     Pixel::Format pixelFormat   = (channelNumber == 4) ? Pixel::RGBA8888 : Pixel::RGB888;
266     pixelBuffer                 = Dali::Devel::PixelBuffer::New(width, height, pixelFormat);
267     uint8_t* frameBuffer        = nullptr;
268     if(channelNumber == 4)
269     {
270       frameBuffer = WebPDecodeRGBA(mImpl->mBuffer, mImpl->mBufferSize, &width, &height);
271     }
272     else
273     {
274       frameBuffer = WebPDecodeRGB(mImpl->mBuffer, mImpl->mBufferSize, &width, &height);
275     }
276
277     if(frameBuffer != nullptr)
278     {
279       const int32_t imageBufferSize = width * height * sizeof(uint8_t) * channelNumber;
280       memcpy(pixelBuffer.GetBuffer(), frameBuffer, imageBufferSize);
281       free((void*)frameBuffer);
282       return pixelBuffer;
283     }
284   }
285 #endif
286
287 #ifdef DALI_ANIMATED_WEBP_ENABLED
288   Mutex::ScopedLock lock(mImpl->mMutex);
289   if(frameIndex >= mImpl->mWebPAnimInfo.frame_count || !mImpl->mLoadSucceeded)
290   {
291     return pixelBuffer;
292   }
293
294   DALI_LOG_INFO(gWebPLoadingLogFilter, Debug::Concise, "LoadFrame( frameIndex:%d )\n", frameIndex);
295
296   if(mImpl->mLoadingFrame > frameIndex)
297   {
298     mImpl->mLoadingFrame = 0;
299     WebPAnimDecoderReset(mImpl->mWebPAnimDecoder);
300   }
301
302   for(; mImpl->mLoadingFrame < frameIndex; ++mImpl->mLoadingFrame)
303   {
304     uint8_t* frameBuffer;
305     int      timestamp;
306     WebPAnimDecoderGetNext(mImpl->mWebPAnimDecoder, &frameBuffer, &timestamp);
307     mImpl->mTimeStamp[mImpl->mLoadingFrame] = timestamp;
308   }
309
310   const int bufferSize = mImpl->mWebPAnimInfo.canvas_width * mImpl->mWebPAnimInfo.canvas_height * sizeof(uint32_t);
311   uint8_t*  frameBuffer;
312   int       timestamp;
313   WebPAnimDecoderGetNext(mImpl->mWebPAnimDecoder, &frameBuffer, &timestamp);
314
315   pixelBuffer = Dali::Devel::PixelBuffer::New(mImpl->mWebPAnimInfo.canvas_width, mImpl->mWebPAnimInfo.canvas_height, Dali::Pixel::RGBA8888);
316   memcpy(pixelBuffer.GetBuffer(), frameBuffer, bufferSize);
317   mImpl->mTimeStamp[mImpl->mLoadingFrame] = timestamp;
318
319   mImpl->mLoadingFrame++;
320   if(mImpl->mLoadingFrame >= mImpl->mWebPAnimInfo.frame_count)
321   {
322     mImpl->mLoadingFrame = 0;
323     WebPAnimDecoderReset(mImpl->mWebPAnimDecoder);
324   }
325 #endif
326   return pixelBuffer;
327 }
328
329 ImageDimensions WebPLoading::GetImageSize() const
330 {
331   return mImpl->mImageSize;
332 }
333
334 uint32_t WebPLoading::GetImageCount() const
335 {
336   return mImpl->mFrameCount;
337 }
338
339 uint32_t WebPLoading::GetFrameInterval(uint32_t frameIndex) const
340 {
341   // If frameIndex is above the value of ImageCount or current frame is not loading yet, return 0u.
342   if(frameIndex >= GetImageCount() || (frameIndex > 0 && mImpl->mTimeStamp[frameIndex - 1] > mImpl->mTimeStamp[frameIndex]))
343   {
344     return 0u;
345   }
346   else
347   {
348     if(frameIndex > 0)
349     {
350       return mImpl->mTimeStamp[frameIndex] - mImpl->mTimeStamp[frameIndex - 1];
351     }
352     return mImpl->mTimeStamp[frameIndex];
353   }
354 }
355
356 std::string WebPLoading::GetUrl() const
357 {
358   return mImpl->mUrl;
359 }
360
361 bool WebPLoading::HasLoadingSucceeded() const
362 {
363   return mImpl->mLoadSucceeded;
364 }
365
366 } // namespace Adaptor
367
368 } // namespace Internal
369
370 } // namespace Dali