e29231876b583a9fa40a401475aed0eabb6882ae
[platform/core/uifw/dali-adaptor.git] / dali / internal / imaging / common / loader-gif.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
18 #include <dali/internal/imaging/common/loader-gif.h>
19
20 #include <gif_lib.h>
21
22 #include <dali/integration-api/debug.h>
23 #include <dali/devel-api/adaptor-framework/pixel-buffer.h>
24 #include <memory>
25
26 #include <dali/internal/system/common/file-closer.h>
27
28 using namespace Dali::Internal::Platform;
29
30 // We need to check if giflib has the new open and close API (including error parameter).
31 #ifdef GIFLIB_MAJOR
32 #define LIBGIF_VERSION_5_1_OR_ABOVE
33 #endif
34
35 namespace Dali
36 {
37
38 namespace TizenPlatform
39 {
40
41 namespace
42 {
43
44 // simple class to enforce clean-up of GIF structures
45 struct AutoCleanupGif
46 {
47   AutoCleanupGif(GifFileType*& _gifInfo)
48   : gifInfo(_gifInfo)
49   {
50   }
51
52   ~AutoCleanupGif()
53   {
54     if(NULL != gifInfo)
55     {
56       // clean up GIF resources
57 #ifdef LIBGIF_VERSION_5_1_OR_ABOVE
58       int errorCode = 0; //D_GIF_SUCCEEDED is 0
59       DGifCloseFile( gifInfo, &errorCode );
60
61       if( errorCode )
62       {
63         DALI_LOG_ERROR( "GIF Loader: DGifCloseFile Error. Code: %d\n", errorCode );
64       }
65 #else
66       DGifCloseFile( gifInfo );
67 #endif
68     }
69   }
70
71   GifFileType*& gifInfo;
72 };
73
74 // Used in the GIF interlace algorithm to determine the starting byte and the increment required
75 // for each pass.
76 struct InterlacePair
77 {
78   unsigned int startingByte;
79   unsigned int incrementalByte;
80 };
81
82 // Used in the GIF interlace algorithm to determine the order and which location to read data from
83 // the file.
84 const InterlacePair INTERLACE_PAIR_TABLE [] = {
85   { 0, 8 }, // Starting at 0, read every 8 bytes.
86   { 4, 8 }, // Starting at 4, read every 8 bytes.
87   { 2, 4 }, // Starting at 2, read every 4 bytes.
88   { 1, 2 }, // Starting at 1, read every 2 bytes.
89 };
90 const unsigned int INTERLACE_PAIR_TABLE_SIZE( sizeof( INTERLACE_PAIR_TABLE ) / sizeof( InterlacePair ) );
91
92 /// Function used by Gif_Lib to read from the image file.
93 int ReadDataFromGif(GifFileType *gifInfo, GifByteType *data, int length)
94 {
95   FILE *fp = reinterpret_cast<FILE*>(gifInfo->UserData);
96   return InternalFile::fread( data, sizeof( GifByteType ), length, fp);
97 }
98
99 /// Loads the GIF Header.
100 bool LoadGifHeader(FILE *fp, unsigned int &width, unsigned int &height, GifFileType** gifInfo)
101 {
102   int errorCode = 0; //D_GIF_SUCCEEDED is 0
103
104 #ifdef LIBGIF_VERSION_5_1_OR_ABOVE
105   *gifInfo = DGifOpen( reinterpret_cast<void*>(fp), ReadDataFromGif, &errorCode );
106 #else
107   *gifInfo = DGifOpen( reinterpret_cast<void*>(fp), ReadDataFromGif );
108 #endif
109
110   if ( !(*gifInfo) || errorCode )
111   {
112     DALI_LOG_ERROR( "GIF Loader: DGifOpen Error. Code: %d\n", errorCode );
113     return false;
114   }
115
116   width  = (*gifInfo)->SWidth;
117   height = (*gifInfo)->SHeight;
118
119   // No proper size in GIF.
120   if ( width <= 0 || height <= 0 )
121   {
122     return false;
123   }
124
125   return true;
126 }
127
128 /// Decode the GIF image.
129 bool DecodeImage( GifFileType* gifInfo, unsigned char* decodedData, const unsigned int width, const unsigned int height, const unsigned int bytesPerRow )
130 {
131   if ( gifInfo->Image.Interlace )
132   {
133     // If the image is interlaced, then use the GIF interlace algorithm to read the file appropriately.
134
135     const InterlacePair* interlacePairPtr( INTERLACE_PAIR_TABLE );
136     for ( unsigned int interlacePair = 0; interlacePair < INTERLACE_PAIR_TABLE_SIZE; ++interlacePair, ++interlacePairPtr )
137     {
138       for( unsigned int currentByte = interlacePairPtr->startingByte; currentByte < height; currentByte += interlacePairPtr->incrementalByte )
139       {
140         unsigned char* row = decodedData + currentByte * bytesPerRow;
141         if ( DGifGetLine( gifInfo, row, width ) == GIF_ERROR )
142         {
143           DALI_LOG_ERROR( "GIF Loader: Error reading Interlaced GIF\n" );
144           return false;
145         }
146       }
147     }
148   }
149   else
150   {
151     // Non-interlace does not require any erratic reading / jumping.
152     unsigned char* decodedDataPtr( decodedData );
153
154     for ( unsigned int row = 0; row < height; ++row )
155     {
156       if ( DGifGetLine( gifInfo, decodedDataPtr, width ) == GIF_ERROR)
157       {
158         DALI_LOG_ERROR( "GIF Loader: Error reading non-interlaced GIF\n" );
159         return false;
160       }
161       decodedDataPtr += bytesPerRow;
162     }
163   }
164   return true;
165 }
166
167 // Retrieves the colors used in the GIF image.
168 GifColorType* GetImageColors( SavedImage* image, GifFileType* gifInfo )
169 {
170   GifColorType* color( NULL );
171   if ( image->ImageDesc.ColorMap )
172   {
173     color = image->ImageDesc.ColorMap->Colors;
174   }
175   else
176   {
177     // if there is no color map for this image use the default one
178     color = gifInfo->SColorMap->Colors;
179   }
180   return color;
181 }
182
183 /// Called when we want to handle IMAGE_DESC_RECORD_TYPE
184 bool HandleImageDescriptionRecordType( Dali::Devel::PixelBuffer& bitmap, GifFileType* gifInfo, unsigned int width, unsigned int height, bool& finished )
185 {
186   if ( DGifGetImageDesc( gifInfo ) == GIF_ERROR )
187   {
188     DALI_LOG_ERROR( "GIF Loader: Error getting Image Description\n" );
189     return false;
190   }
191
192   // Ensure there is at least 1 image in the GIF.
193   if ( gifInfo->ImageCount < 1 )
194   {
195     DALI_LOG_ERROR( "GIF Loader: No Images\n" );
196     return false;
197   }
198
199   Pixel::Format pixelFormat( Pixel::RGB888 );
200
201   SavedImage* image( &gifInfo->SavedImages[ gifInfo->ImageCount - 1 ] );
202   const GifImageDesc& desc( image->ImageDesc );
203
204   auto decodedData = new unsigned char[ width * height * sizeof( GifPixelType ) ];
205
206   std::unique_ptr<unsigned char[]> ptr{ decodedData };
207
208   const unsigned int bytesPerRow( width * sizeof( GifPixelType ) );
209   const unsigned int actualWidth( desc.Width );
210   const unsigned int actualHeight( desc.Height );
211
212   // Create a buffer to store the decoded data.
213   bitmap = Dali::Devel::PixelBuffer::New( actualWidth, actualHeight, pixelFormat );
214
215   // Decode the GIF Image
216   if ( !DecodeImage( gifInfo, decodedData, actualWidth, actualHeight, bytesPerRow ) )
217   {
218     return false;
219   }
220
221   // Get the colormap for the GIF
222   GifColorType* color( GetImageColors( image, gifInfo ) );
223
224   // If it's an animated GIF, we still only read the first image
225
226   // Create and populate pixel buffer.
227   auto pixels = bitmap.GetBuffer();
228   for (unsigned int row = 0; row < actualHeight; ++row)
229   {
230     for (unsigned int column = 0; column < actualWidth; ++column)
231     {
232       unsigned char index = decodedData[row * width + column];
233
234       pixels[0] = color[index].Red;
235       pixels[1] = color[index].Green;
236       pixels[2] = color[index].Blue;
237       pixels += 3;
238     }
239   }
240   finished = true;
241   return true;
242 }
243
244 /// Called when we want to handle EXTENSION_RECORD_TYPE
245 bool HandleExtensionRecordType( GifFileType* gifInfo )
246 {
247   SavedImage image;
248   GifByteType *extensionByte( NULL );
249
250 #ifdef LIBGIF_VERSION_5_1_OR_ABOVE
251   ExtensionBlock extensionBlocks;
252   image.ExtensionBlocks          = &extensionBlocks;
253   image.ExtensionBlockCount      = 1;
254   int *extensionBlockTypePointer = &image.ExtensionBlocks->Function;
255 #else
256   image.ExtensionBlocks     = NULL;
257   image.ExtensionBlockCount = 0;
258   int *extensionBlockTypePointer = &image.Function;
259 #endif
260
261   // Not really interested in the extensions so just skip them unless there is an error.
262   for ( int extRetCode = DGifGetExtension( gifInfo, extensionBlockTypePointer, &extensionByte );
263         extensionByte != NULL;
264         extRetCode = DGifGetExtensionNext( gifInfo, &extensionByte ) )
265   {
266     if ( extRetCode == GIF_ERROR )
267     {
268       DALI_LOG_ERROR( "GIF Loader: Error reading GIF Extension record.\n" );
269       return false;
270     }
271   }
272
273   return true;
274 }
275
276 } // unnamed namespace
277
278 bool LoadGifHeader( const Dali::ImageLoader::Input& input, unsigned int& width, unsigned int& height )
279 {
280   GifFileType* gifInfo = NULL;
281   AutoCleanupGif autoCleanupGif(gifInfo);
282   FILE* const fp = input.file;
283
284   return LoadGifHeader(fp, width, height, &gifInfo);
285 }
286
287 bool LoadBitmapFromGif( const Dali::ImageLoader::Input& input, Dali::Devel::PixelBuffer& bitmap )
288 {
289   FILE* const fp = input.file;
290   // Load the GIF Header file.
291
292   GifFileType* gifInfo( NULL );
293   unsigned int width( 0 );
294   unsigned int height( 0 );
295   if ( !LoadGifHeader( fp, width, height, &gifInfo ) )
296   {
297     return false;
298   }
299   AutoCleanupGif autoGif( gifInfo );
300
301   // Check each record in the GIF file.
302
303   bool finished( false );
304   GifRecordType recordType( UNDEFINED_RECORD_TYPE );
305   for ( int returnCode = DGifGetRecordType( gifInfo, &recordType );
306         !finished && recordType != TERMINATE_RECORD_TYPE;
307         returnCode = DGifGetRecordType( gifInfo, &recordType ) )
308   {
309     if ( returnCode == GIF_ERROR )
310     {
311       DALI_LOG_ERROR( "GIF Loader: Error getting Record Type\n" );
312       return false;
313     }
314
315     if( IMAGE_DESC_RECORD_TYPE == recordType )
316     {
317       if ( !HandleImageDescriptionRecordType( bitmap, gifInfo, width, height, finished ) )
318       {
319         return false;
320       }
321     }
322     else if ( EXTENSION_RECORD_TYPE == recordType )
323     {
324       if ( !HandleExtensionRecordType( gifInfo ))
325       {
326         return false;
327       }
328     }
329   }
330
331   return true;
332 }
333
334 } // namespace TizenPlatform
335
336 } // namespace Dali