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