Merge "GIF loading" into devel/master
[platform/core/uifw/dali-adaptor.git] / adaptors / devel-api / adaptor-framework / gif-loading.cpp
1 /*
2  * Copyright (c) 2016 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     return GIF_ERROR;
92   }
93
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;
99   else
100     GCB->TransparentColor = NO_TRANSPARENT_COLOR;
101
102   return GIF_OK;
103 }
104
105 /******************************************************************************
106  Extract the Graphics Control Block for a saved image, if it exists.
107 ******************************************************************************/
108 int DGifSavedExtensionToGCB(GifFileType *GifFile, int ImageIndex, GraphicsControlBlock *GCB)
109 {
110   int i;
111
112   if (ImageIndex < 0 || ImageIndex > GifFile->ImageCount - 1)
113     return GIF_ERROR;
114
115   GCB->DisposalMode = DISPOSAL_UNSPECIFIED;
116   GCB->UserInputFlag = false;
117   GCB->DelayTime = 0;
118   GCB->TransparentColor = NO_TRANSPARENT_COLOR;
119
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);
124   }
125
126   return GIF_OK;
127 }
128
129 /******************************************************************************
130  End of code copy from GIFLIB version 5.
131 ******************************************************************************/
132
133
134 // simple class to enforce clean-up of GIF structures
135 struct GifAutoCleanup
136 {
137   GifAutoCleanup(GifFileType*& _gifInfo)
138   : gifInfo(_gifInfo)
139   {
140   }
141
142   ~GifAutoCleanup()
143   {
144     if(NULL != gifInfo)
145     {
146       // clean up GIF resources
147       DGifCloseFile( gifInfo );
148     }
149   }
150
151   GifFileType*& gifInfo;
152 };
153
154 /**
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.
159  */
160 bool GifOpen( const char* url, GifFileType*& gifInfo )
161 {
162   gifInfo = DGifOpenFileName( url );
163   if( gifInfo == NULL )
164   {
165     DALI_LOG_ERROR( "GIF Loader: DGifOpen failed. \n" );
166     return false;
167   }
168   return true;
169 }
170
171 /**
172  * With GIFLIB version 4.1.6, the interlacing needs to be handled manually
173  */
174 // Used in the GIF interlace algorithm to determine the starting byte and the increment required
175 // for each pass.
176 struct InterlacePair
177 {
178   unsigned int startingByte;
179   unsigned int incrementalByte;
180 };
181
182 // Used in the GIF interlace algorithm to determine the order and which location to read data from
183 // the file.
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.
189 };
190 const int INTERLACE_PAIR_TABLE_SIZE( sizeof( INTERLACE_PAIR_TABLE ) / sizeof( InterlacePair ) );
191
192 /**
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.
203  */
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 )
207 {
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;
211
212   unsigned char* row;
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
215   {
216     const InterlacePair* interlacePairPtr( INTERLACE_PAIR_TABLE );
217     for ( int interlacePair = 0; interlacePair < INTERLACE_PAIR_TABLE_SIZE; ++interlacePair, ++interlacePairPtr )
218     {
219       for( int currentRow = interlacePairPtr->startingByte; currentRow < copyHeight; currentRow +=interlacePairPtr->incrementalByte )
220       {
221         row = destination + (imageDesc.Top + currentRow) * width * 4 + imageDesc.Left * 4 ;
222         GifCopyLine( row, source, colorMap, transparent, copyWidth, replace);
223         source += imageDesc.Width;
224       }
225     }
226   }
227   else
228   {
229     for( int currentRow = 0; currentRow < copyHeight; currentRow++ )
230     {
231       row = destination + (imageDesc.Top + currentRow) * width * 4 + imageDesc.Left * 4 ;
232       GifCopyLine( row, source, colorMap, transparent, copyWidth, replace);
233       source += imageDesc.Width;
234     }
235   }
236 }
237
238 #else
239
240 /*************************************************
241  * GIFLIB major version 5
242  *************************************************/
243
244 // simple class to enforce clean-up of GIF structures
245 struct GifAutoCleanup
246 {
247   GifAutoCleanup(GifFileType*& _gifInfo)
248   : gifInfo(_gifInfo)
249   {
250   }
251
252   ~GifAutoCleanup()
253   {
254     if(NULL != gifInfo)
255     {
256       // clean up GIF resources
257       int errorCode = 0; //D_GIF_SUCCEEDED is 0
258       DGifCloseFile( gifInfo, &errorCode );
259
260       if( errorCode )
261       {
262         DALI_LOG_ERROR( "GIF Loader: DGifCloseFile Error. Code: %d\n", errorCode );
263       }
264     }
265   }
266
267   GifFileType*& gifInfo;
268 };
269
270 /**
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.
275  */
276 bool GifOpen( const char* url, GifFileType*& gifInfo )
277 {
278   int errorCode;
279   gifInfo = DGifOpenFileName(url, &errorCode);
280   if (gifInfo == NULL)
281   {
282     DALI_LOG_ERROR( "GIF Loader: DGifOpenFileName Error. Code: %d\n", errorCode );
283     return false;
284   }
285   return true;
286 }
287
288 /**
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.
299  */
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 )
303 {
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;
307
308   unsigned char* row;
309   // copy line by line from the color-index formated source to the RGBA formated destination.
310   for( int currentRow = 0; currentRow < copyHeight; currentRow++ )
311   {
312     row = destination + (imageDesc.Top + currentRow) * width * 4 + imageDesc.Left * 4 ;
313     GifCopyLine( row, source, colorMap, transparent, copyWidth, replace);
314     source += imageDesc.Width;
315   }
316 }
317
318 #endif // End of code for different GIF_LIB_VERSION
319
320 /**
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.
329  */
330 void GifCopyLine(unsigned char* destination, unsigned char* source, const ColorMapObject* colorMap, int transparent, int copyWidth, bool replace )
331 {
332   for ( ; copyWidth > 0; copyWidth--, source++ )
333   {
334     if( replace || *source != transparent )
335     {
336       *(destination++) = colorMap->Colors[*source].Red;
337       *(destination++) = colorMap->Colors[*source].Green;
338       *(destination++) = colorMap->Colors[*source].Blue;
339       *(destination++) = *source == transparent ? 0x00 : 0xff;
340     }
341     else
342     {
343       destination += 4;
344     }
345   }
346 }
347
348
349 /**
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. *
359  */
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 )
363 {
364   // Fetch the graphics control block
365   GraphicsControlBlock graphicsControlBlock;
366   if( int errorCode = DGifSavedExtensionToGCB( gifInfo, frameIndex, &graphicsControlBlock ) != GIF_OK )
367   {
368     DALI_LOG_ERROR( "GIF Loader: DGifSavedExtensionToGCB Error. Code: %d\n", errorCode );
369   }
370
371   // Read frame delay time, multiply 10 to change time unit to millisecods
372   delay = graphicsControlBlock.DelayTime * 10.f;
373
374   const int width = gifInfo->SWidth;
375   const int height = gifInfo->SHeight;
376
377   const SavedImage& frame = gifInfo->SavedImages[frameIndex];
378
379   // get the color map. If there is a local one, use the local color map, otherwise use the global color map
380   ColorMapObject* colorMap = frame.ImageDesc.ColorMap ? frame.ImageDesc.ColorMap : gifInfo->SColorMap;
381   if (colorMap == NULL || colorMap->ColorCount != (1 << colorMap->BitsPerPixel))
382   {
383     DALI_LOG_WARNING( "GIF Loader: potentially corrupt color map\n" );
384     return NULL;
385   }
386
387   // Allocate the buffer
388   int bufferSize = width*height*4;
389   unsigned char* buffer = new unsigned char[ bufferSize ];
390
391   // check whether buffer initializetion is needed
392   bool completelyCovered = graphicsControlBlock.TransparentColor == NO_TRANSPARENT_COLOR
393                            && frame.ImageDesc.Left == 0 && frame.ImageDesc.Top == 0
394                            && frame.ImageDesc.Width == width && frame.ImageDesc.Height == height;
395
396    // if not completely covered, needs to initialise the pixels
397   // depends on the disposal method, it would be initialised to background color, previous frame or the last preserved frame
398   if( !completelyCovered )
399   {
400     if( previousFrame && ( graphicsControlBlock.DisposalMode == DISPOSAL_UNSPECIFIED
401                         || graphicsControlBlock.DisposalMode == DISPOSE_DO_NOT
402                         || graphicsControlBlock.DisposalMode == DISPOSE_BACKGROUND) )
403     {
404       // disposal none: overlaid on the previous frame
405       if( clearFrameArea.height < height || clearFrameArea.width < width )
406       {
407         for( int i = 0; i < bufferSize; i++  )
408         {
409           buffer[i] = previousFrame[i];
410         }
411       }
412       // background disposal: When the time delay is finished for a particular frame, the area that was overlaid by that frame is cleared.
413       // 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,
414       // to be overlaid by that frames image.
415       for( int row = 0; row < clearFrameArea.height; row++  )
416       {
417         int idx = ( clearFrameArea.y + row)* width *4 + clearFrameArea.x * 4 + 3;
418         for( int col = 0; col < clearFrameArea.width; col++, idx+=4  )
419         {
420           buffer[idx] = 0x00;
421          }
422       }
423     }
424     else if( lastPreservedFrame && graphicsControlBlock.DisposalMode == DISPOSE_PREVIOUS )
425     {
426       // previous disposal: When the current image is finished, return the canvas to what it looked like before the image was overlaid.
427       for( int i = 0; i < bufferSize; i++  )
428       {
429         buffer[i] = lastPreservedFrame[i];
430       }
431     }
432     else if( !previousFrame && graphicsControlBlock.DisposalMode == DISPOSE_BACKGROUND)
433     {
434       // background disposal for first frame: clear to transparency
435       for( int i = 3; i < bufferSize; i+=4  )
436       {
437         buffer[i] = 0x00;
438       }
439     }
440     else
441     {
442       for( int i = 0; i < bufferSize; i+=4  )
443       {
444         buffer[i] = backgroundColor[0];
445         buffer[i+1] = backgroundColor[1];
446         buffer[i+2] = backgroundColor[2];
447         buffer[i+3] = backgroundColor[3];
448       }
449     }
450   }
451
452   unsigned char* source = frame.RasterBits;
453   bool replace = completelyCovered || (frameIndex == 0 && graphicsControlBlock.DisposalMode != DISPOSE_BACKGROUND);
454   GifCopyFrame( buffer, source, width, height, frame.ImageDesc, colorMap, graphicsControlBlock.TransparentColor, replace );
455
456   // update the pixel buffer of the previous frame and the last preserved frame
457   if( graphicsControlBlock.DisposalMode != DISPOSE_BACKGROUND && graphicsControlBlock.DisposalMode != DISPOSE_PREVIOUS )
458   {
459     lastPreservedFrame = buffer;
460   }
461   previousFrame = buffer;
462   if( graphicsControlBlock.DisposalMode == DISPOSE_BACKGROUND )
463   {
464     clearFrameArea.x = frame.ImageDesc.Left;
465     clearFrameArea.y = frame.ImageDesc.Top;
466     clearFrameArea.width = frame.ImageDesc.Width;
467     clearFrameArea.height = frame.ImageDesc.Height;
468   }
469   else
470   {
471     clearFrameArea.width = 0;
472     clearFrameArea.height = 0;
473   }
474
475   return buffer;
476 }
477
478 } // Anonymous namespace
479
480 namespace Dali
481 {
482
483 bool LoadAnimatedGifFromFile( const std::string& url, std::vector<Dali::PixelData>& pixelData, Dali::Vector<uint32_t>& frameDelays )
484 {
485   // open GIF file
486   GifFileType* gifInfo = NULL;
487   GifAutoCleanup autoGif( gifInfo );
488   // enforce clean-up of the GIF structure when finishing this method.
489   if( !GifOpen( url.c_str(), gifInfo ) )
490   {
491     return false;
492   }
493
494   // read GIF file
495   if( DGifSlurp( gifInfo ) != GIF_OK )
496   {
497     DALI_LOG_ERROR( "GIF Loader: DGifSlurp failed. \n" );
498     return false;
499   }
500
501   // validate attributes
502   if( gifInfo->ImageCount < 1 )
503   {
504     DALI_LOG_ERROR( "GIF Loader: frame count < 1. \n" );
505     return false;
506   }
507
508   // read the image size and frame count
509   ImageDimensions size( gifInfo->SWidth, gifInfo->SHeight );
510
511   // process frames
512   unsigned char* previousFrame = NULL;
513   unsigned char* lastPreservedFrame = NULL;
514
515   // previous frame area
516   Rect<int> clearFrameArea;
517
518   // get background color
519   Dali::Vector<unsigned char> backgroundColor;
520   backgroundColor.Resize( 4 );
521   ColorMapObject* globalColorMap = gifInfo->SColorMap;
522   if( gifInfo->SColorMap )
523   {
524     backgroundColor[0] = globalColorMap->Colors[ gifInfo->SBackGroundColor ].Red;
525     backgroundColor[1] = globalColorMap->Colors[ gifInfo->SBackGroundColor ].Green;
526     backgroundColor[2] = globalColorMap->Colors[ gifInfo->SBackGroundColor ].Blue;
527     backgroundColor[3] = 0xff;
528   }
529
530   int delay;
531   unsigned char* buffer = NULL;
532   // decode the gif frame by frame
533   pixelData.clear();
534   frameDelays.Clear();
535   for( int i = 0; i < gifInfo->ImageCount; i++ )
536   {
537     buffer = DecodeOneFrame( delay, gifInfo, backgroundColor, i, lastPreservedFrame, previousFrame, clearFrameArea );
538     if( buffer )
539     {
540       pixelData.push_back( Dali::PixelData::New( buffer, size.GetWidth()*size.GetHeight()*4,
541                                                      size.GetWidth(), size.GetHeight(),
542                                                      Dali::Pixel::RGBA8888, Dali::PixelData::DELETE_ARRAY ) );
543       frameDelays.PushBack( delay );
544     }
545     else
546     {
547       DALI_LOG_ERROR( "GIF Loader: Loade frame data fail. FrameIndex: %d\n", i );
548     }
549   }
550
551   return true;
552 }
553
554 ImageDimensions GetGifImageSize( const std::string& url )
555 {
556   GifFileType* gifInfo = NULL;
557  // enforce clean-up of the GIF structure when finishing this method.
558   GifAutoCleanup autoGif( gifInfo );
559   if( !GifOpen( url.c_str(), gifInfo ) )
560   {
561     return ImageDimensions();
562   }
563   return ImageDimensions( gifInfo->SWidth, gifInfo->SHeight );
564 }
565
566 } // namespace Dali