2 * Copyright (c) 2016 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) {
94 GCB->DisposalMode = (GifExtension[0] >> 2) & 0x07;
95 GCB->UserInputFlag = (GifExtension[0] & 0x02) != 0;
96 GCB->DelayTime = UNSIGNED_LITTLE_ENDIAN(GifExtension[1], GifExtension[2]);
97 if (GifExtension[0] & 0x01)
98 GCB->TransparentColor = ((int)GifExtension[3]+256)%256;
100 GCB->TransparentColor = NO_TRANSPARENT_COLOR;
105 /******************************************************************************
106 Extract the Graphics Control Block for a saved image, if it exists.
107 ******************************************************************************/
108 int DGifSavedExtensionToGCB(GifFileType *GifFile, int ImageIndex, GraphicsControlBlock *GCB)
112 if (ImageIndex < 0 || ImageIndex > GifFile->ImageCount - 1)
115 GCB->DisposalMode = DISPOSAL_UNSPECIFIED;
116 GCB->UserInputFlag = false;
118 GCB->TransparentColor = NO_TRANSPARENT_COLOR;
120 for (i = 0; i < GifFile->SavedImages[ImageIndex].ExtensionBlockCount; i++) {
121 ExtensionBlock *ep = &GifFile->SavedImages[ImageIndex].ExtensionBlocks[i];
122 if (ep->Function == GRAPHICS_EXT_FUNC_CODE)
123 return DGifExtensionToGCB(ep->ByteCount, ep->Bytes, GCB);
129 /******************************************************************************
130 End of code copy from GIFLIB version 5.
131 ******************************************************************************/
134 // simple class to enforce clean-up of GIF structures
135 struct GifAutoCleanup
137 GifAutoCleanup(GifFileType*& _gifInfo)
146 // clean up GIF resources
147 DGifCloseFile( gifInfo );
151 GifFileType*& gifInfo;
155 * Open a new gif file for read.
156 * @param[in] url The url of the gif to load.
157 * @param[out] gifInfo The dynamically allocated GifFileType pointer which serves as the GIF info record.
158 * @return True if the file is opened successfully, false otherwise.
160 bool GifOpen( const char* url, GifFileType*& gifInfo )
162 gifInfo = DGifOpenFileName( url );
163 if( gifInfo == NULL )
165 DALI_LOG_ERROR( "GIF Loader: DGifOpen failed. \n" );
172 * With GIFLIB version 4.1.6, the interlacing needs to be handled manually
174 // Used in the GIF interlace algorithm to determine the starting byte and the increment required
178 unsigned int startingByte;
179 unsigned int incrementalByte;
182 // Used in the GIF interlace algorithm to determine the order and which location to read data from
184 const InterlacePair INTERLACE_PAIR_TABLE [] = {
185 { 0, 8 }, // Starting at 0, read every 8 bytes.
186 { 4, 8 }, // Starting at 4, read every 8 bytes.
187 { 2, 4 }, // Starting at 2, read every 4 bytes.
188 { 1, 2 }, // Starting at 1, read every 2 bytes.
190 const int INTERLACE_PAIR_TABLE_SIZE( sizeof( INTERLACE_PAIR_TABLE ) / sizeof( InterlacePair ) );
193 * copy a image from color-index formated source to the the RGBA formated destination
194 * @param[out] destination The RGBA formated destination.
195 * @param[in] source The color-index formated source.
196 * @param[in] width The width of the destination image.
197 * @param[in] height The height of the destination image.
198 * @param[in] imageDesc The description of the source image.
199 * @param[in] colorMap The color map for mapping the color index to RGB values.
200 * @param[in] transparent The color index which is interpreted as transparent.
201 * @param[in] replace If true, the pixel with transparent color should be set as transparent.
202 * If false, skip the pixel with transparent color, so that the previously initialized color is used.
204 void GifCopyFrame( unsigned char* destination, unsigned char* source, int width, int height,
205 const GifImageDesc& imageDesc, const ColorMapObject* colorMap,
206 int transparent, bool replace )
208 // Calculate the copy size as the image might only cover sub area of the frame
209 int copyWidth = imageDesc.Width <= ( width-imageDesc.Left) ? imageDesc.Width : width - imageDesc.Left;
210 int copyHeight = imageDesc.Height <= ( height-imageDesc.Top) ? imageDesc.Height : height - imageDesc.Top;
213 // copy line by line from the color-index formated source to the RGBA formated destination.
214 if( imageDesc.Interlace ) // With GIFLIB version 4.1, the interlacing needs to be handled manually
216 const InterlacePair* interlacePairPtr( INTERLACE_PAIR_TABLE );
217 for ( int interlacePair = 0; interlacePair < INTERLACE_PAIR_TABLE_SIZE; ++interlacePair, ++interlacePairPtr )
219 for( int currentRow = interlacePairPtr->startingByte; currentRow < copyHeight; currentRow +=interlacePairPtr->incrementalByte )
221 row = destination + (imageDesc.Top + currentRow) * width * 4 + imageDesc.Left * 4 ;
222 GifCopyLine( row, source, colorMap, transparent, copyWidth, replace);
223 source += imageDesc.Width;
229 for( int currentRow = 0; currentRow < copyHeight; currentRow++ )
231 row = destination + (imageDesc.Top + currentRow) * width * 4 + imageDesc.Left * 4 ;
232 GifCopyLine( row, source, colorMap, transparent, copyWidth, replace);
233 source += imageDesc.Width;
240 /*************************************************
241 * GIFLIB major version 5
242 *************************************************/
244 // simple class to enforce clean-up of GIF structures
245 struct GifAutoCleanup
247 GifAutoCleanup(GifFileType*& _gifInfo)
256 // clean up GIF resources
257 int errorCode = 0; //D_GIF_SUCCEEDED is 0
258 DGifCloseFile( gifInfo, &errorCode );
262 DALI_LOG_ERROR( "GIF Loader: DGifCloseFile Error. Code: %d\n", errorCode );
267 GifFileType*& gifInfo;
271 * Open a new gif file for read.
272 * @param[in] url The url of the gif to load.
273 * @param[out] gifInfo The dynamically allocated GifFileType pointer which serves as the GIF info record.
274 * @return True if the file is opened successfully, false otherwise.
276 bool GifOpen( const char* url, GifFileType*& gifInfo )
279 gifInfo = DGifOpenFileName(url, &errorCode);
282 DALI_LOG_ERROR( "GIF Loader: DGifOpenFileName Error. Code: %d\n", errorCode );
289 * copy a image from color-index formated source to the the RGBA formated destination
290 * @param[out] destination The RGBA formated destination.
291 * @param[in] source The color-index formated source.
292 * @param[in] width The width of the destination image.
293 * @param[in] height The height of the destination image.
294 * @param[in] imageDesc The description of the source image.
295 * @param[in] colorMap The color map for mapping the color index to RGB values.
296 * @param[in] transparent The color index which is interpreted as transparent.
297 * @param[in] replace If true, the pixel with transparent color should be set as transparent.
298 * If false, skip the pixel with transparent color, so that the previously initialized color is used.
300 void GifCopyFrame( unsigned char* destination, unsigned char* source, int width, int height,
301 const GifImageDesc& imageDesc, const ColorMapObject* colorMap,
302 int transparent, bool replace )
304 // Calculate the copy size as the image might only cover sub area of the frame
305 int copyWidth = imageDesc.Width <= ( width-imageDesc.Left) ? imageDesc.Width : width - imageDesc.Left;
306 int copyHeight = imageDesc.Height <= ( height-imageDesc.Top) ? imageDesc.Height : height - imageDesc.Top;
309 // copy line by line from the color-index formated source to the RGBA formated destination.
310 for( int currentRow = 0; currentRow < copyHeight; currentRow++ )
312 row = destination + (imageDesc.Top + currentRow) * width * 4 + imageDesc.Left * 4 ;
313 GifCopyLine( row, source, colorMap, transparent, copyWidth, replace);
314 source += imageDesc.Width;
318 #endif // End of code for different GIF_LIB_VERSION
321 * copy one line from the color-index formated source to the RGBA formated destination.
322 * @param[out] destination The RGBA formated destination.
323 * @param[in] source The color-index formated source.
324 * @param[in] transparent The color index which is interpreted as transparent.
325 * @param[in] color The color map for mapping the color index to RGB values.
326 * @param[in] copyWidth The copy width.
327 * @param[in] replace If true, the pixel with transparent color should be set as transparent.
328 * If false, skip the pixel with transparent color, so that the previously initialized color is used.
330 void GifCopyLine(unsigned char* destination, unsigned char* source, const ColorMapObject* colorMap, int transparent, int copyWidth, bool replace )
332 for ( ; copyWidth > 0; copyWidth--, source++ )
334 if( replace || *source != transparent )
336 *(destination++) = colorMap->Colors[*source].Red;
337 *(destination++) = colorMap->Colors[*source].Green;
338 *(destination++) = colorMap->Colors[*source].Blue;
339 *(destination++) = *source == transparent ? 0x00 : 0xff;
350 * Decode one frame of the animated gif.
351 * @param[out] delay The delay time of this frame.
352 * @param[in] gifInfo The GifFileType structure.
353 * @param[in] backgroundcolor The global background color.
354 * @param[in] frameIndex The index of this frame.
355 * @param[in] lastPreservedFrame The pixel buffer of the last preserved frame.
356 * @param[in] previousFrame The pixel buffer of the previous frame.
357 * @param[in] clearFrameArea The area to be cleared if the disposal mode is DISPOSE_BACKGROUND
358 * @return The pixel buffer of the current frame. *
360 unsigned char* DecodeOneFrame( int& delay, GifFileType* gifInfo, const Dali::Vector<unsigned char>& backgroundColor,
361 unsigned int frameIndex, unsigned char*& lastPreservedFrame,
362 unsigned char*& previousFrame, Dali::Rect<int>& clearFrameArea )
364 // Fetch the graphics control block
365 GraphicsControlBlock graphicsControlBlock;
366 if( int errorCode = DGifSavedExtensionToGCB( gifInfo, frameIndex, &graphicsControlBlock ) != GIF_OK
367 && gifInfo->ImageCount > 1 ) // for static gif, graphics control block may not been specified
369 DALI_LOG_ERROR( "GIF Loader: DGifSavedExtensionToGCB Error. Code: %d\n", errorCode );
372 // Read frame delay time, multiply 10 to change time unit to millisecods
373 delay = graphicsControlBlock.DelayTime * 10.f;
375 const int width = gifInfo->SWidth;
376 const int height = gifInfo->SHeight;
378 const SavedImage& frame = gifInfo->SavedImages[frameIndex];
380 // get the color map. If there is a local one, use the local color map, otherwise use the global color map
381 ColorMapObject* colorMap = frame.ImageDesc.ColorMap ? frame.ImageDesc.ColorMap : gifInfo->SColorMap;
382 if (colorMap == NULL || colorMap->ColorCount != (1 << colorMap->BitsPerPixel))
384 DALI_LOG_WARNING( "GIF Loader: potentially corrupt color map\n" );
388 // Allocate the buffer
389 int bufferSize = width*height*4;
390 unsigned char* buffer = new unsigned char[ bufferSize ];
392 // check whether buffer initializetion is needed
393 bool completelyCovered = graphicsControlBlock.TransparentColor == NO_TRANSPARENT_COLOR
394 && frame.ImageDesc.Left == 0 && frame.ImageDesc.Top == 0
395 && frame.ImageDesc.Width == width && frame.ImageDesc.Height == height;
397 // if not completely covered, needs to initialise the pixels
398 // depends on the disposal method, it would be initialised to background color, previous frame or the last preserved frame
399 if( !completelyCovered )
401 if( previousFrame && ( graphicsControlBlock.DisposalMode == DISPOSAL_UNSPECIFIED
402 || graphicsControlBlock.DisposalMode == DISPOSE_DO_NOT
403 || graphicsControlBlock.DisposalMode == DISPOSE_BACKGROUND) )
405 // disposal none: overlaid on the previous frame
406 if( clearFrameArea.height < height || clearFrameArea.width < width )
408 for( int i = 0; i < bufferSize; i++ )
410 buffer[i] = previousFrame[i];
413 // background disposal: When the time delay is finished for a particular frame, the area that was overlaid by that frame is cleared.
414 // 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,
415 // to be overlaid by that frames image.
416 for( int row = 0; row < clearFrameArea.height; row++ )
418 int idx = ( clearFrameArea.y + row)* width *4 + clearFrameArea.x * 4 + 3;
419 for( int col = 0; col < clearFrameArea.width; col++, idx+=4 )
425 else if( lastPreservedFrame && graphicsControlBlock.DisposalMode == DISPOSE_PREVIOUS )
427 // previous disposal: When the current image is finished, return the canvas to what it looked like before the image was overlaid.
428 for( int i = 0; i < bufferSize; i++ )
430 buffer[i] = lastPreservedFrame[i];
433 else if( !previousFrame && graphicsControlBlock.DisposalMode == DISPOSE_BACKGROUND)
435 // background disposal for first frame: clear to transparency
436 for( int i = 3; i < bufferSize; i+=4 )
443 for( int i = 0; i < bufferSize; i+=4 )
445 buffer[i] = backgroundColor[0];
446 buffer[i+1] = backgroundColor[1];
447 buffer[i+2] = backgroundColor[2];
448 buffer[i+3] = backgroundColor[3];
453 unsigned char* source = frame.RasterBits;
454 bool replace = completelyCovered || (frameIndex == 0 && graphicsControlBlock.DisposalMode != DISPOSE_BACKGROUND);
455 GifCopyFrame( buffer, source, width, height, frame.ImageDesc, colorMap, graphicsControlBlock.TransparentColor, replace );
457 // update the pixel buffer of the previous frame and the last preserved frame
458 if( graphicsControlBlock.DisposalMode != DISPOSE_BACKGROUND && graphicsControlBlock.DisposalMode != DISPOSE_PREVIOUS )
460 lastPreservedFrame = buffer;
462 previousFrame = buffer;
463 if( graphicsControlBlock.DisposalMode == DISPOSE_BACKGROUND )
465 clearFrameArea.x = frame.ImageDesc.Left;
466 clearFrameArea.y = frame.ImageDesc.Top;
467 clearFrameArea.width = frame.ImageDesc.Width;
468 clearFrameArea.height = frame.ImageDesc.Height;
472 clearFrameArea.width = 0;
473 clearFrameArea.height = 0;
479 } // Anonymous namespace
484 bool LoadAnimatedGifFromFile( const std::string& url, std::vector<Dali::PixelData>& pixelData, Dali::Vector<uint32_t>& frameDelays )
487 GifFileType* gifInfo = NULL;
488 GifAutoCleanup autoGif( gifInfo );
489 // enforce clean-up of the GIF structure when finishing this method.
490 if( !GifOpen( url.c_str(), gifInfo ) )
496 if( DGifSlurp( gifInfo ) != GIF_OK )
498 DALI_LOG_ERROR( "GIF Loader: DGifSlurp failed. \n" );
502 // validate attributes
503 if( gifInfo->ImageCount < 1 )
505 DALI_LOG_ERROR( "GIF Loader: frame count < 1. \n" );
509 // read the image size and frame count
510 ImageDimensions size( gifInfo->SWidth, gifInfo->SHeight );
513 unsigned char* previousFrame = NULL;
514 unsigned char* lastPreservedFrame = NULL;
516 // previous frame area
517 Rect<int> clearFrameArea;
519 // get background color
520 Dali::Vector<unsigned char> backgroundColor;
521 backgroundColor.Resize( 4 );
522 ColorMapObject* globalColorMap = gifInfo->SColorMap;
523 if( gifInfo->SColorMap )
525 backgroundColor[0] = globalColorMap->Colors[ gifInfo->SBackGroundColor ].Red;
526 backgroundColor[1] = globalColorMap->Colors[ gifInfo->SBackGroundColor ].Green;
527 backgroundColor[2] = globalColorMap->Colors[ gifInfo->SBackGroundColor ].Blue;
528 backgroundColor[3] = 0xff;
532 unsigned char* buffer = NULL;
533 // decode the gif frame by frame
536 for( int i = 0; i < gifInfo->ImageCount; i++ )
538 buffer = DecodeOneFrame( delay, gifInfo, backgroundColor, i, lastPreservedFrame, previousFrame, clearFrameArea );
541 pixelData.push_back( Dali::PixelData::New( buffer, size.GetWidth()*size.GetHeight()*4,
542 size.GetWidth(), size.GetHeight(),
543 Dali::Pixel::RGBA8888, Dali::PixelData::DELETE_ARRAY ) );
544 frameDelays.PushBack( delay );
548 DALI_LOG_ERROR( "GIF Loader: Loade frame data fail. FrameIndex: %d\n", i );
555 ImageDimensions GetGifImageSize( const std::string& url )
557 GifFileType* gifInfo = NULL;
558 // enforce clean-up of the GIF structure when finishing this method.
559 GifAutoCleanup autoGif( gifInfo );
560 if( !GifOpen( url.c_str(), gifInfo ) )
562 return ImageDimensions();
564 return ImageDimensions( gifInfo->SWidth, gifInfo->SHeight );