2 * Copyright (c) 2024 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>
45 #include <dali/internal/imaging/common/pixel-buffer-impl.h>
47 typedef uint8_t WebPByteType;
57 #if defined(DEBUG_ENABLED)
58 Debug::Filter* gWebPLoadingLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_GIF_LOADING");
61 static constexpr int32_t INITIAL_INDEX = -1;
62 static constexpr size_t MAXIMUM_DOWNLOAD_IMAGE_SIZE = 50 * 1024 * 1024;
66 struct WebPLoading::Impl
69 Impl(const std::string& url, bool isLocalResource)
77 mLoadSucceeded(false),
78 mIsAnimatedImage(false),
79 mIsLocalResource(isLocalResource)
91 mLoadSucceeded(false),
92 mIsAnimatedImage(false),
93 mIsLocalResource(true)
97 bool LoadWebPInformation()
99 // Block to do not load this file again.
100 Mutex::ScopedLock lock(mMutex);
101 if(DALI_UNLIKELY(mLoadSucceeded))
103 return mLoadSucceeded;
106 #ifndef DALI_WEBP_AVAILABLE
107 // If the system doesn't support webp, loading will be failed.
109 mLoadSucceeded = false;
110 return mLoadSucceeded;
113 // mFrameCount will be 1 if the input image is non-animated image or animated image with single frame.
114 if(DALI_LIKELY(ReadWebPInformation()))
116 #ifdef DALI_WEBP_AVAILABLE
117 WebPDataInit(&mWebPData);
118 mWebPData.size = mBufferSize;
119 mWebPData.bytes = mBuffer;
121 WebPDemuxer* demuxer = WebPDemux(&mWebPData);
122 uint32_t flags = WebPDemuxGetI(demuxer, WEBP_FF_FORMAT_FLAGS);
123 if(flags & ANIMATION_FLAG)
125 mIsAnimatedImage = true;
127 WebPDemuxDelete(demuxer);
129 if(!mIsAnimatedImage)
131 int32_t imageWidth, imageHeight;
132 if(WebPGetInfo(mBuffer, mBufferSize, &imageWidth, &imageHeight))
134 mImageSize = ImageDimensions(imageWidth, imageHeight);
138 #ifdef DALI_ANIMATED_WEBP_ENABLED
141 WebPAnimDecoderOptions webPAnimDecoderOptions;
142 WebPAnimDecoderOptionsInit(&webPAnimDecoderOptions);
143 webPAnimDecoderOptions.color_mode = MODE_RGBA;
144 mWebPAnimDecoder = WebPAnimDecoderNew(&mWebPData, &webPAnimDecoderOptions);
145 WebPAnimDecoderGetInfo(mWebPAnimDecoder, &mWebPAnimInfo);
146 mTimeStamp.assign(mWebPAnimInfo.frame_count, 0);
147 mFrameCount = mWebPAnimInfo.frame_count;
148 mImageSize = ImageDimensions(mWebPAnimInfo.canvas_width, mWebPAnimInfo.canvas_height);
151 mLoadSucceeded = true;
156 mLoadSucceeded = false;
157 DALI_LOG_ERROR("Image loading failed for: \"%s\".\n", mUrl.c_str());
160 return mLoadSucceeded;
163 bool ReadWebPInformation()
168 std::unique_ptr<Internal::Platform::FileReader> fileReader;
169 Dali::Vector<uint8_t> dataBuffer;
174 fileReader = std::make_unique<Internal::Platform::FileReader>(mUrl);
179 if(DALI_LIKELY(TizenPlatform::Network::DownloadRemoteFileIntoMemory(mUrl, dataBuffer, dataSize, MAXIMUM_DOWNLOAD_IMAGE_SIZE)))
181 mBufferSize = dataBuffer.Size();
182 if(DALI_LIKELY(mBufferSize > 0U))
184 // Open a file handle on the memory buffer:
185 fileReader = std::make_unique<Internal::Platform::FileReader>(dataBuffer, mBufferSize);
192 fp = fileReader->GetFile();
196 if(DALI_LIKELY(fp != nullptr))
200 if(DALI_UNLIKELY(fseek(fp, 0, SEEK_END) <= -1))
204 mBufferSize = ftell(fp);
207 if(DALI_LIKELY(!fseek(fp, 0, SEEK_SET)))
209 mBuffer = reinterpret_cast<WebPByteType*>(malloc(sizeof(WebPByteType) * mBufferSize));
210 if(DALI_UNLIKELY(!mBuffer))
212 DALI_LOG_ERROR("malloc is failed. request malloc size : %zu\n", sizeof(WebPByteType) * mBufferSize);
215 mBufferSize = fread(mBuffer, sizeof(WebPByteType), mBufferSize, fp);
222 void ReleaseResource()
224 #ifdef DALI_WEBP_AVAILABLE
225 mWebPData.bytes = nullptr;
226 WebPDataInit(&mWebPData);
228 #ifdef DALI_ANIMATED_WEBP_ENABLED
231 if(mWebPAnimDecoder != nullptr)
233 WebPAnimDecoderDelete(mWebPAnimDecoder);
234 mWebPAnimDecoder = nullptr;
238 if(mBuffer != nullptr)
240 free((void*)mBuffer);
244 mLoadSucceeded = false;
247 // Moveable but not copyable
249 Impl(const Impl&) = delete;
250 Impl& operator=(const Impl&) = delete;
251 Impl(Impl&&) = default;
252 Impl& operator=(Impl&&) = default;
261 std::vector<uint32_t> mTimeStamp;
262 int32_t mLatestLoadedFrame{INITIAL_INDEX};
263 uint32_t mFrameCount;
265 // For the case the system doesn't support DALI_ANIMATED_WEBP_ENABLED
266 unsigned char* mBuffer;
267 uint32_t mBufferSize;
268 ImageDimensions mImageSize;
270 bool mIsAnimatedImage;
271 bool mIsLocalResource;
273 #ifdef DALI_WEBP_AVAILABLE
274 WebPData mWebPData{0};
277 #ifdef DALI_ANIMATED_WEBP_ENABLED
278 WebPAnimDecoder* mWebPAnimDecoder{nullptr};
279 WebPAnimInfo mWebPAnimInfo{0};
280 Dali::Devel::PixelBuffer mPreLoadedFrame{};
284 AnimatedImageLoadingPtr WebPLoading::New(const std::string& url, bool isLocalResource)
286 #ifndef DALI_ANIMATED_WEBP_ENABLED
287 DALI_LOG_ERROR("The system does not support Animated WebP format.\n");
289 return AnimatedImageLoadingPtr(new WebPLoading(url, isLocalResource));
292 AnimatedImageLoadingPtr WebPLoading::New(FILE* const fp)
294 #ifndef DALI_ANIMATED_WEBP_ENABLED
295 DALI_LOG_ERROR("The system does not support Animated WebP format.\n");
297 return AnimatedImageLoadingPtr(new WebPLoading(fp));
300 WebPLoading::WebPLoading(const std::string& url, bool isLocalResource)
301 : mImpl(new WebPLoading::Impl(url, isLocalResource))
305 WebPLoading::WebPLoading(FILE* const fp)
306 : mImpl(new WebPLoading::Impl(fp))
310 WebPLoading::~WebPLoading()
315 Dali::Devel::PixelBuffer WebPLoading::LoadFrame(uint32_t frameIndex, ImageDimensions desiredSize)
317 Dali::Devel::PixelBuffer pixelBuffer;
319 // If WebP file is still not loaded, Load the information.
320 if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
322 if(DALI_UNLIKELY(!mImpl->LoadWebPInformation()))
324 mImpl->ReleaseResource();
329 // WebPDecodeRGBA is faster than to use demux API for loading non-animated image.
330 // If frame count is 1, use WebPDecodeRGBA api.
331 #ifdef DALI_WEBP_AVAILABLE
332 if(!mImpl->mIsAnimatedImage)
334 int32_t width, height;
335 if(DALI_LIKELY(WebPGetInfo(mImpl->mBuffer, mImpl->mBufferSize, &width, &height)))
337 WebPBitstreamFeatures features;
338 if(DALI_LIKELY(VP8_STATUS_NOT_ENOUGH_DATA != WebPGetFeatures(mImpl->mBuffer, mImpl->mBufferSize, &features)))
340 uint32_t channelNumber = (features.has_alpha) ? 4 : 3;
341 uint8_t* frameBuffer = nullptr;
343 if(desiredSize.GetWidth() > 0 && desiredSize.GetHeight() > 0)
345 const int desiredWidth = desiredSize.GetWidth();
346 const int desiredHeight = desiredSize.GetHeight();
348 WebPDecoderConfig config;
349 if(!DALI_LIKELY(WebPInitDecoderConfig(&config)))
351 DALI_LOG_ERROR("WebPInitDecoderConfig is failed");
355 // Apply config for scaling
356 config.options.use_scaling = 1;
357 config.options.scaled_width = desiredWidth;
358 config.options.scaled_height = desiredHeight;
360 if(channelNumber == 4)
362 config.output.colorspace = MODE_RGBA;
366 config.output.colorspace = MODE_RGB;
369 if(WebPDecode(mImpl->mBuffer, mImpl->mBufferSize, &config) == VP8_STATUS_OK)
371 frameBuffer = config.output.u.RGBA.rgba;
375 DALI_LOG_ERROR("Webp Decoding with scaled size is failed \n");
378 if(frameBuffer != nullptr)
380 Pixel::Format pixelFormat = (channelNumber == 4) ? Pixel::RGBA8888 : Pixel::RGB888;
381 int32_t bufferSize = desiredWidth * desiredHeight * Dali::Pixel::GetBytesPerPixel(pixelFormat);
382 pixelBuffer = Dali::Devel::PixelBuffer::New(desiredWidth, desiredHeight, pixelFormat);
383 memcpy(pixelBuffer.GetBuffer(), frameBuffer, bufferSize);
386 WebPFreeDecBuffer(&config.output);
390 if(channelNumber == 4)
392 frameBuffer = WebPDecodeRGBA(mImpl->mBuffer, mImpl->mBufferSize, &width, &height);
396 frameBuffer = WebPDecodeRGB(mImpl->mBuffer, mImpl->mBufferSize, &width, &height);
399 if(frameBuffer != nullptr)
401 Pixel::Format pixelFormat = (channelNumber == 4) ? Pixel::RGBA8888 : Pixel::RGB888;
402 int32_t bufferSize = width * height * Dali::Pixel::GetBytesPerPixel(pixelFormat);
403 Internal::Adaptor::PixelBufferPtr internal = Internal::Adaptor::PixelBuffer::New(frameBuffer, bufferSize, width, height, width, pixelFormat);
404 pixelBuffer = Devel::PixelBuffer(internal.Get());
409 // The single frame resource should be released after loading.
410 mImpl->ReleaseResource();
414 #ifdef DALI_ANIMATED_WEBP_ENABLED
415 if(mImpl->mIsAnimatedImage && mImpl->mBuffer != nullptr)
417 Mutex::ScopedLock lock(mImpl->mMutex);
418 if(DALI_LIKELY(frameIndex < mImpl->mWebPAnimInfo.frame_count && mImpl->mLoadSucceeded))
420 DALI_LOG_INFO(gWebPLoadingLogFilter, Debug::Concise, "LoadFrame( frameIndex:%d )\n", frameIndex);
422 if(mImpl->mPreLoadedFrame && mImpl->mLatestLoadedFrame == static_cast<int32_t>(frameIndex))
424 pixelBuffer = mImpl->mPreLoadedFrame;
428 pixelBuffer = DecodeFrame(frameIndex);
430 mImpl->mPreLoadedFrame.Reset();
432 // If time stamp of next frame is unknown, load a frame more to know it.
433 if(frameIndex + 1 < mImpl->mWebPAnimInfo.frame_count && mImpl->mTimeStamp[frameIndex + 1] == 0u)
435 mImpl->mPreLoadedFrame = DecodeFrame(frameIndex + 1);
440 mImpl->ReleaseResource();
447 Dali::Devel::PixelBuffer WebPLoading::DecodeFrame(uint32_t frameIndex)
449 Dali::Devel::PixelBuffer pixelBuffer;
450 #ifdef DALI_ANIMATED_WEBP_ENABLED
451 if(mImpl->mLatestLoadedFrame >= static_cast<int32_t>(frameIndex))
453 mImpl->mLatestLoadedFrame = INITIAL_INDEX;
454 WebPAnimDecoderReset(mImpl->mWebPAnimDecoder);
457 uint8_t* frameBuffer = nullptr;
458 int32_t timestamp = 0u;
459 for(; mImpl->mLatestLoadedFrame < static_cast<int32_t>(frameIndex);)
461 WebPAnimDecoderGetNext(mImpl->mWebPAnimDecoder, &frameBuffer, ×tamp);
462 mImpl->mTimeStamp[++mImpl->mLatestLoadedFrame] = timestamp;
465 if(frameBuffer != nullptr)
467 const int bufferSize = mImpl->mWebPAnimInfo.canvas_width * mImpl->mWebPAnimInfo.canvas_height * sizeof(uint32_t);
468 pixelBuffer = Dali::Devel::PixelBuffer::New(mImpl->mWebPAnimInfo.canvas_width, mImpl->mWebPAnimInfo.canvas_height, Dali::Pixel::RGBA8888);
469 memcpy(pixelBuffer.GetBuffer(), frameBuffer, bufferSize);
476 ImageDimensions WebPLoading::GetImageSize() const
478 if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
480 mImpl->LoadWebPInformation();
482 return mImpl->mImageSize;
485 uint32_t WebPLoading::GetImageCount() const
487 if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
489 mImpl->LoadWebPInformation();
491 return mImpl->mFrameCount;
494 uint32_t WebPLoading::GetFrameInterval(uint32_t frameIndex) const
496 if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
498 DALI_LOG_ERROR("WebP file is still not loaded, this frame interval could not be correct value.\n");
500 if(frameIndex >= GetImageCount())
502 DALI_LOG_ERROR("Input frameIndex exceeded frame count of the WebP.");
507 int32_t interval = 0u;
508 if(GetImageCount() == 1u)
512 else if(frameIndex + 1 == GetImageCount())
514 // For the interval between last frame and first frame, use last interval again.
515 interval = mImpl->mTimeStamp[frameIndex] - mImpl->mTimeStamp[frameIndex - 1];
519 interval = mImpl->mTimeStamp[frameIndex + 1] - mImpl->mTimeStamp[frameIndex];
522 if(DALI_UNLIKELY(interval < 0))
524 DALI_LOG_ERROR("This interval value is not correct, because the frame still hasn't ever been decoded.");
527 return static_cast<uint32_t>(interval);
531 std::string WebPLoading::GetUrl() const
536 bool WebPLoading::HasLoadingSucceeded() const
538 return mImpl->mLoadSucceeded;
541 } // namespace Adaptor
543 } // namespace Internal