tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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