Revert "[Tizen] fix that some animated-gif files are not playing"
[platform/core/uifw/dali-adaptor.git] / adaptors / devel-api / adaptor-framework / gif-loading.cpp
1 /*
2  * Copyright (c) 2017 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  *
16  *
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.
21  *
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:
29  *
30  *     The above copyright notice and this permission notice shall be included in
31  *     all copies or substantial portions of the Software.
32
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
39  *     THE SOFTWARE.
40  */
41
42 // CLASS HEADER
43 #include "gif-loading.h"
44
45 // EXTERNAL INCLUDES
46 #include <gif_lib.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>
50
51 namespace
52 {
53 // forward declaration of function
54 void GifCopyLine(unsigned char* destination, unsigned char* source, const ColorMapObject* colorMap, int transparent, int copyWidth, bool replace );
55
56 #ifdef GIF_LIB_VERSION
57
58 /*********************************************
59  *  GIFLIB version up to 4.1.6
60  ******************************************/
61 /**
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.
66  */
67
68 /* compose unsigned little endian value */
69 #define UNSIGNED_LITTLE_ENDIAN(lo, hi) ((lo) | ((hi) << 8))
70
71 typedef struct GraphicsControlBlock {
72     int DisposalMode;
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;
82
83 /******************************************************************************
84  Extract a Graphics Control Block from raw extension data
85 ******************************************************************************/
86 int DGifExtensionToGCB(const size_t GifExtensionLength,
87                        char *GifExtension,
88                        GraphicsControlBlock *GCB)
89 {
90   if (GifExtensionLength != 4)
91   {
92     return GIF_ERROR;
93   }
94
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)
99   {
100     GCB->TransparentColor = reinterpret_cast< int >( GifExtension[3]+256 ) % 256;
101   }
102   else
103   {
104     GCB->TransparentColor = NO_TRANSPARENT_COLOR;
105   }
106
107   return GIF_OK;
108 }
109
110 /******************************************************************************
111  Extract the Graphics Control Block for a saved image, if it exists.
112 ******************************************************************************/
113 int DGifSavedExtensionToGCB(GifFileType *GifFile, int ImageIndex, GraphicsControlBlock *GCB)
114 {
115   int i;
116
117   if (ImageIndex < 0 || ImageIndex > GifFile->ImageCount - 1)
118     return GIF_ERROR;
119
120   GCB->DisposalMode = DISPOSAL_UNSPECIFIED;
121   GCB->UserInputFlag = false;
122   GCB->DelayTime = 0;
123   GCB->TransparentColor = NO_TRANSPARENT_COLOR;
124
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);
129   }
130
131   return GIF_ERROR;
132 }
133
134 /******************************************************************************
135  End of code copy from GIFLIB version 5.
136 ******************************************************************************/
137
138
139 // simple class to enforce clean-up of GIF structures
140 struct GifAutoCleanup
141 {
142   GifAutoCleanup(GifFileType*& _gifInfo)
143   : gifInfo(_gifInfo)
144   {
145   }
146
147   ~GifAutoCleanup()
148   {
149     if(NULL != gifInfo)
150     {
151       // clean up GIF resources
152       DGifCloseFile( gifInfo );
153     }
154   }
155
156   GifFileType*& gifInfo;
157 };
158
159 /**
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.
164  */
165 bool GifOpen( const char* url, GifFileType*& gifInfo )
166 {
167   gifInfo = DGifOpenFileName( url );
168   if( gifInfo == NULL )
169   {
170     DALI_LOG_ERROR( "GIF Loader: DGifOpen failed. \n" );
171     return false;
172   }
173   return true;
174 }
175
176 /**
177  * With GIFLIB version 4.1.6, the interlacing needs to be handled manually
178  */
179 // Used in the GIF interlace algorithm to determine the starting byte and the increment required
180 // for each pass.
181 struct InterlacePair
182 {
183   unsigned int startingByte;
184   unsigned int incrementalByte;
185 };
186
187 // Used in the GIF interlace algorithm to determine the order and which location to read data from
188 // the file.
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.
194 };
195 const int INTERLACE_PAIR_TABLE_SIZE( sizeof( INTERLACE_PAIR_TABLE ) / sizeof( InterlacePair ) );
196
197 /**
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.
208  */
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 )
212 {
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;
216
217   unsigned char* row;
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
220   {
221     const InterlacePair* interlacePairPtr( INTERLACE_PAIR_TABLE );
222     for ( int interlacePair = 0; interlacePair < INTERLACE_PAIR_TABLE_SIZE; ++interlacePair, ++interlacePairPtr )
223     {
224       for( int currentRow = interlacePairPtr->startingByte; currentRow < copyHeight; currentRow +=interlacePairPtr->incrementalByte )
225       {
226         row = destination + (imageDesc.Top + currentRow) * width * 4 + imageDesc.Left * 4 ;
227         GifCopyLine( row, source, colorMap, transparent, copyWidth, replace);
228         source += imageDesc.Width;
229       }
230     }
231   }
232   else
233   {
234     for( int currentRow = 0; currentRow < copyHeight; currentRow++ )
235     {
236       row = destination + (imageDesc.Top + currentRow) * width * 4 + imageDesc.Left * 4 ;
237       GifCopyLine( row, source, colorMap, transparent, copyWidth, replace);
238       source += imageDesc.Width;
239     }
240   }
241 }
242
243 #else
244
245 /*************************************************
246  * GIFLIB major version 5
247  *************************************************/
248
249 // simple class to enforce clean-up of GIF structures
250 struct GifAutoCleanup
251 {
252   GifAutoCleanup(GifFileType*& _gifInfo)
253   : gifInfo(_gifInfo)
254   {
255   }
256
257   ~GifAutoCleanup()
258   {
259     if(NULL != gifInfo)
260     {
261       // clean up GIF resources
262       int errorCode = 0; //D_GIF_SUCCEEDED is 0
263       DGifCloseFile( gifInfo, &errorCode );
264
265       if( errorCode )
266       {
267         DALI_LOG_ERROR( "GIF Loader: DGifCloseFile Error. Code: %d\n", errorCode );
268       }
269     }
270   }
271
272   GifFileType*& gifInfo;
273 };
274
275 /**
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.
280  */
281 bool GifOpen( const char* url, GifFileType*& gifInfo )
282 {
283   int errorCode;
284   gifInfo = DGifOpenFileName(url, &errorCode);
285   if (gifInfo == NULL)
286   {
287     DALI_LOG_ERROR( "GIF Loader: DGifOpenFileName Error. Code: %d\n", errorCode );
288     return false;
289   }
290   return true;
291 }
292
293 /**
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.
304  */
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 )
308 {
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;
312
313   unsigned char* row;
314   // copy line by line from the color-index formated source to the RGBA formated destination.
315   for( int currentRow = 0; currentRow < copyHeight; currentRow++ )
316   {
317     row = destination + (imageDesc.Top + currentRow) * width * 4 + imageDesc.Left * 4 ;
318     GifCopyLine( row, source, colorMap, transparent, copyWidth, replace);
319     source += imageDesc.Width;
320   }
321 }
322
323 #endif // End of code for different GIF_LIB_VERSION
324
325 /**
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.
334  */
335 void GifCopyLine(unsigned char* destination, unsigned char* source, const ColorMapObject* colorMap, int transparent, int copyWidth, bool replace )
336 {
337   for ( ; copyWidth > 0; copyWidth--, source++ )
338   {
339     if( replace || *source != transparent )
340     {
341       *(destination++) = colorMap->Colors[*source].Red;
342       *(destination++) = colorMap->Colors[*source].Green;
343       *(destination++) = colorMap->Colors[*source].Blue;
344       *(destination++) = *source == transparent ? 0x00 : 0xff;
345     }
346     else
347     {
348       destination += 4;
349     }
350   }
351 }
352
353
354 /**
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. *
364  */
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 )
368 {
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
373   {
374     DALI_LOG_ERROR( "GIF Loader: DGifSavedExtensionToGCB Error. Code: %d\n", errorCode );
375   }
376
377   // Read frame delay time, multiply 10 to change time unit to millisecods
378   delay = graphicsControlBlock.DelayTime * 10.f;
379
380   const int width = gifInfo->SWidth;
381   const int height = gifInfo->SHeight;
382
383   const SavedImage& frame = gifInfo->SavedImages[frameIndex];
384
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))
388   {
389     DALI_LOG_WARNING( "GIF Loader: potentially corrupt color map\n" );
390     return NULL;
391   }
392
393   // Allocate the buffer
394   int bufferSize = width*height*4;
395   unsigned char* buffer = new unsigned char[ bufferSize ];
396
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;
401
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 )
405   {
406     if( previousFrame && ( graphicsControlBlock.DisposalMode == DISPOSAL_UNSPECIFIED
407                         || graphicsControlBlock.DisposalMode == DISPOSE_DO_NOT
408                         || graphicsControlBlock.DisposalMode == DISPOSE_BACKGROUND) )
409     {
410       // disposal none: overlaid on the previous frame
411       if( clearFrameArea.height < height || clearFrameArea.width < width )
412       {
413         for( int i = 0; i < bufferSize; i++  )
414         {
415           buffer[i] = previousFrame[i];
416         }
417       }
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++  )
422       {
423         int idx = ( clearFrameArea.y + row)* width *4 + clearFrameArea.x * 4 + 3;
424         for( int col = 0; col < clearFrameArea.width; col++, idx+=4  )
425         {
426           buffer[idx] = 0x00;
427          }
428       }
429     }
430     else if( lastPreservedFrame && graphicsControlBlock.DisposalMode == DISPOSE_PREVIOUS )
431     {
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++  )
434       {
435         buffer[i] = lastPreservedFrame[i];
436       }
437     }
438     else if( !previousFrame && graphicsControlBlock.DisposalMode == DISPOSE_BACKGROUND)
439     {
440       // background disposal for first frame: clear to transparency
441       for( int i = 3; i < bufferSize; i+=4  )
442       {
443         buffer[i] = 0x00;
444       }
445     }
446     else
447     {
448       for( int i = 0; i < bufferSize; i+=4  )
449       {
450         buffer[i] = backgroundColor[0];
451         buffer[i+1] = backgroundColor[1];
452         buffer[i+2] = backgroundColor[2];
453         buffer[i+3] = backgroundColor[3];
454       }
455     }
456   }
457
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 );
461
462   // update the pixel buffer of the previous frame and the last preserved frame
463   if( graphicsControlBlock.DisposalMode != DISPOSE_BACKGROUND && graphicsControlBlock.DisposalMode != DISPOSE_PREVIOUS )
464   {
465     lastPreservedFrame = buffer;
466   }
467   previousFrame = buffer;
468   if( graphicsControlBlock.DisposalMode == DISPOSE_BACKGROUND )
469   {
470     clearFrameArea.x = frame.ImageDesc.Left;
471     clearFrameArea.y = frame.ImageDesc.Top;
472     clearFrameArea.width = frame.ImageDesc.Width;
473     clearFrameArea.height = frame.ImageDesc.Height;
474   }
475   else
476   {
477     clearFrameArea.width = 0;
478     clearFrameArea.height = 0;
479   }
480
481   return buffer;
482 }
483
484 } // Anonymous namespace
485
486 namespace Dali
487 {
488
489 bool LoadAnimatedGifFromFile( const std::string& url, std::vector<Dali::PixelData>& pixelData, Dali::Vector<uint32_t>& frameDelays )
490 {
491   // open GIF file
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 ) )
496   {
497     return false;
498   }
499
500   // read GIF file
501   if( DGifSlurp( gifInfo ) != GIF_OK )
502   {
503     DALI_LOG_ERROR( "GIF Loader: DGifSlurp failed. \n" );
504     return false;
505   }
506
507   // validate attributes
508   if( gifInfo->ImageCount < 1 )
509   {
510     DALI_LOG_ERROR( "GIF Loader: frame count < 1. \n" );
511     return false;
512   }
513
514   // read the image size and frame count
515   ImageDimensions size( gifInfo->SWidth, gifInfo->SHeight );
516
517   // process frames
518   unsigned char* previousFrame = NULL;
519   unsigned char* lastPreservedFrame = NULL;
520
521   // previous frame area
522   Rect<int> clearFrameArea;
523
524   // get background color
525   Dali::Vector<unsigned char> backgroundColor;
526   backgroundColor.Resize( 4 );
527   ColorMapObject* globalColorMap = gifInfo->SColorMap;
528   if( gifInfo->SColorMap )
529   {
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;
534   }
535
536   int delay;
537   unsigned char* buffer = NULL;
538   // decode the gif frame by frame
539   pixelData.clear();
540   frameDelays.Clear();
541   for( int i = 0; i < gifInfo->ImageCount; i++ )
542   {
543     buffer = DecodeOneFrame( delay, gifInfo, backgroundColor, i, lastPreservedFrame, previousFrame, clearFrameArea );
544     if( buffer )
545     {
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 );
550     }
551     else
552     {
553       DALI_LOG_ERROR( "GIF Loader: Loade frame data fail. FrameIndex: %d\n", i );
554     }
555   }
556
557   return true;
558 }
559
560 ImageDimensions GetGifImageSize( const std::string& url )
561 {
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 ) )
566   {
567     return ImageDimensions();
568   }
569   return ImageDimensions( gifInfo->SWidth, gifInfo->SHeight );
570 }
571
572 } // namespace Dali