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