Merge "[Tizen] Destructors cannot throw exceptions." into tizen
[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_ERROR;
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       && gifInfo->ImageCount > 1 ) // for static gif, graphics control block may not been specified
368   {
369     DALI_LOG_ERROR( "GIF Loader: DGifSavedExtensionToGCB Error. Code: %d\n", errorCode );
370   }
371
372   // Read frame delay time, multiply 10 to change time unit to millisecods
373   delay = graphicsControlBlock.DelayTime * 10.f;
374
375   const int width = gifInfo->SWidth;
376   const int height = gifInfo->SHeight;
377
378   const SavedImage& frame = gifInfo->SavedImages[frameIndex];
379
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))
383   {
384     DALI_LOG_WARNING( "GIF Loader: potentially corrupt color map\n" );
385     return NULL;
386   }
387
388   // Allocate the buffer
389   int bufferSize = width*height*4;
390   unsigned char* buffer = new unsigned char[ bufferSize ];
391
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;
396
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 )
400   {
401     if( previousFrame && ( graphicsControlBlock.DisposalMode == DISPOSAL_UNSPECIFIED
402                         || graphicsControlBlock.DisposalMode == DISPOSE_DO_NOT
403                         || graphicsControlBlock.DisposalMode == DISPOSE_BACKGROUND) )
404     {
405       // disposal none: overlaid on the previous frame
406       if( clearFrameArea.height < height || clearFrameArea.width < width )
407       {
408         for( int i = 0; i < bufferSize; i++  )
409         {
410           buffer[i] = previousFrame[i];
411         }
412       }
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++  )
417       {
418         int idx = ( clearFrameArea.y + row)* width *4 + clearFrameArea.x * 4 + 3;
419         for( int col = 0; col < clearFrameArea.width; col++, idx+=4  )
420         {
421           buffer[idx] = 0x00;
422          }
423       }
424     }
425     else if( lastPreservedFrame && graphicsControlBlock.DisposalMode == DISPOSE_PREVIOUS )
426     {
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++  )
429       {
430         buffer[i] = lastPreservedFrame[i];
431       }
432     }
433     else if( !previousFrame && graphicsControlBlock.DisposalMode == DISPOSE_BACKGROUND)
434     {
435       // background disposal for first frame: clear to transparency
436       for( int i = 3; i < bufferSize; i+=4  )
437       {
438         buffer[i] = 0x00;
439       }
440     }
441     else
442     {
443       for( int i = 0; i < bufferSize; i+=4  )
444       {
445         buffer[i] = backgroundColor[0];
446         buffer[i+1] = backgroundColor[1];
447         buffer[i+2] = backgroundColor[2];
448         buffer[i+3] = backgroundColor[3];
449       }
450     }
451   }
452
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 );
456
457   // update the pixel buffer of the previous frame and the last preserved frame
458   if( graphicsControlBlock.DisposalMode != DISPOSE_BACKGROUND && graphicsControlBlock.DisposalMode != DISPOSE_PREVIOUS )
459   {
460     lastPreservedFrame = buffer;
461   }
462   previousFrame = buffer;
463   if( graphicsControlBlock.DisposalMode == DISPOSE_BACKGROUND )
464   {
465     clearFrameArea.x = frame.ImageDesc.Left;
466     clearFrameArea.y = frame.ImageDesc.Top;
467     clearFrameArea.width = frame.ImageDesc.Width;
468     clearFrameArea.height = frame.ImageDesc.Height;
469   }
470   else
471   {
472     clearFrameArea.width = 0;
473     clearFrameArea.height = 0;
474   }
475
476   return buffer;
477 }
478
479 } // Anonymous namespace
480
481 namespace Dali
482 {
483
484 bool LoadAnimatedGifFromFile( const std::string& url, std::vector<Dali::PixelData>& pixelData, Dali::Vector<uint32_t>& frameDelays )
485 {
486   // open GIF file
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 ) )
491   {
492     return false;
493   }
494
495   // read GIF file
496   if( DGifSlurp( gifInfo ) != GIF_OK )
497   {
498     DALI_LOG_ERROR( "GIF Loader: DGifSlurp failed. \n" );
499     return false;
500   }
501
502   // validate attributes
503   if( gifInfo->ImageCount < 1 )
504   {
505     DALI_LOG_ERROR( "GIF Loader: frame count < 1. \n" );
506     return false;
507   }
508
509   // read the image size and frame count
510   ImageDimensions size( gifInfo->SWidth, gifInfo->SHeight );
511
512   // process frames
513   unsigned char* previousFrame = NULL;
514   unsigned char* lastPreservedFrame = NULL;
515
516   // previous frame area
517   Rect<int> clearFrameArea;
518
519   // get background color
520   Dali::Vector<unsigned char> backgroundColor;
521   backgroundColor.Resize( 4 );
522   ColorMapObject* globalColorMap = gifInfo->SColorMap;
523   if( gifInfo->SColorMap )
524   {
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;
529   }
530
531   int delay;
532   unsigned char* buffer = NULL;
533   // decode the gif frame by frame
534   pixelData.clear();
535   frameDelays.Clear();
536   for( int i = 0; i < gifInfo->ImageCount; i++ )
537   {
538     buffer = DecodeOneFrame( delay, gifInfo, backgroundColor, i, lastPreservedFrame, previousFrame, clearFrameArea );
539     if( buffer )
540     {
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 );
545     }
546     else
547     {
548       DALI_LOG_ERROR( "GIF Loader: Loade frame data fail. FrameIndex: %d\n", i );
549     }
550   }
551
552   return true;
553 }
554
555 ImageDimensions GetGifImageSize( const std::string& url )
556 {
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 ) )
561   {
562     return ImageDimensions();
563   }
564   return ImageDimensions( gifInfo->SWidth, gifInfo->SHeight );
565 }
566
567 } // namespace Dali