2 * Copyright (c) 2023 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.
16 * Copyright notice for the EFL:
17 * Copyright (C) EFL developers (see AUTHORS)
21 #include <dali/internal/imaging/common/gif-loading.h>
27 #include <sys/types.h>
32 #include <dali/devel-api/threading/mutex.h>
33 #include <dali/integration-api/debug.h>
34 #include <dali/internal/imaging/common/file-download.h>
35 #include <dali/internal/system/common/file-reader.h>
36 #include <dali/public-api/images/pixel-data.h>
38 #define IMG_TOO_BIG(w, h) \
39 ((static_cast<unsigned long long>(w) * static_cast<unsigned long long>(h)) >= \
40 ((1ULL << (29 * (sizeof(void*) / 4))) - 2048))
42 #define PRINT_ERROR_AND_EXIT(errorFormat, ...) \
45 DALI_LOG_ERROR(errorFormat, ##__VA_ARGS__); \
61 #if defined(DEBUG_ENABLED)
62 Debug::Filter* gGifLoadingLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_GIF_LOADING");
65 const int IMG_MAX_SIZE = 65000;
66 constexpr size_t MAXIMUM_DOWNLOAD_IMAGE_SIZE = 50 * 1024 * 1024;
68 constexpr int LOCAL_CACHED_COLOR_GENERATE_THRESHOLD = 64; ///< Generate color map optimize only if colorCount * threshold < width * height, So we don't loop if image is small
71 const int DISPOSE_BACKGROUND = 2; /* Set area too background color */
72 const int DISPOSE_PREVIOUS = 3; /* Restore to previous content */
84 dispose(DISPOSE_BACKGROUND),
90 unsigned short delay; // delay time in 1/100ths of a sec
91 short transparent : 10; // -1 == not, anything else == index
92 short dispose : 6; // 0, 1, 2, 3 (others invalid)
93 short interlace : 1; // interlaced or not
110 // De-allocate memory of the frame data.
117 uint32_t* data; /* frame decoding data */
118 FrameInfo info; /* special image type info */
122 struct GifAnimationData
133 std::vector<ImageFrame> frames;
140 struct GifCachedColorData
142 GifCachedColorData() = default;
144 // precalculated colormap table
145 std::vector<std::uint32_t> globalCachedColor{};
146 std::vector<std::uint32_t> localCachedColor{};
149 // Forward declaration
164 isLocalResource(true)
180 bool LoadLocalFile();
181 bool LoadRemoteFile();
184 const char* fileName; /**< The absolute path of the file. */
185 unsigned char* globalMap; /**< A pointer to the entire contents of the file */
186 long long length; /**< The length of the file in bytes. */
187 bool isLocalResource; /**< The flag whether the file is a local resource */
200 int position, length; // yes - gif uses ints for file sizes.
204 GifAnimationData animated;
205 GifCachedColorData cachedColor;
206 std::unique_ptr<GifAccessor> gifAccessor{nullptr};
211 struct ImageProperties
219 * Class to access gif open/close using riaa
225 * @param[in,out] fileInfo Contains ptr to memory, and is updated by DGifOpen
227 GifAccessor(LoaderInfo::FileInfo& fileInfo)
229 // actually ask libgif to open the file
230 #if GIFLIB_MAJOR >= 5
231 gif = DGifOpen(&fileInfo, FileRead, NULL);
233 gif = DGifOpen(&fileInfo, FileRead);
238 DALI_LOG_ERROR("LOAD_ERROR_UNKNOWN_FORMAT\n");
246 #if(GIFLIB_MAJOR > 5) || ((GIFLIB_MAJOR == 5) && (GIFLIB_MINOR >= 1))
247 DGifCloseFile(gif, NULL);
255 * @brief Copy data from gif file into buffer.
257 * @param[in] gifFileType A pointer pointing to GIF File Type
258 * @param[out] buffer A pointer to buffer containing GIF raw data
259 * @param[in] len The length in bytes to be copied
260 * @return The data length of the image in bytes
262 static int FileRead(GifFileType* gifFileType, GifByteType* buffer, int length)
264 LoaderInfo::FileInfo* fi = reinterpret_cast<LoaderInfo::FileInfo*>(gifFileType->UserData);
266 if(DALI_UNLIKELY(fi->position >= fi->length))
268 return 0; // if at or past end - no
270 if((fi->position + length) >= fi->length)
272 length = fi->length - fi->position;
274 memcpy(buffer, fi->map + fi->position, length);
275 fi->position += length;
279 GifFileType* gif = nullptr;
282 bool LoaderInfo::FileData::LoadFile()
284 bool success = false;
287 success = LoadLocalFile();
291 success = LoadRemoteFile();
296 bool LoaderInfo::FileData::LoadLocalFile()
298 Internal::Platform::FileReader fileReader(fileName);
299 FILE* fp = fileReader.GetFile();
300 if(DALI_UNLIKELY(fp == NULL))
305 if(DALI_UNLIKELY(fseek(fp, 0, SEEK_END) <= -1))
311 if(DALI_UNLIKELY(length <= -1))
316 if(DALI_LIKELY(!fseek(fp, 0, SEEK_SET)))
318 globalMap = reinterpret_cast<GifByteType*>(malloc(sizeof(GifByteType) * length));
319 length = fread(globalMap, sizeof(GifByteType), length, fp);
328 bool LoaderInfo::FileData::LoadRemoteFile()
331 bool succeeded = false;
332 Dali::Vector<uint8_t> dataBuffer;
335 succeeded = TizenPlatform::Network::DownloadRemoteFileIntoMemory(fileName, dataBuffer, dataSize, MAXIMUM_DOWNLOAD_IMAGE_SIZE);
336 if(DALI_LIKELY(succeeded))
338 size_t blobSize = dataBuffer.Size();
339 if(DALI_LIKELY(blobSize > 0U))
341 // Open a file handle on the memory buffer:
342 Dali::Internal::Platform::FileReader fileReader(dataBuffer, blobSize);
343 FILE* const fp = fileReader.GetFile();
344 if(DALI_LIKELY(NULL != fp))
346 if(DALI_LIKELY(!fseek(fp, 0, SEEK_SET)))
348 globalMap = reinterpret_cast<GifByteType*>(malloc(sizeof(GifByteType) * blobSize));
349 length = fread(globalMap, sizeof(GifByteType), blobSize, fp);
354 DALI_LOG_ERROR("Error seeking within file\n");
359 DALI_LOG_ERROR("Error reading file\n");
368 * @brief This combines R, G, B and Alpha values into a single 32-bit (ABGR) value.
370 * @param[in] animated A structure containing GIF animation data
371 * @param[in] index Frame index to be searched in GIF
372 * @return single 32-bit (ABGR) value.
374 inline std::uint32_t CombinePixelABGR(const std::uint32_t& a, const std::uint32_t& r, const std::uint32_t& g, const std::uint32_t& b)
376 return (((a) << 24) + ((b) << 16) + ((g) << 8) + (r));
379 inline std::uint32_t PixelLookup(const ColorMapObject* const& colorMap, int index)
381 return CombinePixelABGR(0xFF, colorMap->Colors[index].Red, colorMap->Colors[index].Green, colorMap->Colors[index].Blue);
385 * @brief Get the Background Color from frameInfo
387 * @param[in] gif A pointer pointing to GIF File Type
388 * @param[in] frameInfo A pointer pointing to Frame Information data
389 * @return single 32-bit (ABGR) value. of background color
391 std::uint32_t GetBackgroundColor(GifFileType* gif, FrameInfo* frameInfo)
393 if(frameInfo->transparent < 0)
395 ColorMapObject* colorMap;
398 // work out color to use from colorMap
399 if(gif->Image.ColorMap)
401 colorMap = gif->Image.ColorMap;
405 colorMap = gif->SColorMap;
407 backGroundColor = gif->SBackGroundColor;
408 // Get background color from colormap
409 return PixelLookup(colorMap, backGroundColor);
419 * @brief Brute force find frame index - gifs are normally small so ok for now.
421 * @param[in] animated A structure containing GIF animation data
422 * @param[in] index Frame index to be searched in GIF
423 * @return A pointer to the ImageFrame.
425 ImageFrame* FindFrame(const GifAnimationData& animated, int index)
427 for(auto&& elem : animated.frames)
429 if(elem.index == index)
431 return const_cast<ImageFrame*>(&elem);
438 * @brief Fill in an image with a specific rgba color value.
440 * @param[in] data A pointer pointing to an image data
441 * @param[in] stride A int containing the number of stride in an image
442 * @param[in] val A uint32_t containing rgba color value
443 * @param[in] x X-coordinate used an offset to calculate pixel position
444 * @param[in] y Y-coordinate used an offset to calculate pixel position
445 * @param[in] width Width of the image
446 * @param[in] height Height of the image
448 void FillImage(uint32_t* data, int stride, uint32_t val, int x, int y, int width, int height)
450 uint32_t* pixelPosition;
452 // Boost time if stride == width and x == 0. We can assume that all pointer is continuous.
453 if(x == 0 && stride == width)
455 pixelPosition = data + (y * stride);
456 // Clear as white or transparent
457 // Special case. we can use memset.
458 if(val == 0x00 || val == 0xffffffffu)
460 const std::int8_t setupVal = val & 0xff;
461 memset(pixelPosition, setupVal, width * height * sizeof(std::uint32_t));
465 for(int byteCount = 0; byteCount < width * height; ++byteCount)
467 *pixelPosition = val;
474 for(int yAxis = 0; yAxis < height; ++yAxis)
476 pixelPosition = data + ((y + yAxis) * stride) + x;
477 for(int xAxis = 0; xAxis < width; ++xAxis)
479 *pixelPosition = val;
487 * @brief Store common fields from gif file info into frame info
489 * @param[in] gif A pointer pointing to GIF File Type
490 * @param[in] frameInfo A pointer pointing to Frame Information data
492 void StoreFrameInfo(GifFileType* gif, FrameInfo* frameInfo)
494 frameInfo->x = gif->Image.Left;
495 frameInfo->y = gif->Image.Top;
496 frameInfo->w = gif->Image.Width;
497 frameInfo->h = gif->Image.Height;
498 frameInfo->interlace = gif->Image.Interlace;
502 * @brief Check if image fills "screen space" and if so, if it is transparent
503 * at all then the image could be transparent - OR if image doesnt fill,
504 * then it could be trasnparent (full coverage of screen). Some gifs will
505 * be recognized as solid here for faster rendering, but not all.
507 * @param[out] full A boolean to show whether image is transparent or not
508 * @param[in] frameInfo A pointer pointing to Frame Information data
509 * @param[in] width Width of the image
510 * @param[in] height Height of the image
512 void CheckTransparency(bool& full, FrameInfo* frameInfo, int width, int height)
514 if((frameInfo->x == 0) && (frameInfo->y == 0) &&
515 (frameInfo->w == width) && (frameInfo->h == height))
517 if(frameInfo->transparent >= 0)
529 * @brief Fix coords and work out an x and y inset in orig data if out of image bounds.
531 void ClipCoordinates(int imageWidth, int imageHeight, int* xin, int* yin, int x0, int y0, int w0, int h0, int* x, int* y, int* w, int* h)
539 if((x0 + w0) > imageWidth)
541 w0 = imageWidth - x0;
549 if((y0 + h0) > imageHeight)
551 h0 = imageHeight - y0;
560 * @brief Flush out rgba frame images to save memory but skip current,
561 * previous and lastPreservedFrame frames (needed for dispose mode DISPOSE_PREVIOUS)
563 * @param[in] animated A structure containing GIF animation data
564 * @param[in] width Width of the image
565 * @param[in] height Height of the image
566 * @param[in] thisframe The current frame
567 * @param[in] prevframe The previous frame
568 * @param[in] lastPreservedFrame The last preserved frame
570 void FlushFrames(GifAnimationData& animated, int width, int height, ImageFrame* thisframe, ImageFrame* prevframe, ImageFrame* lastPreservedFrame)
572 DALI_LOG_INFO(gGifLoadingLogFilter, Debug::Concise, "FlushFrames() START \n");
574 // target is the amount of memory we want to be under for stored frames
575 int total = 0, target = 512 * 1024;
577 // total up the amount of memory used by stored frames for this image
578 for(auto&& frame : animated.frames)
585 total *= (width * height * sizeof(uint32_t));
587 DALI_LOG_INFO(gGifLoadingLogFilter, Debug::Concise, "Total used frame size: %d\n", total);
589 // If we use more than target (512k) for frames - flush
592 // Clean frames (except current and previous) until below target
593 for(auto&& frame : animated.frames)
595 if((frame.index != thisframe->index) && (!prevframe || frame.index != prevframe->index) &&
596 (!lastPreservedFrame || frame.index != lastPreservedFrame->index))
598 if(frame.data != nullptr)
601 frame.data = nullptr;
603 // subtract memory used and if below target - stop flush
604 total -= (width * height * sizeof(uint32_t));
614 DALI_LOG_INFO(gGifLoadingLogFilter, Debug::Concise, "FlushFrames() END \n");
618 * @brief allocate frame and frame info and append to list and store fields.
620 * @param[in] animated A structure containing GIF animation data
621 * @param[in] transparent Transparent index of the new frame
622 * @param[in] dispose Dispose mode of new frame
623 * @param[in] delay The frame delay of new frame
624 * @param[in] index The index of new frame
626 FrameInfo* NewFrame(GifAnimationData& animated, int transparent, int dispose, int delay, int index)
630 // record transparent index to be used or -1 if none
631 // for this SPECIFIC frame
632 frame.info.transparent = transparent;
633 // record dispose mode (3 bits)
634 frame.info.dispose = dispose;
635 // record delay (2 bytes so max 65546 /100 sec)
636 frame.info.delay = delay;
637 // record the index number we are at
639 // that frame is stored AT image/screen size
641 animated.frames.push_back(frame);
643 DALI_LOG_INFO(gGifLoadingLogFilter, Debug::Concise, "NewFrame: animated.frames.size() = %d\n", animated.frames.size());
645 return &(animated.frames.back().info);
648 /// FILL (overwrite with transparency kept)
649 void FillOverwriteWithTransparencyKept(
650 const std::uint32_t*& cachedColorPtr,
660 const int transparent,
661 const uint32_t fillColor,
666 ColorMapObject*& colorMap)
668 // if we use cachedColor, use it
671 for(yy = 0; yy < h; yy++)
673 p = data + ((y + yy) * rowpix) + x;
674 for(xx = 0; xx < w; xx++)
676 pix = rows[yin + yy][xin + xx];
677 if(pix != transparent)
679 *p = cachedColorPtr[pix];
689 // we don't have cachedColor. use PixelLookup function.
692 for(yy = 0; yy < h; yy++)
694 p = data + ((y + yy) * rowpix) + x;
695 for(xx = 0; xx < w; xx++)
697 pix = rows[yin + yy][xin + xx];
698 if(pix != transparent)
700 *p = PixelLookup(colorMap, pix);
712 /// Paste on top with transparent pixels untouched
713 void PasteOnTopWithTransparentPixelsUntouched(
714 const std::uint32_t*& cachedColorPtr,
724 const int transparent,
725 const uint32_t fillColor,
730 ColorMapObject*& colorMap)
732 // if we use cachedColor, use it
735 for(yy = 0; yy < h; yy++)
737 p = data + ((y + yy) * rowpix) + x;
738 for(xx = 0; xx < w; xx++)
740 pix = rows[yin + yy][xin + xx];
741 if(pix != transparent)
743 *p = cachedColorPtr[pix];
749 // we don't have cachedColor. use PixelLookup function.
752 for(yy = 0; yy < h; yy++)
754 p = data + ((y + yy) * rowpix) + x;
755 for(xx = 0; xx < w; xx++)
757 pix = rows[yin + yy][xin + xx];
758 if(pix != transparent)
760 *p = PixelLookup(colorMap, pix);
768 void HandleTransparentPixels(
770 const std::uint32_t*& cachedColorPtr,
780 const int transparent,
781 const uint32_t fillColor,
786 ColorMapObject*& colorMap)
790 FillOverwriteWithTransparencyKept(cachedColorPtr, xx, yy, x, y, w, h, xin, yin, rowpix, transparent, fillColor, pix, p, data, rows, colorMap);
794 PasteOnTopWithTransparentPixelsUntouched(cachedColorPtr, xx, yy, x, y, w, h, xin, yin, rowpix, transparent, fillColor, pix, p, data, rows, colorMap);
798 void HandleNonTransparentPixels(
799 const std::uint32_t*& cachedColorPtr,
809 const int transparent,
810 const uint32_t fillColor,
815 ColorMapObject*& colorMap)
817 // if we use cachedColor, use it
820 // walk pixels without worring about transparency at all
821 for(yy = 0; yy < h; yy++)
823 p = data + ((y + yy) * rowpix) + x;
824 for(xx = 0; xx < w; xx++)
826 pix = rows[yin + yy][xin + xx];
827 *p = cachedColorPtr[pix];
832 // we don't have cachedColor. use PixelLookup function.
835 // walk pixels without worring about transparency at all
836 for(yy = 0; yy < h; yy++)
838 p = data + ((y + yy) * rowpix) + x;
839 for(xx = 0; xx < w; xx++)
841 pix = rows[yin + yy][xin + xx];
842 *p = PixelLookup(colorMap, pix);
850 * @brief Decode a gif image into rows then expand to 32bit into the destination
853 bool DecodeImage(GifFileType* gif, GifCachedColorData& gifCachedColor, uint32_t* data, int rowpix, int xin, int yin, int transparent, int x, int y, int w, int h, bool fill, uint32_t fillColor = 0u)
855 int intoffset[] = {0, 4, 2, 1};
856 int intjump[] = {8, 8, 4, 2};
857 int i, xx, yy, pix, gifW, gifH;
858 GifRowType* rows = NULL;
860 ColorMapObject* colorMap;
863 // cached color data.
864 const std::uint32_t* cachedColorPtr = nullptr;
866 // what we need is image size.
868 sp = &gif->SavedImages[gif->ImageCount - 1];
870 gifW = sp->ImageDesc.Width;
871 gifH = sp->ImageDesc.Height;
873 if(DALI_UNLIKELY((gifW < w) || (gifH < h)))
875 DALI_LOG_ERROR("gifW : %d, w : %d, gifH : %d, h : %d\n", gifW, w, gifH, h);
876 DALI_ASSERT_DEBUG(false && "Dimensions are bigger than the Gif image size");
877 PRINT_ERROR_AND_EXIT("GIF Loader: Dimensions are bigger than the Gif image size! gifW : %d, w : %d, gifH : %d, h : %d\n", gifW, w, gifH, h);
880 // build a blob of memory to have pointers to rows of pixels
881 // AND store the decoded gif pixels (1 byte per pixel) as welll
882 rows = static_cast<GifRowType*>(malloc((gifH * sizeof(GifRowType)) + (gifW * gifH * sizeof(GifPixelType))));
883 if(DALI_UNLIKELY(!rows))
885 PRINT_ERROR_AND_EXIT("GIF Loader: malloc failed! gifW : %d, w : %d, gifH : %d, h : %d\n", gifW, w, gifH, h);
888 // fill in the pointers at the start
889 for(yy = 0; yy < gifH; yy++)
891 rows[yy] = reinterpret_cast<unsigned char*>(rows) + (gifH * sizeof(GifRowType)) + (yy * gifW * sizeof(GifPixelType));
894 // if gif is interlaced, walk interlace pattern and decode into rows
895 if(gif->Image.Interlace)
897 for(i = 0; i < 4; i++)
899 for(yy = intoffset[i]; yy < gifH; yy += intjump[i])
901 if(DALI_UNLIKELY(DGifGetLine(gif, rows[yy], gifW) != GIF_OK))
903 PRINT_ERROR_AND_EXIT("GIF Loader: Decode failed at line %d! gifW : %d, gifH : %d, rows[yy] : %d, i : %d, intoffset[i] : %d, intjump[i] : %d\n", yy, gifW, gifH, rows[yy], i, intoffset[i], intjump[i]);
908 // normal top to bottom - decode into rows
911 for(yy = 0; yy < gifH; yy++)
913 if(DALI_UNLIKELY(DGifGetLine(gif, rows[yy], gifW) != GIF_OK))
915 PRINT_ERROR_AND_EXIT("GIF Loader: Decode failed at line %d! gifW : %d, gifH : %d, rows[yy] : %d\n", yy, gifW, gifH, rows[yy]);
920 // work out what colormap to use
921 if(gif->Image.ColorMap)
923 colorMap = gif->Image.ColorMap;
924 // if w * h is big enough, generate local cached color.
925 if(colorMap->ColorCount * LOCAL_CACHED_COLOR_GENERATE_THRESHOLD < w * h)
927 gifCachedColor.localCachedColor.resize(colorMap->ColorCount);
928 for(i = 0; i < colorMap->ColorCount; ++i)
930 gifCachedColor.localCachedColor[i] = PixelLookup(colorMap, i);
933 cachedColorPtr = gifCachedColor.localCachedColor.data();
938 colorMap = gif->SColorMap;
939 cachedColorPtr = gifCachedColor.globalCachedColor.data();
942 // HARD-CODING optimize
943 // if we need to deal with transparent pixels at all...
946 HandleTransparentPixels(fill, cachedColorPtr, xx, yy, x, y, w, h, xin, yin, rowpix, transparent, fillColor, pix, p, data, rows, colorMap);
950 HandleNonTransparentPixels(cachedColorPtr, xx, yy, x, y, w, h, xin, yin, rowpix, transparent, fillColor, pix, p, data, rows, colorMap);
961 /// Walk through gif records in file to figure out info while reading the header
962 void WalkThroughGifRecordsWhileReadingHeader(
963 const GifAccessor& gifAccessor,
966 FrameInfo*& frameInfo,
968 const ImageProperties& prop,
969 GifAnimationData& animated,
975 if(DGifGetRecordType(gifAccessor.gif, &rec) == GIF_ERROR)
977 // if we have a gif that ends part way through a sequence
978 // (or animation) consider it valid and just break - no error
986 DALI_LOG_ERROR("LOAD_ERROR_UNKNOWN_FORMAT\n");
991 // get image description section
992 if(rec == IMAGE_DESC_RECORD_TYPE)
998 if(DALI_UNLIKELY(DGifGetImageDesc(gifAccessor.gif) == GIF_ERROR))
1001 DALI_LOG_ERROR("LOAD_ERROR_UNKNOWN_FORMAT\n");
1004 // skip decoding and just walk image to next
1005 if(DALI_UNLIKELY(DGifGetCode(gifAccessor.gif, &img_code, &img) == GIF_ERROR))
1008 DALI_LOG_ERROR("LOAD_ERROR_UNKNOWN_FORMAT\n");
1011 // skip till next...
1015 DGifGetCodeNext(gifAccessor.gif, &img);
1017 // store geometry in the last frame info data
1020 StoreFrameInfo(gifAccessor.gif, frameInfo);
1021 CheckTransparency(full, frameInfo, prop.w, prop.h);
1023 // or if we dont have a frameInfo entry - create one even for stills
1026 // allocate and save frame with field data
1027 frameInfo = NewFrame(animated, -1, 0, 0, imageNumber + 1);
1028 if(DALI_UNLIKELY(!frameInfo))
1031 DALI_LOG_ERROR("LOAD_ERROR_RESOURCE_ALLOCATION_FAILED");
1034 // store geometry info from gif image
1035 StoreFrameInfo(gifAccessor.gif, frameInfo);
1036 // check for transparency/alpha
1037 CheckTransparency(full, frameInfo, prop.w, prop.h);
1041 // we have an extension code block - for animated gifs for sure
1042 else if(rec == EXTENSION_RECORD_TYPE)
1045 GifByteType* ext = NULL;
1047 // get the first extension entry
1048 DGifGetExtension(gifAccessor.gif, &ext_code, &ext);
1051 // graphic control extension - for animated gif data
1052 // and transparent index + flag
1053 if(ext_code == 0xf9)
1055 // create frame and store it in image
1056 int transparencyIndex = (ext[1] & 1) ? ext[4] : -1;
1057 int disposeMode = (ext[1] >> 2) & 0x7;
1058 int delay = (int(ext[3]) << 8) | int(ext[2]);
1059 frameInfo = NewFrame(animated, transparencyIndex, disposeMode, delay, imageNumber + 1);
1060 if(DALI_UNLIKELY(!frameInfo))
1063 DALI_LOG_ERROR("LOAD_ERROR_RESOURCE_ALLOCATION_FAILED");
1067 // netscape extension indicating loop count.
1068 else if(ext_code == 0xff) /* application extension */
1070 if(!strncmp(reinterpret_cast<char*>(&ext[1]), "NETSCAPE2.0", 11) ||
1071 !strncmp(reinterpret_cast<char*>(&ext[1]), "ANIMEXTS1.0", 11))
1074 DGifGetExtensionNext(gifAccessor.gif, &ext);
1077 loopCount = (int(ext[3]) << 8) | int(ext[2]);
1086 // and continue onto the next extension entry
1088 DGifGetExtensionNext(gifAccessor.gif, &ext);
1091 } while(rec != TERMINATE_RECORD_TYPE && success);
1095 * @brief Reader header from the gif file and populates structures accordingly.
1097 * @param[in] loaderInfo A LoaderInfo structure containing file descriptor and other data about GIF.
1098 * @param[out] prop A ImageProperties structure for storing information about GIF data.
1099 * @return The true or false whether reading was successful or not.
1101 bool ReadHeader(LoaderInfo& loaderInfo,
1102 ImageProperties& prop)
1104 GifAnimationData& animated = loaderInfo.animated;
1105 GifCachedColorData& cachedColor = loaderInfo.cachedColor;
1106 LoaderInfo::FileData& fileData = loaderInfo.fileData;
1107 bool success = false;
1108 LoaderInfo::FileInfo fileInfo;
1111 // it is possible which gif file have error midle of frames,
1112 // in that case we should play gif file until meet error frame.
1113 int imageNumber = 0;
1115 FrameInfo* frameInfo = NULL;
1118 success = fileData.LoadFile();
1119 if(DALI_UNLIKELY(!success || !fileData.globalMap))
1122 DALI_LOG_ERROR("LOAD_ERROR_CORRUPT_FILE\n");
1126 fileInfo.map = fileData.globalMap;
1127 fileInfo.length = fileData.length;
1128 fileInfo.position = 0;
1129 GifAccessor gifAccessor(fileInfo);
1133 // get the gif "screen size" (the actual image size)
1134 prop.w = gifAccessor.gif->SWidth;
1135 prop.h = gifAccessor.gif->SHeight;
1137 // if size is invalid - abort here
1138 if(DALI_UNLIKELY((prop.w < 1) || (prop.h < 1) || (prop.w > IMG_MAX_SIZE) || (prop.h > IMG_MAX_SIZE) || IMG_TOO_BIG(prop.w, prop.h)))
1140 if(IMG_TOO_BIG(prop.w, prop.h))
1143 DALI_LOG_ERROR("LOAD_ERROR_RESOURCE_ALLOCATION_FAILED");
1148 DALI_LOG_ERROR("LOAD_ERROR_GENERIC");
1154 WalkThroughGifRecordsWhileReadingHeader(gifAccessor, rec, imageNumber, frameInfo, full, prop, animated, loopCount, success);
1158 // if the gif main says we have more than one image or our image counting
1159 // says so, then this image is animated - indicate this
1160 if((gifAccessor.gif->ImageCount > 1) || (imageNumber > 1))
1162 animated.animated = 1;
1163 animated.loopCount = loopCount;
1165 animated.frameCount = std::min(gifAccessor.gif->ImageCount, imageNumber);
1172 animated.currentFrame = 1;
1174 // cache global color map
1175 ColorMapObject* colorMap = gifAccessor.gif->SColorMap;
1178 cachedColor.globalCachedColor.resize(colorMap->ColorCount);
1179 for(int i = 0; i < colorMap->ColorCount; ++i)
1181 cachedColor.globalCachedColor[i] = PixelLookup(colorMap, i);
1190 DALI_LOG_ERROR("LOAD_ERROR_UNKNOWN_FORMAT\n");
1196 /// Walk through gif records in file to figure out info
1197 bool WalkThroughGifRecords(
1199 LoaderInfo& loaderInfo,
1200 GifAnimationData& animated,
1203 FrameInfo*& frameInfo,
1204 const ImageProperties& prop,
1205 ImageFrame*& lastPreservedFrame,
1206 unsigned char*& pixels)
1210 if(DALI_UNLIKELY(DGifGetRecordType(loaderInfo.gifAccessor->gif, &rec) == GIF_ERROR))
1212 DALI_LOG_ERROR("LOAD_ERROR_UNKNOWN_FORMAT\n");
1216 if(rec == EXTENSION_RECORD_TYPE)
1219 GifByteType* ext = NULL;
1220 DGifGetExtension(loaderInfo.gifAccessor->gif, &ext_code, &ext);
1225 DGifGetExtensionNext(loaderInfo.gifAccessor->gif, &ext);
1228 // get image description section
1229 else if(rec == IMAGE_DESC_RECORD_TYPE)
1231 int xin = 0, yin = 0, x = 0, y = 0, w = 0, h = 0;
1234 ImageFrame* previousFrame = NULL;
1235 ImageFrame* thisFrame = NULL;
1238 if(DALI_UNLIKELY(DGifGetImageDesc(loaderInfo.gifAccessor->gif) == GIF_ERROR))
1240 DALI_LOG_ERROR("LOAD_ERROR_UNKNOWN_FORMAT\n");
1244 // get the previous frame entry AND the current one to fill in
1245 previousFrame = FindFrame(animated, imageNumber - 1);
1246 thisFrame = FindFrame(animated, imageNumber);
1248 // if we have a frame AND we're animated AND we have no data...
1249 if((thisFrame) && (!thisFrame->data) && (animated.animated))
1254 thisFrame->data = new uint32_t[prop.w * prop.h];
1256 if(DALI_UNLIKELY(!thisFrame->data))
1258 DALI_LOG_ERROR("LOAD_ERROR_RESOURCE_ALLOCATION_FAILED");
1262 // Lazy fill background color feature.
1263 // DecodeImage function draw range is EQUAL with previous FillImage range,
1264 // We don't need to fill background that time.
1265 // It will try to reduce the number of FillImage API call
1266 // Note : We might check overlapping. But that operation looks expensive
1267 // So, just optimize only if EQUAL case.
1268 bool updateBackgroundColorLazy = false;
1269 uint32_t backgroundColor = 0u;
1275 // if we have no prior frame OR prior frame data... empty
1276 if((!previousFrame) || (!previousFrame->data))
1279 frameInfo = &(thisFrame->info);
1280 updateBackgroundColorLazy = true;
1281 backgroundColor = 0u;
1287 // we have a prior frame to copy data from...
1290 frameInfo = &(previousFrame->info);
1292 // fix coords of sub image in case it goes out...
1293 ClipCoordinates(prop.w, prop.h, &xin, &yin, frameInfo->x, frameInfo->y, frameInfo->w, frameInfo->h, &x, &y, &w, &h);
1295 // if dispose mode is not restore - then copy pre frame
1296 if(frameInfo->dispose != DISPOSE_PREVIOUS)
1298 memcpy(thisFrame->data, previousFrame->data, prop.w * prop.h * sizeof(uint32_t));
1301 // if dispose mode is "background" then fill with bg
1302 if(frameInfo->dispose == DISPOSE_BACKGROUND)
1304 updateBackgroundColorLazy = true;
1305 backgroundColor = GetBackgroundColor(loaderInfo.gifAccessor->gif, frameInfo);
1311 else if(frameInfo->dispose == DISPOSE_PREVIOUS) // GIF_DISPOSE_RESTORE
1316 // Find last preserved frame.
1317 lastPreservedFrame = FindFrame(animated, imageNumber - prevIndex);
1318 if(DALI_UNLIKELY(!lastPreservedFrame))
1320 DALI_LOG_ERROR("LOAD_ERROR_LAST_PRESERVED_FRAME_NOT_FOUND");
1324 } while(lastPreservedFrame && lastPreservedFrame->info.dispose == DISPOSE_PREVIOUS);
1326 if(lastPreservedFrame)
1328 memcpy(thisFrame->data, lastPreservedFrame->data, prop.w * prop.h * sizeof(uint32_t));
1332 // now draw this frame on top
1333 frameInfo = &(thisFrame->info);
1334 ClipCoordinates(prop.w, prop.h, &xin, &yin, frameInfo->x, frameInfo->y, frameInfo->w, frameInfo->h, &x, &y, &w, &h);
1336 if(updateBackgroundColorLazy)
1338 // If this frame's x,y,w,h is not equal with previous x,y,w,h, FillImage. else, don't fill
1339 if(prevX != x || prevY != y || prevW != w || prevH != h)
1341 FillImage(thisFrame->data, prop.w, backgroundColor, prevX, prevY, prevW, prevH);
1342 // Don't send background color information to DecodeImage function.
1343 updateBackgroundColorLazy = false;
1346 if(DALI_UNLIKELY(!DecodeImage(loaderInfo.gifAccessor->gif, loaderInfo.cachedColor, thisFrame->data, prop.w, xin, yin, frameInfo->transparent, x, y, w, h, first || updateBackgroundColorLazy, backgroundColor)))
1348 DALI_LOG_ERROR("LOAD_ERROR_CORRUPT_FILE\n");
1352 // mark as loaded and done
1353 thisFrame->loaded = true;
1355 FlushFrames(animated, prop.w, prop.h, thisFrame, previousFrame, lastPreservedFrame);
1357 // if we have a frame BUT the image is not animated. different
1359 else if((thisFrame) && (!thisFrame->data) && (!animated.animated))
1361 // if we don't have the data decoded yet - decode it
1362 if((!thisFrame->loaded) || (!thisFrame->data))
1364 // use frame info but we WONT allocate frame pixels
1365 frameInfo = &(thisFrame->info);
1366 ClipCoordinates(prop.w, prop.h, &xin, &yin, frameInfo->x, frameInfo->y, frameInfo->w, frameInfo->h, &x, &y, &w, &h);
1368 // clear out all pixels only if x,y,w,h is not whole image.
1369 if(x != 0 || y != 0 || w != static_cast<int>(prop.w) || h != static_cast<int>(prop.h))
1371 const std::uint32_t backgroundColor = GetBackgroundColor(loaderInfo.gifAccessor->gif, frameInfo);
1372 FillImage(reinterpret_cast<uint32_t*>(pixels), prop.w, backgroundColor, 0, 0, prop.w, prop.h);
1375 // and decode the gif with overwriting
1376 if(DALI_UNLIKELY(!DecodeImage(loaderInfo.gifAccessor->gif, loaderInfo.cachedColor, reinterpret_cast<uint32_t*>(pixels), prop.w, xin, yin, frameInfo->transparent, x, y, w, h, true, 0u)))
1378 DALI_LOG_ERROR("LOAD_ERROR_CORRUPT_FILE\n");
1382 // mark as loaded and done
1383 thisFrame->loaded = true;
1385 // flush mem we don't need (at expense of decode cpu)
1389 // skip decoding and just walk image to next
1390 if(DALI_UNLIKELY(DGifGetCode(loaderInfo.gifAccessor->gif, &img_code, &img) == GIF_ERROR))
1392 DALI_LOG_ERROR("LOAD_ERROR_UNKNOWN_FORMAT\n");
1399 DGifGetCodeNext(loaderInfo.gifAccessor->gif, &img);
1404 // if we found the image we wanted - get out of here
1405 if(imageNumber > index)
1410 } while(rec != TERMINATE_RECORD_TYPE);
1416 * @brief Reader next frame of the gif file and populates structures accordingly.
1418 * @param[in,out] loaderInfo A LoaderInfo structure containing file descriptor and other data about GIF.
1419 * @param[in,out] prop A ImageProperties structure containing information about gif data.
1420 * @param[out] pixels A pointer to buffer which will contain all pixel data of the frame on return.
1421 * @param[out] error Error code
1422 * @return The true or false whether reading was successful or not.
1424 bool ReadNextFrame(LoaderInfo& loaderInfo, ImageProperties& prop, // use for w and h
1425 unsigned char* pixels,
1428 GifAnimationData& animated = loaderInfo.animated;
1429 LoaderInfo::FileData& fileData = loaderInfo.fileData;
1432 int index = 0, imageNumber = 0;
1433 FrameInfo* frameInfo;
1434 ImageFrame* frame = NULL;
1435 ImageFrame* lastPreservedFrame = NULL;
1437 index = animated.currentFrame;
1439 // if index is invalid for animated image - error out
1440 if(DALI_UNLIKELY((animated.animated) && ((index <= 0) || (index > animated.frameCount))))
1442 DALI_LOG_ERROR("LOAD_ERROR_GENERIC");
1446 // find the given frame index
1447 frame = FindFrame(animated, index);
1448 if(DALI_UNLIKELY(!frame))
1450 DALI_LOG_ERROR("LOAD_ERROR_CORRUPT_FILE\n");
1453 else if(!(frame->loaded) || !(frame->data))
1455 // if we want to go backwards, we likely need/want to re-decode from the
1456 // start as we have nothing to build on. If there is a gif, imageNumber
1457 // has been set already.
1458 if(loaderInfo.gifAccessor && loaderInfo.imageNumber > 0)
1460 if((index > 0) && (index < loaderInfo.imageNumber) && (animated.animated))
1462 loaderInfo.gifAccessor.reset();
1463 loaderInfo.imageNumber = 0;
1467 // actually ask libgif to open the file
1468 if(!loaderInfo.gifAccessor)
1470 loaderInfo.fileInfo.map = fileData.globalMap;
1471 loaderInfo.fileInfo.length = fileData.length;
1472 loaderInfo.fileInfo.position = 0;
1473 if(DALI_UNLIKELY(!loaderInfo.fileInfo.map))
1475 DALI_LOG_ERROR("LOAD_ERROR_CORRUPT_FILE\n");
1478 std::unique_ptr<GifAccessor> gifAccessor = std::make_unique<GifAccessor>(loaderInfo.fileInfo);
1479 if(DALI_UNLIKELY(!gifAccessor->gif))
1481 DALI_LOG_ERROR("LOAD_ERROR_UNKNOWN_FORMAT\n");
1484 loaderInfo.gifAccessor = std::move(gifAccessor);
1485 loaderInfo.imageNumber = 1;
1488 // our current position is the previous frame we decoded from the file
1489 imageNumber = loaderInfo.imageNumber;
1491 if(!WalkThroughGifRecords(rec, loaderInfo, animated, imageNumber, index, frameInfo, prop, lastPreservedFrame, pixels))
1496 // if we are at the end of the animation or not animated, close file
1497 loaderInfo.imageNumber = imageNumber;
1498 if((animated.frameCount <= 1) || (rec == TERMINATE_RECORD_TYPE))
1500 loaderInfo.gifAccessor.reset();
1501 loaderInfo.imageNumber = 0;
1505 // no errors in header scan etc. so set err and return value
1509 // if it was an animated image we need to copy the data to the
1510 // pixels for the image from the frame holding the data
1511 if(animated.animated && frame->data)
1513 memcpy(pixels, frame->data, prop.w * prop.h * sizeof(uint32_t));
1519 } // unnamed namespace
1521 struct GifLoading::Impl
1524 Impl(const std::string& url, bool isLocalResource)
1526 mLoadSucceeded(false),
1529 loaderInfo.gifAccessor = nullptr;
1530 loaderInfo.fileData.fileName = mUrl.c_str();
1531 loaderInfo.fileData.isLocalResource = isLocalResource;
1534 bool LoadGifInformation()
1536 // Block to do not load this file again.
1537 Mutex::ScopedLock lock(mMutex);
1538 if(DALI_LIKELY(mLoadSucceeded))
1540 return mLoadSucceeded;
1543 mLoadSucceeded = ReadHeader(loaderInfo, imageProperties);
1546 DALI_LOG_ERROR("ReadHeader is failed [%s]\n", mUrl.c_str());
1548 return mLoadSucceeded;
1551 // Moveable but not copyable
1552 Impl(const Impl&) = delete;
1553 Impl& operator=(const Impl&) = delete;
1554 Impl(Impl&&) = default;
1555 Impl& operator=(Impl&&) = default;
1558 LoaderInfo loaderInfo;
1559 ImageProperties imageProperties;
1560 bool mLoadSucceeded;
1564 AnimatedImageLoadingPtr GifLoading::New(const std::string& url, bool isLocalResource)
1566 return AnimatedImageLoadingPtr(new GifLoading(url, isLocalResource));
1569 GifLoading::GifLoading(const std::string& url, bool isLocalResource)
1570 : mImpl(new GifLoading::Impl(url, isLocalResource))
1574 GifLoading::~GifLoading()
1579 Dali::Devel::PixelBuffer GifLoading::LoadFrame(uint32_t frameIndex, ImageDimensions desiredSize)
1582 Dali::Devel::PixelBuffer pixelBuffer;
1584 DALI_LOG_INFO(gGifLoadingLogFilter, Debug::Concise, "LoadFrame( frameIndex:%d )\n", frameIndex);
1586 // If Gif file is still not loaded, Load the information.
1587 if(DALI_UNLIKELY(!mImpl->LoadGifInformation()))
1592 Mutex::ScopedLock lock(mImpl->mMutex);
1593 pixelBuffer = Dali::Devel::PixelBuffer::New(mImpl->imageProperties.w, mImpl->imageProperties.h, Dali::Pixel::RGBA8888);
1595 mImpl->loaderInfo.animated.currentFrame = 1 + (frameIndex % mImpl->loaderInfo.animated.frameCount);
1596 ReadNextFrame(mImpl->loaderInfo, mImpl->imageProperties, pixelBuffer.GetBuffer(), &error);
1600 pixelBuffer = Dali::Devel::PixelBuffer();
1605 ImageDimensions GifLoading::GetImageSize() const
1607 if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
1609 mImpl->LoadGifInformation();
1611 return ImageDimensions(mImpl->imageProperties.w, mImpl->imageProperties.h);
1614 uint32_t GifLoading::GetImageCount() const
1616 if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
1618 mImpl->LoadGifInformation();
1620 return mImpl->loaderInfo.animated.frameCount;
1623 uint32_t GifLoading::GetFrameInterval(uint32_t frameIndex) const
1625 if(DALI_UNLIKELY(!mImpl->mLoadSucceeded))
1627 mImpl->LoadGifInformation();
1630 uint32_t interval = 0u;
1631 if(DALI_LIKELY(mImpl->mLoadSucceeded))
1633 interval = mImpl->loaderInfo.animated.frames[frameIndex].info.delay * 10;
1638 std::string GifLoading::GetUrl() const
1643 bool GifLoading::HasLoadingSucceeded() const
1645 return mImpl->mLoadSucceeded;
1648 } // namespace Adaptor
1650 } // namespace Internal