xref: /aosp_15_r20/external/tensorflow/tensorflow/core/lib/gif/gif_io.cc (revision b6fb3261f9314811a0f4371741dbb8839866f948)
1 /* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
2 
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6 
7     http://www.apache.org/licenses/LICENSE-2.0
8 
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15 
16 // Functions to read images in GIF format.
17 
18 #include "tensorflow/core/lib/gif/gif_io.h"
19 
20 #include <algorithm>
21 
22 #include "absl/strings/str_cat.h"
23 #include "tensorflow/core/lib/gtl/cleanup.h"
24 #include "tensorflow/core/platform/gif.h"
25 #include "tensorflow/core/platform/logging.h"
26 #include "tensorflow/core/platform/mem.h"
27 #include "tensorflow/core/platform/types.h"
28 
29 namespace tensorflow {
30 namespace gif {
31 
32 struct InputBufferInfo {
33   const uint8_t* buf;
34   int bytes_left;
35 };
36 
input_callback(GifFileType * gif_file,GifByteType * buf,int size)37 int input_callback(GifFileType* gif_file, GifByteType* buf, int size) {
38   InputBufferInfo* const info =
39       reinterpret_cast<InputBufferInfo*>(gif_file->UserData);
40   if (info != nullptr) {
41     if (size > info->bytes_left) size = info->bytes_left;
42     memcpy(buf, info->buf, size);
43     info->buf += size;
44     info->bytes_left -= size;
45     return size;
46   }
47   return 0;
48 }
49 
GifErrorStringNonNull(int error_code)50 static const char* GifErrorStringNonNull(int error_code) {
51   const char* error_string = GifErrorString(error_code);
52   if (error_string == nullptr) {
53     return "Unknown error";
54   }
55   return error_string;
56 }
57 
Decode(const void * srcdata,int datasize,const std::function<uint8 * (int,int,int,int)> & allocate_output,string * error_string,bool expand_animations)58 uint8* Decode(const void* srcdata, int datasize,
59               const std::function<uint8*(int, int, int, int)>& allocate_output,
60               string* error_string, bool expand_animations) {
61   int error_code = D_GIF_SUCCEEDED;
62   InputBufferInfo info = {reinterpret_cast<const uint8*>(srcdata), datasize};
63   GifFileType* gif_file =
64       DGifOpen(static_cast<void*>(&info), &input_callback, &error_code);
65   const auto cleanup = gtl::MakeCleanup([gif_file]() {
66     int error_code = D_GIF_SUCCEEDED;
67     if (gif_file && DGifCloseFile(gif_file, &error_code) != GIF_OK) {
68       LOG(WARNING) << "Fail to close gif file, reason: "
69                    << GifErrorStringNonNull(error_code);
70     }
71   });
72   if (error_code != D_GIF_SUCCEEDED) {
73     *error_string = absl::StrCat("failed to open gif file: ",
74                                  GifErrorStringNonNull(error_code));
75     return nullptr;
76   }
77   if (DGifSlurp(gif_file) != GIF_OK) {
78     *error_string = absl::StrCat("failed to slurp gif file: ",
79                                  GifErrorStringNonNull(gif_file->Error));
80     return nullptr;
81   }
82   if (gif_file->ImageCount <= 0) {
83     *error_string = "gif file does not contain any image";
84     return nullptr;
85   }
86 
87   int target_num_frames = gif_file->ImageCount;
88 
89   // Don't request more memory than needed for each frame, preventing OOM
90   int max_frame_width = 0;
91   int max_frame_height = 0;
92   for (int k = 0; k < target_num_frames; k++) {
93     SavedImage* si = &gif_file->SavedImages[k];
94     if (max_frame_height < si->ImageDesc.Height)
95       max_frame_height = si->ImageDesc.Height;
96     if (max_frame_width < si->ImageDesc.Width)
97       max_frame_width = si->ImageDesc.Width;
98   }
99 
100   const int width = max_frame_width;
101   const int height = max_frame_height;
102   const int channel = 3;
103   if (!expand_animations) target_num_frames = 1;
104 
105   uint8* const dstdata =
106       allocate_output(target_num_frames, width, height, channel);
107   if (!dstdata) return nullptr;
108   for (int k = 0; k < target_num_frames; k++) {
109     uint8* this_dst = dstdata + k * width * channel * height;
110 
111     SavedImage* this_image = &gif_file->SavedImages[k];
112     GifImageDesc* img_desc = &this_image->ImageDesc;
113 
114     // The Graphics Control Block tells us which index in the color map
115     // correspond to "transparent color", i.e. no need to update the pixel
116     // on the canvas. The "transparent color index" is specific to each
117     // sub-frame.
118     GraphicsControlBlock gcb;
119     DGifSavedExtensionToGCB(gif_file, k, &gcb);
120 
121     int imgLeft = img_desc->Left;
122     int imgTop = img_desc->Top;
123     int imgRight = img_desc->Left + img_desc->Width;
124     int imgBottom = img_desc->Top + img_desc->Height;
125 
126     if (k > 0) {
127       uint8* last_dst = dstdata + (k - 1) * width * channel * height;
128       for (int i = 0; i < height; ++i) {
129         uint8* p_dst = this_dst + i * width * channel;
130         uint8* l_dst = last_dst + i * width * channel;
131         for (int j = 0; j < width; ++j) {
132           p_dst[j * channel + 0] = l_dst[j * channel + 0];
133           p_dst[j * channel + 1] = l_dst[j * channel + 1];
134           p_dst[j * channel + 2] = l_dst[j * channel + 2];
135         }
136       }
137     }
138 
139     if (img_desc->Left != 0 || img_desc->Top != 0 || img_desc->Width != width ||
140         img_desc->Height != height) {
141       // If the first frame does not fill the entire canvas then fill the
142       // unoccupied canvas with zeros (black).
143       if (k == 0) {
144         for (int i = 0; i < height; ++i) {
145           uint8* p_dst = this_dst + i * width * channel;
146           for (int j = 0; j < width; ++j) {
147             p_dst[j * channel + 0] = 0;
148             p_dst[j * channel + 1] = 0;
149             p_dst[j * channel + 2] = 0;
150           }
151         }
152       }
153 
154       imgLeft = std::max(imgLeft, 0);
155       imgTop = std::max(imgTop, 0);
156       imgRight = std::min(imgRight, width);
157       imgBottom = std::min(imgBottom, height);
158     }
159 
160     ColorMapObject* color_map = this_image->ImageDesc.ColorMap
161                                     ? this_image->ImageDesc.ColorMap
162                                     : gif_file->SColorMap;
163     if (color_map == nullptr) {
164       *error_string = absl::StrCat("missing color map for frame ", k);
165       return nullptr;
166     }
167 
168     for (int i = imgTop; i < imgBottom; ++i) {
169       uint8* p_dst = this_dst + i * width * channel;
170       for (int j = imgLeft; j < imgRight; ++j) {
171         GifByteType color_index =
172             this_image->RasterBits[(i - img_desc->Top) * (img_desc->Width) +
173                                    (j - img_desc->Left)];
174 
175         if (color_index >= color_map->ColorCount) {
176           *error_string = absl::StrCat("found color index ", color_index,
177                                        " outside of color map range ",
178                                        color_map->ColorCount);
179           return nullptr;
180         }
181 
182         if (color_index == gcb.TransparentColor) {
183           // Use the pixel from the previous frame. In other words, no need to
184           // update our canvas for this pixel.
185           continue;
186         }
187 
188         const GifColorType& gif_color = color_map->Colors[color_index];
189         p_dst[j * channel + 0] = gif_color.Red;
190         p_dst[j * channel + 1] = gif_color.Green;
191         p_dst[j * channel + 2] = gif_color.Blue;
192       }
193     }
194   }
195 
196   return dstdata;
197 }
198 
199 }  // namespace gif
200 }  // namespace tensorflow
201