libjxl

FORK: libjxl patches used on blog
git clone https://git.neptards.moe/blog/libjxl.git
Log | Files | Refs | Submodules | README | LICENSE

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