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/dec/gif.h"
7 
8 #include <gif_lib.h>
9 #include <string.h>
10 
11 #include <memory>
12 #include <utility>
13 #include <vector>
14 
15 #include "lib/jxl/base/compiler_specific.h"
16 #include "lib/jxl/sanitizers.h"
17 
18 namespace jxl {
19 namespace extras {
20 
21 namespace {
22 
23 struct ReadState {
24   Span<const uint8_t> bytes;
25 };
26 
27 struct DGifCloser {
operator ()jxl::extras::__anon2846beaf0111::DGifCloser28   void operator()(GifFileType* const ptr) const { DGifCloseFile(ptr, nullptr); }
29 };
30 using GifUniquePtr = std::unique_ptr<GifFileType, DGifCloser>;
31 
32 struct PackedRgba {
33   uint8_t r, g, b, a;
34 };
35 
36 struct PackedRgb {
37   uint8_t r, g, b;
38 };
39 
40 // Gif does not support partial transparency, so this considers any nonzero
41 // alpha channel value as opaque.
AllOpaque(const PackedImage & color)42 bool AllOpaque(const PackedImage& color) {
43   for (size_t y = 0; y < color.ysize; ++y) {
44     const PackedRgba* const JXL_RESTRICT row =
45         static_cast<const PackedRgba*>(color.pixels()) + y * color.xsize;
46     for (size_t x = 0; x < color.xsize; ++x) {
47       if (row[x].a == 0) {
48         return false;
49       }
50     }
51   }
52   return true;
53 }
54 
ensure_have_alpha(PackedFrame * frame)55 void ensure_have_alpha(PackedFrame* frame) {
56   if (!frame->extra_channels.empty()) return;
57   const JxlPixelFormat alpha_format{
58       /*num_channels=*/1u,
59       /*data_type=*/JXL_TYPE_UINT8,
60       /*endianness=*/JXL_NATIVE_ENDIAN,
61       /*align=*/0,
62   };
63   frame->extra_channels.emplace_back(frame->color.xsize, frame->color.ysize,
64                                      alpha_format);
65   // We need to set opaque-by-default.
66   std::fill_n(static_cast<uint8_t*>(frame->extra_channels[0].pixels()),
67               frame->color.xsize * frame->color.ysize, 255u);
68 }
69 
70 }  // namespace
71 
DecodeImageGIF(Span<const uint8_t> bytes,const ColorHints & color_hints,const SizeConstraints & constraints,PackedPixelFile * ppf)72 Status DecodeImageGIF(Span<const uint8_t> bytes, const ColorHints& color_hints,
73                       const SizeConstraints& constraints,
74                       PackedPixelFile* ppf) {
75   int error = GIF_OK;
76   ReadState state = {bytes};
77   const auto ReadFromSpan = [](GifFileType* const gif, GifByteType* const bytes,
78                                int n) {
79     ReadState* const state = reinterpret_cast<ReadState*>(gif->UserData);
80     // giflib API requires the input size `n` to be signed int.
81     if (static_cast<size_t>(n) > state->bytes.size()) {
82       n = state->bytes.size();
83     }
84     memcpy(bytes, state->bytes.data(), n);
85     state->bytes.remove_prefix(n);
86     return n;
87   };
88   GifUniquePtr gif(DGifOpen(&state, ReadFromSpan, &error));
89   if (gif == nullptr) {
90     if (error == D_GIF_ERR_NOT_GIF_FILE) {
91       // Not an error.
92       return false;
93     } else {
94       return JXL_FAILURE("Failed to read GIF: %s", GifErrorString(error));
95     }
96   }
97   error = DGifSlurp(gif.get());
98   if (error != GIF_OK) {
99     return JXL_FAILURE("Failed to read GIF: %s", GifErrorString(gif->Error));
100   }
101 
102   msan::UnpoisonMemory(gif.get(), sizeof(*gif));
103   if (gif->SColorMap) {
104     msan::UnpoisonMemory(gif->SColorMap, sizeof(*gif->SColorMap));
105     msan::UnpoisonMemory(
106         gif->SColorMap->Colors,
107         sizeof(*gif->SColorMap->Colors) * gif->SColorMap->ColorCount);
108   }
109   msan::UnpoisonMemory(gif->SavedImages,
110                        sizeof(*gif->SavedImages) * gif->ImageCount);
111 
112   JXL_RETURN_IF_ERROR(
113       VerifyDimensions<uint32_t>(&constraints, gif->SWidth, gif->SHeight));
114   uint64_t total_pixel_count =
115       static_cast<uint64_t>(gif->SWidth) * gif->SHeight;
116   for (int i = 0; i < gif->ImageCount; ++i) {
117     const SavedImage& image = gif->SavedImages[i];
118     uint32_t w = image.ImageDesc.Width;
119     uint32_t h = image.ImageDesc.Height;
120     JXL_RETURN_IF_ERROR(VerifyDimensions<uint32_t>(&constraints, w, h));
121     uint64_t pixel_count = static_cast<uint64_t>(w) * h;
122     if (total_pixel_count + pixel_count < total_pixel_count) {
123       return JXL_FAILURE("Image too big");
124     }
125     total_pixel_count += pixel_count;
126     if (total_pixel_count > constraints.dec_max_pixels) {
127       return JXL_FAILURE("Image too big");
128     }
129   }
130 
131   if (!gif->SColorMap) {
132     for (int i = 0; i < gif->ImageCount; ++i) {
133       if (!gif->SavedImages[i].ImageDesc.ColorMap) {
134         return JXL_FAILURE("Missing GIF color map");
135       }
136     }
137   }
138 
139   if (gif->ImageCount > 1) {
140     ppf->info.have_animation = true;
141     // Delays in GIF are specified in 100ths of a second.
142     ppf->info.animation.tps_numerator = 100;
143     ppf->info.animation.tps_denominator = 1;
144   }
145 
146   ppf->frames.clear();
147   ppf->frames.reserve(gif->ImageCount);
148 
149   ppf->info.xsize = gif->SWidth;
150   ppf->info.ysize = gif->SHeight;
151   ppf->info.bits_per_sample = 8;
152   ppf->info.exponent_bits_per_sample = 0;
153   // alpha_bits is later set to 8 if we find a frame with transparent pixels.
154   ppf->info.alpha_bits = 0;
155   ppf->info.alpha_exponent_bits = 0;
156   JXL_RETURN_IF_ERROR(ApplyColorHints(color_hints, /*color_already_set=*/false,
157                                       /*is_gray=*/false, ppf));
158 
159   ppf->info.num_color_channels = 3;
160 
161   // Pixel format for the 'canvas' onto which we paint
162   // the (potentially individually cropped) GIF frames
163   // of an animation.
164   const JxlPixelFormat canvas_format{
165       /*num_channels=*/4u,
166       /*data_type=*/JXL_TYPE_UINT8,
167       /*endianness=*/JXL_NATIVE_ENDIAN,
168       /*align=*/0,
169   };
170 
171   // Pixel format for the JXL PackedFrame that goes into the
172   // PackedPixelFile. Here, we use 3 color channels, and provide
173   // the alpha channel as an extra_channel wherever it is used.
174   const JxlPixelFormat packed_frame_format{
175       /*num_channels=*/3u,
176       /*data_type=*/JXL_TYPE_UINT8,
177       /*endianness=*/JXL_NATIVE_ENDIAN,
178       /*align=*/0,
179   };
180 
181   GifColorType background_color;
182   if (gif->SColorMap == nullptr ||
183       gif->SBackGroundColor >= gif->SColorMap->ColorCount) {
184     background_color = {0, 0, 0};
185   } else {
186     background_color = gif->SColorMap->Colors[gif->SBackGroundColor];
187   }
188   const PackedRgba background_rgba{background_color.Red, background_color.Green,
189                                    background_color.Blue, 0};
190   PackedFrame canvas(gif->SWidth, gif->SHeight, canvas_format);
191   std::fill_n(static_cast<PackedRgba*>(canvas.color.pixels()),
192               canvas.color.xsize * canvas.color.ysize, background_rgba);
193   Rect canvas_rect{0, 0, canvas.color.xsize, canvas.color.ysize};
194 
195   Rect previous_rect_if_restore_to_background;
196 
197   bool replace = true;
198   bool last_base_was_none = true;
199   for (int i = 0; i < gif->ImageCount; ++i) {
200     const SavedImage& image = gif->SavedImages[i];
201     msan::UnpoisonMemory(image.RasterBits, sizeof(*image.RasterBits) *
202                                                image.ImageDesc.Width *
203                                                image.ImageDesc.Height);
204     const Rect image_rect(image.ImageDesc.Left, image.ImageDesc.Top,
205                           image.ImageDesc.Width, image.ImageDesc.Height);
206 
207     Rect total_rect;
208     if (previous_rect_if_restore_to_background.xsize() != 0 ||
209         previous_rect_if_restore_to_background.ysize() != 0) {
210       const size_t xbegin = std::min(
211           image_rect.x0(), previous_rect_if_restore_to_background.x0());
212       const size_t ybegin = std::min(
213           image_rect.y0(), previous_rect_if_restore_to_background.y0());
214       const size_t xend =
215           std::max(image_rect.x0() + image_rect.xsize(),
216                    previous_rect_if_restore_to_background.x0() +
217                        previous_rect_if_restore_to_background.xsize());
218       const size_t yend =
219           std::max(image_rect.y0() + image_rect.ysize(),
220                    previous_rect_if_restore_to_background.y0() +
221                        previous_rect_if_restore_to_background.ysize());
222       total_rect = Rect(xbegin, ybegin, xend - xbegin, yend - ybegin);
223       previous_rect_if_restore_to_background = Rect();
224       replace = true;
225     } else {
226       total_rect = image_rect;
227       replace = false;
228     }
229     if (!image_rect.IsInside(canvas_rect)) {
230       return JXL_FAILURE("GIF frame extends outside of the canvas");
231     }
232 
233     // Allocates the frame buffer.
234     ppf->frames.emplace_back(total_rect.xsize(), total_rect.ysize(),
235                              packed_frame_format);
236     PackedFrame* frame = &ppf->frames.back();
237 
238     // We cannot tell right from the start whether there will be a
239     // need for an alpha channel. This is discovered only as soon as
240     // we see a transparent pixel. We hence initialize alpha lazily.
241     auto set_pixel_alpha = [&frame](size_t x, size_t y, uint8_t a) {
242       // If we do not have an alpha-channel and a==255 (fully opaque),
243       // we can skip setting this pixel-value and rely on
244       // "no alpha channel = no transparency".
245       if (a == 255 && !frame->extra_channels.empty()) return;
246       ensure_have_alpha(frame);
247       static_cast<uint8_t*>(
248           frame->extra_channels[0].pixels())[y * frame->color.xsize + x] = a;
249     };
250 
251     const ColorMapObject* const color_map =
252         image.ImageDesc.ColorMap ? image.ImageDesc.ColorMap : gif->SColorMap;
253     JXL_CHECK(color_map);
254     msan::UnpoisonMemory(color_map, sizeof(*color_map));
255     msan::UnpoisonMemory(color_map->Colors,
256                          sizeof(*color_map->Colors) * color_map->ColorCount);
257     GraphicsControlBlock gcb;
258     DGifSavedExtensionToGCB(gif.get(), i, &gcb);
259     msan::UnpoisonMemory(&gcb, sizeof(gcb));
260 
261     if (ppf->info.have_animation) {
262       frame->frame_info.duration = gcb.DelayTime;
263       frame->x0 = total_rect.x0();
264       frame->y0 = total_rect.y0();
265       if (last_base_was_none) {
266         replace = true;
267       }
268       frame->blend = !replace;
269       // TODO(veluca): this could in principle be implemented.
270       if (last_base_was_none &&
271           (total_rect.x0() != 0 || total_rect.y0() != 0 ||
272            total_rect.xsize() != canvas.color.xsize ||
273            total_rect.ysize() != canvas.color.ysize || !replace)) {
274         return JXL_FAILURE(
275             "GIF with dispose-to-0 is not supported for non-full or "
276             "blended frames");
277       }
278       switch (gcb.DisposalMode) {
279         case DISPOSE_DO_NOT:
280         case DISPOSE_BACKGROUND:
281           frame->use_for_next_frame = true;
282           last_base_was_none = false;
283           break;
284         case DISPOSE_PREVIOUS:
285           frame->use_for_next_frame = false;
286           break;
287         default:
288           frame->use_for_next_frame = false;
289           last_base_was_none = true;
290       }
291     }
292 
293     // Update the canvas by creating a copy first.
294     PackedImage new_canvas_image(canvas.color.xsize, canvas.color.ysize,
295                                  canvas.color.format);
296     memcpy(new_canvas_image.pixels(), canvas.color.pixels(),
297            new_canvas_image.pixels_size);
298     for (size_t y = 0, byte_index = 0; y < image_rect.ysize(); ++y) {
299       // Assumes format.align == 0. row points to the beginning of the y row in
300       // the image_rect.
301       PackedRgba* row = static_cast<PackedRgba*>(new_canvas_image.pixels()) +
302                         (y + image_rect.y0()) * new_canvas_image.xsize +
303                         image_rect.x0();
304       for (size_t x = 0; x < image_rect.xsize(); ++x, ++byte_index) {
305         const GifByteType byte = image.RasterBits[byte_index];
306         if (byte >= color_map->ColorCount) {
307           return JXL_FAILURE("GIF color is out of bounds");
308         }
309 
310         if (byte == gcb.TransparentColor) continue;
311         GifColorType color = color_map->Colors[byte];
312         row[x].r = color.Red;
313         row[x].g = color.Green;
314         row[x].b = color.Blue;
315         row[x].a = 255;
316       }
317     }
318     const PackedImage& sub_frame_image = frame->color;
319     if (replace) {
320       // Copy from the new canvas image to the subframe
321       for (size_t y = 0; y < total_rect.ysize(); ++y) {
322         const PackedRgba* row_in =
323             static_cast<const PackedRgba*>(new_canvas_image.pixels()) +
324             (y + total_rect.y0()) * new_canvas_image.xsize + total_rect.x0();
325         PackedRgb* row_out = static_cast<PackedRgb*>(sub_frame_image.pixels()) +
326                              y * sub_frame_image.xsize;
327         for (size_t x = 0; x < sub_frame_image.xsize; ++x) {
328           row_out[x].r = row_in[x].r;
329           row_out[x].g = row_in[x].g;
330           row_out[x].b = row_in[x].b;
331           set_pixel_alpha(x, y, row_in[x].a);
332         }
333       }
334     } else {
335       for (size_t y = 0, byte_index = 0; y < image_rect.ysize(); ++y) {
336         // Assumes format.align == 0
337         PackedRgb* row = static_cast<PackedRgb*>(sub_frame_image.pixels()) +
338                          y * sub_frame_image.xsize;
339         for (size_t x = 0; x < image_rect.xsize(); ++x, ++byte_index) {
340           const GifByteType byte = image.RasterBits[byte_index];
341           if (byte > color_map->ColorCount) {
342             return JXL_FAILURE("GIF color is out of bounds");
343           }
344           if (byte == gcb.TransparentColor) {
345             row[x].r = 0;
346             row[x].g = 0;
347             row[x].b = 0;
348             set_pixel_alpha(x, y, 0);
349             continue;
350           }
351           GifColorType color = color_map->Colors[byte];
352           row[x].r = color.Red;
353           row[x].g = color.Green;
354           row[x].b = color.Blue;
355           set_pixel_alpha(x, y, 255);
356         }
357       }
358     }
359 
360     if (!frame->extra_channels.empty()) {
361       ppf->info.alpha_bits = 8;
362     }
363 
364     switch (gcb.DisposalMode) {
365       case DISPOSE_DO_NOT:
366         canvas.color = std::move(new_canvas_image);
367         break;
368 
369       case DISPOSE_BACKGROUND:
370         std::fill_n(static_cast<PackedRgba*>(canvas.color.pixels()),
371                     canvas.color.xsize * canvas.color.ysize, background_rgba);
372         previous_rect_if_restore_to_background = image_rect;
373         break;
374 
375       case DISPOSE_PREVIOUS:
376         break;
377 
378       case DISPOSAL_UNSPECIFIED:
379       default:
380         std::fill_n(static_cast<PackedRgba*>(canvas.color.pixels()),
381                     canvas.color.xsize * canvas.color.ysize, background_rgba);
382     }
383   }
384   // Finally, if any frame has an alpha-channel, every frame will need
385   // to have an alpha-channel.
386   bool seen_alpha = false;
387   for (const PackedFrame& frame : ppf->frames) {
388     if (!frame.extra_channels.empty()) {
389       seen_alpha = true;
390       break;
391     }
392   }
393   if (seen_alpha) {
394     for (PackedFrame& frame : ppf->frames) {
395       ensure_have_alpha(&frame);
396     }
397   }
398   return true;
399 }
400 
401 }  // namespace extras
402 }  // namespace jxl
403