2 * Copyright (c) 2017 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.
17 * When the available GIFLIB is with version up to 4.1.6, for using the New functions DGifSavedExtensionToGCB()
18 * which makes it easy to read GIF89 graphics control blocks in saved images,
19 * we copied the DGifExtensionToGCB and DGifSavedExtensionToGCB functions
20 * along with the GraphicsControlBlock structure from the GIFLIB 5.1.4 to this source file.
22 * The GIFLIB distribution is Copyright (c) 1997 Eric S. Raymond
23 * Permission is hereby granted, free of charge, to any person obtaining a copy
24 * of this software and associated documentation files (the "Software"), to deal
25 * in the Software without restriction, including without limitation the rights
26 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
27 * copies of the Software, and to permit persons to whom the Software is
28 * furnished to do so, subject to the following conditions:
30 * The above copyright notice and this permission notice shall be included in
31 * all copies or substantial portions of the Software.
33 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
43 #include "gif-loading.h"
47 #include <dali/integration-api/debug.h>
48 #include <dali/public-api/math/rect.h>
49 #include <dali/public-api/images/pixel-data.h>
53 // forward declaration of function
54 void GifCopyLine(unsigned char* destination, unsigned char* source, const ColorMapObject* colorMap, int transparent, int copyWidth, bool replace );
56 #ifdef GIF_LIB_VERSION
58 /*********************************************
59 * GIFLIB version up to 4.1.6
60 ******************************************/
62 * With GIFLIB version up to 4.1.6, for using the New functions DGifSavedExtensionToGCB()
63 * which makes it easy to read GIF89 graphics control blocks in saved images,
64 * we copied the DGifExtensionToGCB and DGifSavedExtensionToGCB functions
65 * along with the GraphicsControlBlock structure from the GIFLIB 5.1.4 to this source file.
68 /* compose unsigned little endian value */
69 #define UNSIGNED_LITTLE_ENDIAN(lo, hi) ((lo) | ((hi) << 8))
71 typedef struct GraphicsControlBlock {
73 #define DISPOSAL_UNSPECIFIED 0 /* No disposal specified. */
74 #define DISPOSE_DO_NOT 1 /* Leave image in place */
75 #define DISPOSE_BACKGROUND 2 /* Set area too background color */
76 #define DISPOSE_PREVIOUS 3 /* Restore to previous content */
77 bool UserInputFlag; /* User confirmation required before disposal */
78 int DelayTime; /* pre-display delay in 0.01sec units */
79 int TransparentColor; /* Palette index for transparency, -1 if none */
80 #define NO_TRANSPARENT_COLOR -1
81 } GraphicsControlBlock;
83 /******************************************************************************
84 Extract a Graphics Control Block from raw extension data
85 ******************************************************************************/
86 int DGifExtensionToGCB(const size_t GifExtensionLength,
88 GraphicsControlBlock *GCB)
90 if (GifExtensionLength != 4)
95 GCB->DisposalMode = (GifExtension[0] >> 2) & 0x07;
96 GCB->UserInputFlag = (GifExtension[0] & 0x02) != 0;
97 GCB->DelayTime = UNSIGNED_LITTLE_ENDIAN(GifExtension[1], GifExtension[2]);
98 if (GifExtension[0] & 0x01)
100 GCB->TransparentColor = reinterpret_cast< int >( GifExtension[3]+256 ) % 256;
104 GCB->TransparentColor = NO_TRANSPARENT_COLOR;
110 /******************************************************************************
111 Extract the Graphics Control Block for a saved image, if it exists.
112 ******************************************************************************/
113 int DGifSavedExtensionToGCB(GifFileType *GifFile, int ImageIndex, GraphicsControlBlock *GCB)
117 if (ImageIndex < 0 || ImageIndex > GifFile->ImageCount - 1)
120 GCB->DisposalMode = DISPOSAL_UNSPECIFIED;
121 GCB->UserInputFlag = false;
123 GCB->TransparentColor = NO_TRANSPARENT_COLOR;
125 for (i = 0; i < GifFile->SavedImages[ImageIndex].ExtensionBlockCount; i++) {
126 ExtensionBlock *ep = &GifFile->SavedImages[ImageIndex].ExtensionBlocks[i];
127 if (ep->Function == GRAPHICS_EXT_FUNC_CODE)
128 return DGifExtensionToGCB(ep->ByteCount, ep->Bytes, GCB);
134 /******************************************************************************
135 End of code copy from GIFLIB version 5.
136 ******************************************************************************/
139 // simple class to enforce clean-up of GIF structures
140 struct GifAutoCleanup
142 GifAutoCleanup(GifFileType*& _gifInfo)
151 // clean up GIF resources
152 DGifCloseFile( gifInfo );
156 GifFileType*& gifInfo;
160 * Open a new gif file for read.
161 * @param[in] url The url of the gif to load.
162 * @param[out] gifInfo The dynamically allocated GifFileType pointer which serves as the GIF info record.
163 * @return True if the file is opened successfully, false otherwise.
165 bool GifOpen( const char* url, GifFileType*& gifInfo )
167 gifInfo = DGifOpenFileName( url );
168 if( gifInfo == NULL )
170 DALI_LOG_ERROR( "GIF Loader: DGifOpen failed. \n" );
177 * With GIFLIB version 4.1.6, the interlacing needs to be handled manually
179 // Used in the GIF interlace algorithm to determine the starting byte and the increment required
183 unsigned int startingByte;
184 unsigned int incrementalByte;
187 // Used in the GIF interlace algorithm to determine the order and which location to read data from
189 const InterlacePair INTERLACE_PAIR_TABLE [] = {
190 { 0, 8 }, // Starting at 0, read every 8 bytes.
191 { 4, 8 }, // Starting at 4, read every 8 bytes.
192 { 2, 4 }, // Starting at 2, read every 4 bytes.
193 { 1, 2 }, // Starting at 1, read every 2 bytes.
195 const int INTERLACE_PAIR_TABLE_SIZE( sizeof( INTERLACE_PAIR_TABLE ) / sizeof( InterlacePair ) );
198 * copy a image from color-index formated source to the the RGBA formated destination
199 * @param[out] destination The RGBA formated destination.
200 * @param[in] source The color-index formated source.
201 * @param[in] width The width of the destination image.
202 * @param[in] height The height of the destination image.
203 * @param[in] imageDesc The description of the source image.
204 * @param[in] colorMap The color map for mapping the color index to RGB values.
205 * @param[in] transparent The color index which is interpreted as transparent.
206 * @param[in] replace If true, the pixel with transparent color should be set as transparent.
207 * If false, skip the pixel with transparent color, so that the previously initialized color is used.
209 void GifCopyFrame( unsigned char* destination, unsigned char* source, int width, int height,
210 const GifImageDesc& imageDesc, const ColorMapObject* colorMap,
211 int transparent, bool replace )
213 // Calculate the copy size as the image might only cover sub area of the frame
214 int copyWidth = imageDesc.Width <= ( width-imageDesc.Left) ? imageDesc.Width : width - imageDesc.Left;
215 int copyHeight = imageDesc.Height <= ( height-imageDesc.Top) ? imageDesc.Height : height - imageDesc.Top;
218 // copy line by line from the color-index formated source to the RGBA formated destination.
219 if( imageDesc.Interlace ) // With GIFLIB version 4.1, the interlacing needs to be handled manually
221 const InterlacePair* interlacePairPtr( INTERLACE_PAIR_TABLE );
222 for ( int interlacePair = 0; interlacePair < INTERLACE_PAIR_TABLE_SIZE; ++interlacePair, ++interlacePairPtr )
224 for( int currentRow = interlacePairPtr->startingByte; currentRow < copyHeight; currentRow +=interlacePairPtr->incrementalByte )
226 row = destination + (imageDesc.Top + currentRow) * width * 4 + imageDesc.Left * 4 ;
227 GifCopyLine( row, source, colorMap, transparent, copyWidth, replace);
228 source += imageDesc.Width;
234 for( int currentRow = 0; currentRow < copyHeight; currentRow++ )
236 row = destination + (imageDesc.Top + currentRow) * width * 4 + imageDesc.Left * 4 ;
237 GifCopyLine( row, source, colorMap, transparent, copyWidth, replace);
238 source += imageDesc.Width;
245 /*************************************************
246 * GIFLIB major version 5
247 *************************************************/
249 // simple class to enforce clean-up of GIF structures
250 struct GifAutoCleanup
252 GifAutoCleanup(GifFileType*& _gifInfo)
261 // clean up GIF resources
262 int errorCode = 0; //D_GIF_SUCCEEDED is 0
263 DGifCloseFile( gifInfo, &errorCode );
267 DALI_LOG_ERROR( "GIF Loader: DGifCloseFile Error. Code: %d\n", errorCode );
272 GifFileType*& gifInfo;
276 * Open a new gif file for read.
277 * @param[in] url The url of the gif to load.
278 * @param[out] gifInfo The dynamically allocated GifFileType pointer which serves as the GIF info record.
279 * @return True if the file is opened successfully, false otherwise.
281 bool GifOpen( const char* url, GifFileType*& gifInfo )
284 gifInfo = DGifOpenFileName(url, &errorCode);
287 DALI_LOG_ERROR( "GIF Loader: DGifOpenFileName Error. Code: %d\n", errorCode );
294 * copy a image from color-index formated source to the the RGBA formated destination
295 * @param[out] destination The RGBA formated destination.
296 * @param[in] source The color-index formated source.
297 * @param[in] width The width of the destination image.
298 * @param[in] height The height of the destination image.
299 * @param[in] imageDesc The description of the source image.
300 * @param[in] colorMap The color map for mapping the color index to RGB values.
301 * @param[in] transparent The color index which is interpreted as transparent.
302 * @param[in] replace If true, the pixel with transparent color should be set as transparent.
303 * If false, skip the pixel with transparent color, so that the previously initialized color is used.
305 void GifCopyFrame( unsigned char* destination, unsigned char* source, int width, int height,
306 const GifImageDesc& imageDesc, const ColorMapObject* colorMap,
307 int transparent, bool replace )
309 // Calculate the copy size as the image might only cover sub area of the frame
310 int copyWidth = imageDesc.Width <= ( width-imageDesc.Left) ? imageDesc.Width : width - imageDesc.Left;
311 int copyHeight = imageDesc.Height <= ( height-imageDesc.Top) ? imageDesc.Height : height - imageDesc.Top;
314 // copy line by line from the color-index formated source to the RGBA formated destination.
315 for( int currentRow = 0; currentRow < copyHeight; currentRow++ )
317 row = destination + (imageDesc.Top + currentRow) * width * 4 + imageDesc.Left * 4 ;
318 GifCopyLine( row, source, colorMap, transparent, copyWidth, replace);
319 source += imageDesc.Width;
323 #endif // End of code for different GIF_LIB_VERSION
326 * copy one line from the color-index formated source to the RGBA formated destination.
327 * @param[out] destination The RGBA formated destination.
328 * @param[in] source The color-index formated source.
329 * @param[in] transparent The color index which is interpreted as transparent.
330 * @param[in] color The color map for mapping the color index to RGB values.
331 * @param[in] copyWidth The copy width.
332 * @param[in] replace If true, the pixel with transparent color should be set as transparent.
333 * If false, skip the pixel with transparent color, so that the previously initialized color is used.
335 void GifCopyLine(unsigned char* destination, unsigned char* source, const ColorMapObject* colorMap, int transparent, int copyWidth, bool replace )
337 for ( ; copyWidth > 0; copyWidth--, source++ )
339 if( replace || *source != transparent )
341 *(destination++) = colorMap->Colors[*source].Red;
342 *(destination++) = colorMap->Colors[*source].Green;
343 *(destination++) = colorMap->Colors[*source].Blue;
344 *(destination++) = *source == transparent ? 0x00 : 0xff;
355 * Decode one frame of the animated gif.
356 * @param[out] delay The delay time of this frame.
357 * @param[in] gifInfo The GifFileType structure.
358 * @param[in] backgroundcolor The global background color.
359 * @param[in] frameIndex The index of this frame.
360 * @param[in] lastPreservedFrame The pixel buffer of the last preserved frame.
361 * @param[in] previousFrame The pixel buffer of the previous frame.
362 * @param[in] clearFrameArea The area to be cleared if the disposal mode is DISPOSE_BACKGROUND
363 * @return The pixel buffer of the current frame. *
365 unsigned char* DecodeOneFrame( int& delay, GifFileType* gifInfo, const Dali::Vector<unsigned char>& backgroundColor,
366 unsigned int frameIndex, unsigned char*& lastPreservedFrame,
367 unsigned char*& previousFrame, Dali::Rect<int>& clearFrameArea )
369 // Fetch the graphics control block
370 GraphicsControlBlock graphicsControlBlock;
371 if( int errorCode = DGifSavedExtensionToGCB( gifInfo, frameIndex, &graphicsControlBlock ) != GIF_OK
372 && gifInfo->ImageCount > 1 ) // for static gif, graphics control block may not been specified
374 DALI_LOG_ERROR( "GIF Loader: DGifSavedExtensionToGCB Error. Code: %d\n", errorCode );
377 // Read frame delay time, multiply 10 to change time unit to millisecods
378 delay = graphicsControlBlock.DelayTime * 10.f;
380 const int width = gifInfo->SWidth;
381 const int height = gifInfo->SHeight;
383 const SavedImage& frame = gifInfo->SavedImages[frameIndex];
385 // get the color map. If there is a local one, use the local color map, otherwise use the global color map
386 ColorMapObject* colorMap = frame.ImageDesc.ColorMap ? frame.ImageDesc.ColorMap : gifInfo->SColorMap;
387 if (colorMap == NULL || colorMap->ColorCount != (1 << colorMap->BitsPerPixel))
389 DALI_LOG_WARNING( "GIF Loader: potentially corrupt color map\n" );
393 // Allocate the buffer
394 int bufferSize = width*height*4;
395 unsigned char* buffer = new unsigned char[ bufferSize ];
397 // check whether buffer initializetion is needed
398 bool completelyCovered = graphicsControlBlock.TransparentColor == NO_TRANSPARENT_COLOR
399 && frame.ImageDesc.Left == 0 && frame.ImageDesc.Top == 0
400 && frame.ImageDesc.Width == width && frame.ImageDesc.Height == height;
402 // if not completely covered, needs to initialise the pixels
403 // depends on the disposal method, it would be initialised to background color, previous frame or the last preserved frame
404 if( !completelyCovered )
406 if( previousFrame && ( graphicsControlBlock.DisposalMode == DISPOSAL_UNSPECIFIED
407 || graphicsControlBlock.DisposalMode == DISPOSE_DO_NOT
408 || graphicsControlBlock.DisposalMode == DISPOSE_BACKGROUND) )
410 // disposal none: overlaid on the previous frame
411 if( clearFrameArea.height < height || clearFrameArea.width < width )
413 for( int i = 0; i < bufferSize; i++ )
415 buffer[i] = previousFrame[i];
418 // background disposal: When the time delay is finished for a particular frame, the area that was overlaid by that frame is cleared.
419 // Not the whole canvas, just the area that was overlaid. Once that is done then the resulting canvas is what is passed to the next frame of the animation,
420 // to be overlaid by that frames image.
421 for( int row = 0; row < clearFrameArea.height; row++ )
423 int idx = ( clearFrameArea.y + row)* width *4 + clearFrameArea.x * 4 + 3;
424 for( int col = 0; col < clearFrameArea.width; col++, idx+=4 )
430 else if( lastPreservedFrame && graphicsControlBlock.DisposalMode == DISPOSE_PREVIOUS )
432 // previous disposal: When the current image is finished, return the canvas to what it looked like before the image was overlaid.
433 for( int i = 0; i < bufferSize; i++ )
435 buffer[i] = lastPreservedFrame[i];
438 else if( !previousFrame && graphicsControlBlock.DisposalMode == DISPOSE_BACKGROUND)
440 // background disposal for first frame: clear to transparency
441 for( int i = 3; i < bufferSize; i+=4 )
448 for( int i = 0; i < bufferSize; i+=4 )
450 buffer[i] = backgroundColor[0];
451 buffer[i+1] = backgroundColor[1];
452 buffer[i+2] = backgroundColor[2];
453 buffer[i+3] = backgroundColor[3];
458 unsigned char* source = frame.RasterBits;
459 bool replace = completelyCovered || (frameIndex == 0 && graphicsControlBlock.DisposalMode != DISPOSE_BACKGROUND);
460 GifCopyFrame( buffer, source, width, height, frame.ImageDesc, colorMap, graphicsControlBlock.TransparentColor, replace );
462 // update the pixel buffer of the previous frame and the last preserved frame
463 if( graphicsControlBlock.DisposalMode != DISPOSE_BACKGROUND && graphicsControlBlock.DisposalMode != DISPOSE_PREVIOUS )
465 lastPreservedFrame = buffer;
467 previousFrame = buffer;
468 if( graphicsControlBlock.DisposalMode == DISPOSE_BACKGROUND )
470 clearFrameArea.x = frame.ImageDesc.Left;
471 clearFrameArea.y = frame.ImageDesc.Top;
472 clearFrameArea.width = frame.ImageDesc.Width;
473 clearFrameArea.height = frame.ImageDesc.Height;
477 clearFrameArea.width = 0;
478 clearFrameArea.height = 0;
484 } // Anonymous namespace
489 bool LoadAnimatedGifFromFile( const std::string& url, std::vector<Dali::PixelData>& pixelData, Dali::Vector<uint32_t>& frameDelays )
492 GifFileType* gifInfo = NULL;
493 GifAutoCleanup autoGif( gifInfo );
494 // enforce clean-up of the GIF structure when finishing this method.
495 if( !GifOpen( url.c_str(), gifInfo ) )
501 if( DGifSlurp( gifInfo ) != GIF_OK )
503 DALI_LOG_ERROR( "GIF Loader: DGifSlurp failed. \n" );
507 // validate attributes
508 if( gifInfo->ImageCount < 1 )
510 DALI_LOG_ERROR( "GIF Loader: frame count < 1. \n" );
514 // read the image size and frame count
515 ImageDimensions size( gifInfo->SWidth, gifInfo->SHeight );
518 unsigned char* previousFrame = NULL;
519 unsigned char* lastPreservedFrame = NULL;
521 // previous frame area
522 Rect<int> clearFrameArea;
524 // get background color
525 Dali::Vector<unsigned char> backgroundColor;
526 backgroundColor.Resize( 4 );
527 ColorMapObject* globalColorMap = gifInfo->SColorMap;
528 if( gifInfo->SColorMap )
530 backgroundColor[0] = globalColorMap->Colors[ gifInfo->SBackGroundColor ].Red;
531 backgroundColor[1] = globalColorMap->Colors[ gifInfo->SBackGroundColor ].Green;
532 backgroundColor[2] = globalColorMap->Colors[ gifInfo->SBackGroundColor ].Blue;
533 backgroundColor[3] = 0xff;
537 unsigned char* buffer = NULL;
538 // decode the gif frame by frame
541 for( int i = 0; i < gifInfo->ImageCount; i++ )
543 buffer = DecodeOneFrame( delay, gifInfo, backgroundColor, i, lastPreservedFrame, previousFrame, clearFrameArea );
546 pixelData.push_back( Dali::PixelData::New( buffer, size.GetWidth()*size.GetHeight()*4,
547 size.GetWidth(), size.GetHeight(),
548 Dali::Pixel::RGBA8888, Dali::PixelData::DELETE_ARRAY ) );
549 frameDelays.PushBack( delay );
553 DALI_LOG_ERROR( "GIF Loader: Loade frame data fail. FrameIndex: %d\n", i );
560 ImageDimensions GetGifImageSize( const std::string& url )
562 GifFileType* gifInfo = NULL;
563 // enforce clean-up of the GIF structure when finishing this method.
564 GifAutoCleanup autoGif( gifInfo );
565 if( !GifOpen( url.c_str(), gifInfo ) )
567 return ImageDimensions();
569 return ImageDimensions( gifInfo->SWidth, gifInfo->SHeight );