2 * Copyright (c) 2022 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include <dali/internal/imaging/common/webp-loading.h>
22 #ifdef DALI_WEBP_AVAILABLE
23 #include <webp/decode.h>
24 #include <webp/demux.h>
26 #if WEBP_DEMUX_ABI_VERSION > 0x0101
27 #define DALI_ANIMATED_WEBP_ENABLED 1
31 #include <dali/integration-api/debug.h>
32 #include <dali/public-api/images/pixel-data.h>
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>
39 #include <sys/types.h>
44 #include <dali/devel-api/adaptor-framework/image-loading.h>
46 typedef unsigned char WebPByteType;
56 #if defined(DEBUG_ENABLED)
57 Debug::Filter* gWebPLoadingLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_GIF_LOADING");
60 static constexpr int32_t INITIAL_INDEX = -1;
61 static constexpr size_t MAXIMUM_DOWNLOAD_IMAGE_SIZE = 50 * 1024 * 1024;
65 struct WebPLoading::Impl
68 Impl(const std::string& url, bool isLocalResource)
75 mLoadSucceeded(false),
76 mIsLocalResource(isLocalResource)
80 bool LoadWebPInformation()
82 // Block to do not load this file again.
83 Mutex::ScopedLock lock(mMutex);
84 if(DALI_UNLIKELY(mLoadSucceeded))
86 return mLoadSucceeded;
89 #ifndef DALI_WEBP_AVAILABLE
90 // If the system doesn't support webp, loading will be failed.
92 mLoadSucceeded = false;
93 return mLoadSucceeded;
96 // mFrameCount will be 1 if the input image is non-animated image or animated image with single frame.
97 if(DALI_LIKELY(ReadWebPInformation()))
99 #ifdef DALI_ANIMATED_WEBP_ENABLED
100 WebPDataInit(&mWebPData);
101 mWebPData.size = mBufferSize;
102 mWebPData.bytes = mBuffer;
103 WebPAnimDecoderOptions webPAnimDecoderOptions;
104 WebPAnimDecoderOptionsInit(&webPAnimDecoderOptions);
105 webPAnimDecoderOptions.color_mode = MODE_RGBA;
106 mWebPAnimDecoder = WebPAnimDecoderNew(&mWebPData, &webPAnimDecoderOptions);
107 WebPAnimDecoderGetInfo(mWebPAnimDecoder, &mWebPAnimInfo);
108 mTimeStamp.assign(mWebPAnimInfo.frame_count, 0);
109 mFrameCount = mWebPAnimInfo.frame_count;
110 mImageSize = ImageDimensions(mWebPAnimInfo.canvas_width, mWebPAnimInfo.canvas_height);
111 #elif DALI_WEBP_AVAILABLE
112 int32_t imageWidth, imageHeight;
113 if(WebPGetInfo(mBuffer, mBufferSize, &imageWidth, &imageHeight))
115 mImageSize = ImageDimensions(imageWidth, imageHeight);
118 mLoadSucceeded = true;
123 mLoadSucceeded = false;
124 DALI_LOG_ERROR("Image loading failed for: \"%s\".\n", mUrl.c_str());
127 return mLoadSucceeded;
130 bool ReadWebPInformation()
135 Internal::Platform::FileReader fileReader(mUrl);
136 fp = fileReader.GetFile();
137 if(DALI_UNLIKELY(fp == nullptr))
142 if(DALI_UNLIKELY(fseek(fp, 0, SEEK_END) <= -1))
147 mBufferSize = ftell(fp);
148 if(DALI_LIKELY(!fseek(fp, 0, SEEK_SET)))
150 mBuffer = reinterpret_cast<WebPByteType*>(malloc(sizeof(WebPByteType) * mBufferSize));
151 mBufferSize = fread(mBuffer, sizeof(WebPByteType), mBufferSize, fp);
159 Dali::Vector<uint8_t> dataBuffer;
162 succeeded = TizenPlatform::Network::DownloadRemoteFileIntoMemory(mUrl, dataBuffer, dataSize, MAXIMUM_DOWNLOAD_IMAGE_SIZE);
163 if(DALI_LIKELY(succeeded))
165 mBufferSize = dataBuffer.Size();
166 if(DALI_LIKELY(mBufferSize > 0U))
168 // Open a file handle on the memory buffer:
169 Internal::Platform::FileReader fileReader(dataBuffer, mBufferSize);
170 fp = fileReader.GetFile();
171 if(DALI_LIKELY(fp != nullptr))
173 if(DALI_LIKELY(!fseek(fp, 0, SEEK_SET)))
175 mBuffer = reinterpret_cast<WebPByteType*>(malloc(sizeof(WebPByteType) * mBufferSize));
176 mBufferSize = fread(mBuffer, sizeof(WebPByteType), mBufferSize, fp);
186 void ReleaseResource()
188 #ifdef DALI_ANIMATED_WEBP_ENABLED
189 if(&mWebPData != nullptr)
191 mWebPData.bytes = nullptr;
192 WebPDataInit(&mWebPData);
194 if(mWebPAnimDecoder != nullptr)
196 WebPAnimDecoderDelete(mWebPAnimDecoder);
197 mWebPAnimDecoder = nullptr;
200 if(mBuffer != nullptr)
202 free((void*)mBuffer);
207 // Moveable but not copyable
209 Impl(const Impl&) = delete;
210 Impl& operator=(const Impl&) = delete;
211 Impl(Impl&&) = default;
212 Impl& operator=(Impl&&) = default;
220 std::vector<uint32_t> mTimeStamp;
221 int32_t mLatestLoadedFrame{INITIAL_INDEX};
222 uint32_t mFrameCount;
224 // For the case the system doesn't support DALI_ANIMATED_WEBP_ENABLED
225 unsigned char* mBuffer;
226 uint32_t mBufferSize;
227 ImageDimensions mImageSize;
229 bool mIsLocalResource;
231 #ifdef DALI_ANIMATED_WEBP_ENABLED
232 WebPData mWebPData{0};
233 WebPAnimDecoder* mWebPAnimDecoder{nullptr};
234 WebPAnimInfo mWebPAnimInfo{0};
235 Dali::Devel::PixelBuffer mPreLoadedFrame{};
239 AnimatedImageLoadingPtr WebPLoading::New(const std::string& url, bool isLocalResource)
241 #ifndef DALI_ANIMATED_WEBP_ENABLED
242 DALI_LOG_ERROR("The system does not support Animated WebP format.\n");
244 return AnimatedImageLoadingPtr(new WebPLoading(url, isLocalResource));
247 WebPLoading::WebPLoading(const std::string& url, bool isLocalResource)
248 : mImpl(new WebPLoading::Impl(url, isLocalResource))
252 WebPLoading::~WebPLoading()
257 bool WebPLoading::LoadNextNFrames(uint32_t frameStartIndex, int count, std::vector<Dali::PixelData>& pixelData)
259 for(int i = 0; i < count; ++i)
261 Dali::Devel::PixelBuffer pixelBuffer = LoadFrame((frameStartIndex + i) % mImpl->mFrameCount);
262 if(DALI_UNLIKELY(!pixelBuffer))
267 Dali::PixelData imageData = Devel::PixelBuffer::Convert(pixelBuffer);
268 pixelData.push_back(imageData);
270 if(DALI_UNLIKELY(pixelData.size() != static_cast<uint32_t>(count)))
277 Dali::Devel::PixelBuffer WebPLoading::LoadFrame(uint32_t frameIndex)
279 Dali::Devel::PixelBuffer pixelBuffer;
281 // If WebP file is still not loaded, Load the information.
282 if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
284 if(DALI_UNLIKELY(!mImpl->LoadWebPInformation()))
290 // WebPDecodeRGBA is faster than to use demux API for loading non-animated image.
291 // If frame count is 1, use WebPDecodeRGBA api.
292 #ifdef DALI_WEBP_AVAILABLE
293 if(mImpl->mFrameCount == 1)
295 int32_t width, height;
296 if(DALI_UNLIKELY(!WebPGetInfo(mImpl->mBuffer, mImpl->mBufferSize, &width, &height)))
301 WebPBitstreamFeatures features;
302 if(DALI_UNLIKELY(VP8_STATUS_NOT_ENOUGH_DATA == WebPGetFeatures(mImpl->mBuffer, mImpl->mBufferSize, &features)))
307 uint32_t channelNumber = (features.has_alpha) ? 4 : 3;
308 Pixel::Format pixelFormat = (channelNumber == 4) ? Pixel::RGBA8888 : Pixel::RGB888;
309 pixelBuffer = Dali::Devel::PixelBuffer::New(width, height, pixelFormat);
310 uint8_t* frameBuffer = nullptr;
311 if(channelNumber == 4)
313 frameBuffer = WebPDecodeRGBA(mImpl->mBuffer, mImpl->mBufferSize, &width, &height);
317 frameBuffer = WebPDecodeRGB(mImpl->mBuffer, mImpl->mBufferSize, &width, &height);
320 if(frameBuffer != nullptr)
322 const int32_t imageBufferSize = width * height * sizeof(uint8_t) * channelNumber;
323 memcpy(pixelBuffer.GetBuffer(), frameBuffer, imageBufferSize);
324 free((void*)frameBuffer);
326 mImpl->ReleaseResource();
331 #ifdef DALI_ANIMATED_WEBP_ENABLED
332 Mutex::ScopedLock lock(mImpl->mMutex);
333 if(DALI_UNLIKELY(frameIndex >= mImpl->mWebPAnimInfo.frame_count || !mImpl->mLoadSucceeded))
338 DALI_LOG_INFO(gWebPLoadingLogFilter, Debug::Concise, "LoadFrame( frameIndex:%d )\n", frameIndex);
340 if(mImpl->mPreLoadedFrame && mImpl->mLatestLoadedFrame == static_cast<int32_t>(frameIndex))
342 pixelBuffer = mImpl->mPreLoadedFrame;
346 pixelBuffer = DecodeFrame(frameIndex);
348 mImpl->mPreLoadedFrame.Reset();
350 // If time stamp of next frame is unknown, load a frame more to know it.
351 if(frameIndex + 1 < mImpl->mWebPAnimInfo.frame_count && mImpl->mTimeStamp[frameIndex + 1] == 0u)
353 mImpl->mPreLoadedFrame = DecodeFrame(frameIndex + 1);
360 Dali::Devel::PixelBuffer WebPLoading::DecodeFrame(uint32_t frameIndex)
362 Dali::Devel::PixelBuffer pixelBuffer;
363 #ifdef DALI_ANIMATED_WEBP_ENABLED
364 if(mImpl->mLatestLoadedFrame > static_cast<int32_t>(frameIndex))
366 mImpl->mLatestLoadedFrame = INITIAL_INDEX;
367 WebPAnimDecoderReset(mImpl->mWebPAnimDecoder);
370 uint8_t* frameBuffer = nullptr;
371 int32_t timestamp = 0u;
372 for(; mImpl->mLatestLoadedFrame < static_cast<int32_t>(frameIndex);)
374 WebPAnimDecoderGetNext(mImpl->mWebPAnimDecoder, &frameBuffer, ×tamp);
375 mImpl->mTimeStamp[++mImpl->mLatestLoadedFrame] = timestamp;
377 if(frameBuffer != nullptr)
379 const int bufferSize = mImpl->mWebPAnimInfo.canvas_width * mImpl->mWebPAnimInfo.canvas_height * sizeof(uint32_t);
380 pixelBuffer = Dali::Devel::PixelBuffer::New(mImpl->mWebPAnimInfo.canvas_width, mImpl->mWebPAnimInfo.canvas_height, Dali::Pixel::RGBA8888);
381 memcpy(pixelBuffer.GetBuffer(), frameBuffer, bufferSize);
388 ImageDimensions WebPLoading::GetImageSize() const
390 if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
392 mImpl->LoadWebPInformation();
394 return mImpl->mImageSize;
397 uint32_t WebPLoading::GetImageCount() const
399 if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
401 mImpl->LoadWebPInformation();
403 return mImpl->mFrameCount;
406 uint32_t WebPLoading::GetFrameInterval(uint32_t frameIndex) const
408 if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
410 DALI_LOG_ERROR("WebP file is still not loaded, this frame interval could not be correct value.\n");
412 if(frameIndex >= GetImageCount())
414 DALI_LOG_ERROR("Input frameIndex exceeded frame count of the WebP.");
419 int32_t interval = 0u;
420 if(GetImageCount() == 1u)
424 else if(frameIndex + 1 == GetImageCount())
426 // For the interval between last frame and first frame, use last interval again.
427 interval = mImpl->mTimeStamp[frameIndex] - mImpl->mTimeStamp[frameIndex - 1];
431 interval = mImpl->mTimeStamp[frameIndex + 1] - mImpl->mTimeStamp[frameIndex];
434 if(DALI_UNLIKELY(interval < 0))
436 DALI_LOG_ERROR("This interval value is not correct, because the frame still hasn't ever been decoded.");
439 return static_cast<uint32_t>(interval);
443 std::string WebPLoading::GetUrl() const
448 bool WebPLoading::HasLoadingSucceeded() const
450 return mImpl->mLoadSucceeded;
453 } // namespace Adaptor
455 } // namespace Internal