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