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