1 // Copyright (c) the JPEG XL Project Authors. All rights reserved.
2 //
3 // Use of this source code is governed by a BSD-style
4 // license that can be found in the LICENSE file.
5 
6 #include "lib/extras/codec_gif.h"
7 
8 #include <gif_lib.h>
9 #include <string.h>
10 
11 #include <algorithm>
12 #include <string>
13 #include <utility>
14 #include <vector>
15 
16 #include "lib/jxl/base/compiler_specific.h"
17 #include "lib/jxl/color_encoding_internal.h"
18 #include "lib/jxl/frame_header.h"
19 #include "lib/jxl/headers.h"
20 #include "lib/jxl/image.h"
21 #include "lib/jxl/image_bundle.h"
22 #include "lib/jxl/image_ops.h"
23 #include "lib/jxl/luminance.h"
24 
25 #ifdef MEMORY_SANITIZER
26 #include "sanitizer/msan_interface.h"
27 #endif
28 
29 namespace jxl {
30 
31 namespace {
32 
33 struct ReadState {
34   Span<const uint8_t> bytes;
35 };
36 
37 struct DGifCloser {
operator ()jxl::__anon3b8b515d0111::DGifCloser38   void operator()(GifFileType* const ptr) const { DGifCloseFile(ptr, nullptr); }
39 };
40 using GifUniquePtr = std::unique_ptr<GifFileType, DGifCloser>;
41 
42 // Gif does not support partial transparency, so this considers anything non-0
43 // as opaque.
AllOpaque(const ImageF & alpha)44 bool AllOpaque(const ImageF& alpha) {
45   for (size_t y = 0; y < alpha.ysize(); ++y) {
46     const float* const JXL_RESTRICT row = alpha.ConstRow(y);
47     for (size_t x = 0; x < alpha.xsize(); ++x) {
48       if (row[x] == 0.f) {
49         return false;
50       }
51     }
52   }
53   return true;
54 }
55 
56 }  // namespace
57 
DecodeImageGIF(Span<const uint8_t> bytes,ThreadPool * pool,CodecInOut * io)58 Status DecodeImageGIF(Span<const uint8_t> bytes, ThreadPool* pool,
59                       CodecInOut* io) {
60   int error = GIF_OK;
61   ReadState state = {bytes};
62   const auto ReadFromSpan = [](GifFileType* const gif, GifByteType* const bytes,
63                                int n) {
64     ReadState* const state = reinterpret_cast<ReadState*>(gif->UserData);
65     // giflib API requires the input size `n` to be signed int.
66     if (static_cast<size_t>(n) > state->bytes.size()) {
67       n = state->bytes.size();
68     }
69     memcpy(bytes, state->bytes.data(), n);
70     state->bytes.remove_prefix(n);
71     return n;
72   };
73   GifUniquePtr gif(DGifOpen(&state, ReadFromSpan, &error));
74   if (gif == nullptr) {
75     if (error == D_GIF_ERR_NOT_GIF_FILE) {
76       // Not an error.
77       return false;
78     } else {
79       return JXL_FAILURE("Failed to read GIF: %s", GifErrorString(error));
80     }
81   }
82   error = DGifSlurp(gif.get());
83   if (error != GIF_OK) {
84     return JXL_FAILURE("Failed to read GIF: %s", GifErrorString(gif->Error));
85   }
86 
87 #ifdef MEMORY_SANITIZER
88   __msan_unpoison(gif.get(), sizeof(*gif));
89   if (gif->SColorMap) {
90     __msan_unpoison(gif->SColorMap, sizeof(*gif->SColorMap));
91     __msan_unpoison(gif->SColorMap->Colors, sizeof(*gif->SColorMap->Colors) *
92                                                 gif->SColorMap->ColorCount);
93   }
94   __msan_unpoison(gif->SavedImages,
95                   sizeof(*gif->SavedImages) * gif->ImageCount);
96 #endif
97 
98   const SizeConstraints* constraints = &io->constraints;
99 
100   JXL_RETURN_IF_ERROR(
101       VerifyDimensions<uint32_t>(constraints, gif->SWidth, gif->SHeight));
102   uint64_t total_pixel_count =
103       static_cast<uint64_t>(gif->SWidth) * gif->SHeight;
104   for (int i = 0; i < gif->ImageCount; ++i) {
105     const SavedImage& image = gif->SavedImages[i];
106     uint32_t w = image.ImageDesc.Width;
107     uint32_t h = image.ImageDesc.Height;
108     JXL_RETURN_IF_ERROR(VerifyDimensions<uint32_t>(constraints, w, h));
109     uint64_t pixel_count = static_cast<uint64_t>(w) * h;
110     if (total_pixel_count + pixel_count < total_pixel_count) {
111       return JXL_FAILURE("Image too big");
112     }
113     total_pixel_count += pixel_count;
114     if (total_pixel_count > constraints->dec_max_pixels) {
115       return JXL_FAILURE("Image too big");
116     }
117   }
118 
119   if (!gif->SColorMap) {
120     for (int i = 0; i < gif->ImageCount; ++i) {
121       if (!gif->SavedImages[i].ImageDesc.ColorMap) {
122         return JXL_FAILURE("Missing GIF color map");
123       }
124     }
125   }
126 
127   if (gif->ImageCount > 1) {
128     io->metadata.m.have_animation = true;
129     // Delays in GIF are specified in 100ths of a second.
130     io->metadata.m.animation.tps_numerator = 100;
131   }
132 
133   io->frames.clear();
134   io->frames.reserve(gif->ImageCount);
135   io->dec_pixels = 0;
136 
137   io->metadata.m.SetUintSamples(8);
138   io->metadata.m.color_encoding = ColorEncoding::SRGB();
139   io->metadata.m.SetAlphaBits(0);
140   (void)io->dec_hints.Foreach(
141       [](const std::string& key, const std::string& /*value*/) {
142         JXL_WARNING("GIF decoder ignoring %s hint", key.c_str());
143         return true;
144       });
145 
146   Image3F canvas(gif->SWidth, gif->SHeight);
147   io->SetSize(gif->SWidth, gif->SHeight);
148   ImageF alpha(gif->SWidth, gif->SHeight);
149   GifColorType background_color;
150   if (gif->SColorMap == nullptr) {
151     background_color = {0, 0, 0};
152   } else {
153     if (gif->SBackGroundColor >= gif->SColorMap->ColorCount) {
154       return JXL_FAILURE("GIF specifies out-of-bounds background color");
155     }
156     background_color = gif->SColorMap->Colors[gif->SBackGroundColor];
157   }
158   FillPlane<float>(background_color.Red, &canvas.Plane(0));
159   FillPlane<float>(background_color.Green, &canvas.Plane(1));
160   FillPlane<float>(background_color.Blue, &canvas.Plane(2));
161   ZeroFillImage(&alpha);
162 
163   Rect previous_rect_if_restore_to_background;
164 
165   bool has_alpha = false;
166   bool replace = true;
167   bool last_base_was_none = true;
168   for (int i = 0; i < gif->ImageCount; ++i) {
169     const SavedImage& image = gif->SavedImages[i];
170 #ifdef MEMORY_SANITIZER
171     __msan_unpoison(image.RasterBits, sizeof(*image.RasterBits) *
172                                           image.ImageDesc.Width *
173                                           image.ImageDesc.Height);
174 #endif
175     const Rect image_rect(image.ImageDesc.Left, image.ImageDesc.Top,
176                           image.ImageDesc.Width, image.ImageDesc.Height);
177     io->dec_pixels += image_rect.xsize() * image_rect.ysize();
178     Rect total_rect;
179     if (previous_rect_if_restore_to_background.xsize() != 0 ||
180         previous_rect_if_restore_to_background.ysize() != 0) {
181       const size_t xbegin = std::min(
182           image_rect.x0(), previous_rect_if_restore_to_background.x0());
183       const size_t ybegin = std::min(
184           image_rect.y0(), previous_rect_if_restore_to_background.y0());
185       const size_t xend =
186           std::max(image_rect.x0() + image_rect.xsize(),
187                    previous_rect_if_restore_to_background.x0() +
188                        previous_rect_if_restore_to_background.xsize());
189       const size_t yend =
190           std::max(image_rect.y0() + image_rect.ysize(),
191                    previous_rect_if_restore_to_background.y0() +
192                        previous_rect_if_restore_to_background.ysize());
193       total_rect = Rect(xbegin, ybegin, xend - xbegin, yend - ybegin);
194       previous_rect_if_restore_to_background = Rect();
195       replace = true;
196     } else {
197       total_rect = image_rect;
198       replace = false;
199     }
200     if (!image_rect.IsInside(canvas)) {
201       return JXL_FAILURE("GIF frame extends outside of the canvas");
202     }
203     const ColorMapObject* const color_map =
204         image.ImageDesc.ColorMap ? image.ImageDesc.ColorMap : gif->SColorMap;
205     JXL_CHECK(color_map);
206 #ifdef MEMORY_SANITIZER
207     __msan_unpoison(color_map, sizeof(*color_map));
208     __msan_unpoison(color_map->Colors,
209                     sizeof(*color_map->Colors) * color_map->ColorCount);
210 #endif
211     GraphicsControlBlock gcb;
212     DGifSavedExtensionToGCB(gif.get(), i, &gcb);
213 #ifdef MEMORY_SANITIZER
214     __msan_unpoison(&gcb, sizeof(gcb));
215 #endif
216 
217     ImageBundle bundle(&io->metadata.m);
218     if (io->metadata.m.have_animation) {
219       bundle.duration = gcb.DelayTime;
220       bundle.origin.x0 = total_rect.x0();
221       bundle.origin.y0 = total_rect.y0();
222       if (last_base_was_none) {
223         replace = true;
224       }
225       bundle.blend = !replace;
226       // TODO(veluca): this could in principle be implemented.
227       if (last_base_was_none &&
228           (total_rect.x0() != 0 || total_rect.y0() != 0 ||
229            total_rect.xsize() != canvas.xsize() ||
230            total_rect.ysize() != canvas.ysize() || !replace)) {
231         return JXL_FAILURE(
232             "GIF with dispose-to-0 is not supported for non-full or "
233             "blended frames");
234       }
235       switch (gcb.DisposalMode) {
236         case DISPOSE_DO_NOT:
237         case DISPOSE_BACKGROUND:
238           bundle.use_for_next_frame = true;
239           last_base_was_none = false;
240           break;
241         case DISPOSE_PREVIOUS:
242           bundle.use_for_next_frame = false;
243           break;
244         default:
245           bundle.use_for_next_frame = false;
246           last_base_was_none = true;
247       }
248     }
249     Image3F frame = CopyImage(canvas);
250     ImageF frame_alpha = CopyImage(alpha);
251     for (size_t y = 0, byte_index = 0; y < image_rect.ysize(); ++y) {
252       float* const JXL_RESTRICT row_r = image_rect.Row(&frame.Plane(0), y);
253       float* const JXL_RESTRICT row_g = image_rect.Row(&frame.Plane(1), y);
254       float* const JXL_RESTRICT row_b = image_rect.Row(&frame.Plane(2), y);
255       float* const JXL_RESTRICT row_alpha = image_rect.Row(&frame_alpha, y);
256       for (size_t x = 0; x < image_rect.xsize(); ++x, ++byte_index) {
257         const GifByteType byte = image.RasterBits[byte_index];
258         if (byte >= color_map->ColorCount) {
259           return JXL_FAILURE("GIF color is out of bounds");
260         }
261         if (byte == gcb.TransparentColor) continue;
262         GifColorType color = color_map->Colors[byte];
263         row_alpha[x] = 1.f;
264         row_r[x] = (1.f / 255) * color.Red;
265         row_g[x] = (1.f / 255) * color.Green;
266         row_b[x] = (1.f / 255) * color.Blue;
267       }
268     }
269     Image3F sub_frame(total_rect.xsize(), total_rect.ysize());
270     ImageF sub_frame_alpha(total_rect.xsize(), total_rect.ysize());
271     bool blend_alpha = false;
272     if (replace) {
273       CopyImageTo(total_rect, frame, &sub_frame);
274       CopyImageTo(total_rect, frame_alpha, &sub_frame_alpha);
275     } else {
276       for (size_t y = 0, byte_index = 0; y < image_rect.ysize(); ++y) {
277         float* const JXL_RESTRICT row_r = sub_frame.PlaneRow(0, y);
278         float* const JXL_RESTRICT row_g = sub_frame.PlaneRow(1, y);
279         float* const JXL_RESTRICT row_b = sub_frame.PlaneRow(2, y);
280         float* const JXL_RESTRICT row_alpha = sub_frame_alpha.Row(y);
281         for (size_t x = 0; x < image_rect.xsize(); ++x, ++byte_index) {
282           const GifByteType byte = image.RasterBits[byte_index];
283           if (byte > color_map->ColorCount) {
284             return JXL_FAILURE("GIF color is out of bounds");
285           }
286           if (byte == gcb.TransparentColor) {
287             row_alpha[x] = 0;
288             row_r[x] = 0;
289             row_g[x] = 0;
290             row_b[x] = 0;
291             blend_alpha =
292                 true;  // need to use alpha channel if BlendMode blend is used
293             continue;
294           }
295           GifColorType color = color_map->Colors[byte];
296           row_alpha[x] = 1.f;
297           row_r[x] = (1.f / 255) * color.Red;
298           row_g[x] = (1.f / 255) * color.Green;
299           row_b[x] = (1.f / 255) * color.Blue;
300         }
301       }
302     }
303     bundle.SetFromImage(std::move(sub_frame), ColorEncoding::SRGB());
304     if (has_alpha || !AllOpaque(frame_alpha) || blend_alpha) {
305       if (!has_alpha) {
306         has_alpha = true;
307         io->metadata.m.SetAlphaBits(8);
308         for (ImageBundle& previous_frame : io->frames) {
309           ImageF previous_alpha(previous_frame.xsize(), previous_frame.ysize());
310           FillImage(1.f, &previous_alpha);
311           previous_frame.SetAlpha(std::move(previous_alpha),
312                                   /*alpha_is_premultiplied=*/false);
313         }
314       }
315       bundle.SetAlpha(std::move(sub_frame_alpha),
316                       /*alpha_is_premultiplied=*/false);
317     }
318     io->frames.push_back(std::move(bundle));
319     switch (gcb.DisposalMode) {
320       case DISPOSE_DO_NOT:
321         canvas = std::move(frame);
322         alpha = std::move(frame_alpha);
323         break;
324 
325       case DISPOSE_BACKGROUND:
326         FillPlane<float>((1.f / 255) * background_color.Red, &canvas.Plane(0),
327                          image_rect);
328         FillPlane<float>((1.f / 255) * background_color.Green, &canvas.Plane(1),
329                          image_rect);
330         FillPlane<float>((1.f / 255) * background_color.Blue, &canvas.Plane(2),
331                          image_rect);
332         FillPlane(0.f, &alpha, image_rect);
333         previous_rect_if_restore_to_background = image_rect;
334         break;
335 
336       case DISPOSE_PREVIOUS:
337         break;
338 
339       case DISPOSAL_UNSPECIFIED:
340       default:
341         FillPlane<float>((1.f / 255) * background_color.Red, &canvas.Plane(0));
342         FillPlane<float>((1.f / 255) * background_color.Green,
343                          &canvas.Plane(1));
344         FillPlane<float>((1.f / 255) * background_color.Blue, &canvas.Plane(2));
345         ZeroFillImage(&alpha);
346     }
347   }
348 
349   SetIntensityTarget(io);
350   return true;
351 }
352 
353 }  // namespace jxl
354