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