tor-browser

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

packed_image_convert.cc (15570B)


      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/packed_image_convert.h"
      7 
      8 #include <jxl/cms.h>
      9 #include <jxl/color_encoding.h>
     10 #include <jxl/memory_manager.h>
     11 #include <jxl/types.h>
     12 
     13 #include <cstdint>
     14 #include <cstdio>
     15 
     16 #include "lib/extras/packed_image.h"
     17 #include "lib/jxl/base/rect.h"
     18 #include "lib/jxl/base/status.h"
     19 #include "lib/jxl/color_encoding_internal.h"
     20 #include "lib/jxl/dec_external_image.h"
     21 #include "lib/jxl/enc_external_image.h"
     22 #include "lib/jxl/enc_image_bundle.h"
     23 #include "lib/jxl/luminance.h"
     24 
     25 namespace jxl {
     26 namespace extras {
     27 
     28 Status ConvertPackedFrameToImageBundle(const JxlBasicInfo& info,
     29                                       const JxlBitDepth& input_bitdepth,
     30                                       const PackedFrame& frame,
     31                                       const CodecInOut& io, ThreadPool* pool,
     32                                       ImageBundle* bundle) {
     33  JxlMemoryManager* memory_manager = io.memory_manager;
     34  JXL_ENSURE(frame.color.pixels() != nullptr);
     35  size_t frame_bits_per_sample;
     36  if (input_bitdepth.type == JXL_BIT_DEPTH_FROM_PIXEL_FORMAT) {
     37    JXL_RETURN_IF_ERROR(
     38        PackedImage::ValidateDataType(frame.color.format.data_type));
     39    frame_bits_per_sample =
     40        PackedImage::BitsPerChannel(frame.color.format.data_type);
     41  } else {
     42    frame_bits_per_sample = info.bits_per_sample;
     43  }
     44  JXL_ENSURE(frame_bits_per_sample != 0);
     45  // It is ok for the frame.color.format.num_channels to not match the
     46  // number of channels on the image.
     47  JXL_ENSURE(1 <= frame.color.format.num_channels &&
     48             frame.color.format.num_channels <= 4);
     49 
     50  const Span<const uint8_t> span(
     51      static_cast<const uint8_t*>(frame.color.pixels()),
     52      frame.color.pixels_size);
     53  JXL_ENSURE(Rect(frame.frame_info.layer_info.crop_x0,
     54                  frame.frame_info.layer_info.crop_y0,
     55                  frame.frame_info.layer_info.xsize,
     56                  frame.frame_info.layer_info.ysize)
     57                 .IsInside(Rect(0, 0, info.xsize, info.ysize)));
     58  if (info.have_animation) {
     59    bundle->duration = frame.frame_info.duration;
     60    bundle->blend = frame.frame_info.layer_info.blend_info.blendmode > 0;
     61    bundle->use_for_next_frame =
     62        frame.frame_info.layer_info.save_as_reference > 0;
     63    bundle->origin.x0 = frame.frame_info.layer_info.crop_x0;
     64    bundle->origin.y0 = frame.frame_info.layer_info.crop_y0;
     65  }
     66  bundle->name = frame.name;  // frame.frame_info.name_length is ignored here.
     67  JXL_ENSURE(io.metadata.m.color_encoding.IsGray() ==
     68             (frame.color.format.num_channels <= 2));
     69 
     70  JXL_RETURN_IF_ERROR(ConvertFromExternal(
     71      span, frame.color.xsize, frame.color.ysize, io.metadata.m.color_encoding,
     72      frame_bits_per_sample, frame.color.format, pool, bundle));
     73 
     74  bundle->extra_channels().resize(io.metadata.m.extra_channel_info.size());
     75  for (size_t i = 0; i < frame.extra_channels.size(); i++) {
     76    const auto& ppf_ec = frame.extra_channels[i];
     77    JXL_ASSIGN_OR_RETURN(
     78        bundle->extra_channels()[i],
     79        ImageF::Create(memory_manager, ppf_ec.xsize, ppf_ec.ysize));
     80    JXL_RETURN_IF_ERROR(BufferToImageF(
     81        ppf_ec.format, ppf_ec.xsize, ppf_ec.ysize, ppf_ec.pixels(),
     82        ppf_ec.pixels_size, pool, &bundle->extra_channels()[i]));
     83  }
     84  return true;
     85 }
     86 
     87 Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf,
     88                                          ThreadPool* pool, CodecInOut* io) {
     89  JxlMemoryManager* memory_manager = io->memory_manager;
     90  const bool has_alpha = ppf.info.alpha_bits != 0;
     91  JXL_ENSURE(!ppf.frames.empty());
     92  if (has_alpha) {
     93    JXL_ENSURE(ppf.info.alpha_bits == ppf.info.bits_per_sample);
     94    JXL_ENSURE(ppf.info.alpha_exponent_bits ==
     95               ppf.info.exponent_bits_per_sample);
     96  }
     97 
     98  const bool is_gray = (ppf.info.num_color_channels == 1);
     99  JXL_ENSURE(ppf.info.num_color_channels == 1 ||
    100             ppf.info.num_color_channels == 3);
    101 
    102  // Convert the image metadata
    103  JXL_RETURN_IF_ERROR(io->SetSize(ppf.info.xsize, ppf.info.ysize));
    104  io->metadata.m.bit_depth.bits_per_sample = ppf.info.bits_per_sample;
    105  io->metadata.m.bit_depth.exponent_bits_per_sample =
    106      ppf.info.exponent_bits_per_sample;
    107  io->metadata.m.bit_depth.floating_point_sample =
    108      ppf.info.exponent_bits_per_sample != 0;
    109  io->metadata.m.modular_16_bit_buffer_sufficient =
    110      ppf.info.exponent_bits_per_sample == 0 && ppf.info.bits_per_sample <= 12;
    111 
    112  io->metadata.m.SetAlphaBits(ppf.info.alpha_bits,
    113                              FROM_JXL_BOOL(ppf.info.alpha_premultiplied));
    114  ExtraChannelInfo* alpha = io->metadata.m.Find(ExtraChannel::kAlpha);
    115  if (alpha) alpha->bit_depth = io->metadata.m.bit_depth;
    116 
    117  io->metadata.m.xyb_encoded = !FROM_JXL_BOOL(ppf.info.uses_original_profile);
    118  JXL_ENSURE(ppf.info.orientation > 0 && ppf.info.orientation <= 8);
    119  io->metadata.m.orientation = ppf.info.orientation;
    120 
    121  // Convert animation metadata
    122  JXL_ENSURE(ppf.frames.size() == 1 || ppf.info.have_animation);
    123  io->metadata.m.have_animation = FROM_JXL_BOOL(ppf.info.have_animation);
    124  io->metadata.m.animation.tps_numerator = ppf.info.animation.tps_numerator;
    125  io->metadata.m.animation.tps_denominator = ppf.info.animation.tps_denominator;
    126  io->metadata.m.animation.num_loops = ppf.info.animation.num_loops;
    127 
    128  // Convert the color encoding.
    129  if (ppf.primary_color_representation == PackedPixelFile::kIccIsPrimary) {
    130    IccBytes icc = ppf.icc;
    131    if (!io->metadata.m.color_encoding.SetICC(std::move(icc),
    132                                              JxlGetDefaultCms())) {
    133      fprintf(stderr, "Warning: error setting ICC profile, assuming SRGB\n");
    134      io->metadata.m.color_encoding = ColorEncoding::SRGB(is_gray);
    135    } else {
    136      if (io->metadata.m.color_encoding.IsCMYK()) {
    137        // We expect gray or tri-color.
    138        return JXL_FAILURE("Embedded ICC is CMYK");
    139      }
    140      if (io->metadata.m.color_encoding.IsGray() != is_gray) {
    141        // E.g. JPG image has 3 channels, but gray ICC.
    142        return JXL_FAILURE("Embedded ICC does not match image color type");
    143      }
    144    }
    145  } else {
    146    JXL_RETURN_IF_ERROR(
    147        io->metadata.m.color_encoding.FromExternal(ppf.color_encoding));
    148    if (io->metadata.m.color_encoding.ICC().empty()) {
    149      return JXL_FAILURE("Failed to serialize ICC");
    150    }
    151  }
    152 
    153  // Convert the extra blobs
    154  io->blobs.exif = ppf.metadata.exif;
    155  io->blobs.iptc = ppf.metadata.iptc;
    156  io->blobs.jhgm = ppf.metadata.jhgm;
    157  io->blobs.jumbf = ppf.metadata.jumbf;
    158  io->blobs.xmp = ppf.metadata.xmp;
    159 
    160  // Append all other extra channels.
    161  for (const auto& info : ppf.extra_channels_info) {
    162    ExtraChannelInfo out;
    163    out.type = static_cast<jxl::ExtraChannel>(info.ec_info.type);
    164    out.bit_depth.bits_per_sample = info.ec_info.bits_per_sample;
    165    out.bit_depth.exponent_bits_per_sample =
    166        info.ec_info.exponent_bits_per_sample;
    167    out.bit_depth.floating_point_sample =
    168        info.ec_info.exponent_bits_per_sample != 0;
    169    out.dim_shift = info.ec_info.dim_shift;
    170    out.name = info.name;
    171    out.alpha_associated = (info.ec_info.alpha_premultiplied != 0);
    172    out.spot_color[0] = info.ec_info.spot_color[0];
    173    out.spot_color[1] = info.ec_info.spot_color[1];
    174    out.spot_color[2] = info.ec_info.spot_color[2];
    175    out.spot_color[3] = info.ec_info.spot_color[3];
    176    io->metadata.m.extra_channel_info.push_back(std::move(out));
    177  }
    178 
    179  // Convert the preview
    180  if (ppf.preview_frame) {
    181    size_t preview_xsize = ppf.preview_frame->color.xsize;
    182    size_t preview_ysize = ppf.preview_frame->color.ysize;
    183    io->metadata.m.have_preview = true;
    184    JXL_RETURN_IF_ERROR(
    185        io->metadata.m.preview_size.Set(preview_xsize, preview_ysize));
    186    JXL_RETURN_IF_ERROR(ConvertPackedFrameToImageBundle(
    187        ppf.info, ppf.input_bitdepth, *ppf.preview_frame, *io, pool,
    188        &io->preview_frame));
    189  }
    190 
    191  // Convert the pixels
    192  io->frames.clear();
    193  for (const auto& frame : ppf.frames) {
    194    ImageBundle bundle(memory_manager, &io->metadata.m);
    195    JXL_RETURN_IF_ERROR(ConvertPackedFrameToImageBundle(
    196        ppf.info, ppf.input_bitdepth, frame, *io, pool, &bundle));
    197    io->frames.push_back(std::move(bundle));
    198  }
    199 
    200  if (ppf.info.exponent_bits_per_sample == 0) {
    201    // uint case.
    202    io->metadata.m.bit_depth.bits_per_sample = io->Main().DetectRealBitdepth();
    203  }
    204  if (ppf.info.intensity_target != 0) {
    205    io->metadata.m.SetIntensityTarget(ppf.info.intensity_target);
    206  } else {
    207    SetIntensityTarget(&io->metadata.m);
    208  }
    209  JXL_RETURN_IF_ERROR(io->CheckMetadata());
    210  return true;
    211 }
    212 
    213 StatusOr<PackedPixelFile> ConvertImage3FToPackedPixelFile(
    214    const Image3F& image, const ColorEncoding& c_enc, JxlPixelFormat format,
    215    ThreadPool* pool) {
    216  PackedPixelFile ppf{};
    217  ppf.info.xsize = image.xsize();
    218  ppf.info.ysize = image.ysize();
    219  ppf.info.num_color_channels = 3;
    220  JXL_RETURN_IF_ERROR(PackedImage::ValidateDataType(format.data_type));
    221  ppf.info.bits_per_sample = PackedImage::BitsPerChannel(format.data_type);
    222  ppf.info.exponent_bits_per_sample = format.data_type == JXL_TYPE_FLOAT ? 8
    223                                      : format.data_type == JXL_TYPE_FLOAT16
    224                                          ? 5
    225                                          : 0;
    226  ppf.color_encoding = c_enc.ToExternal();
    227  ppf.frames.clear();
    228  JXL_ASSIGN_OR_RETURN(
    229      PackedFrame frame,
    230      PackedFrame::Create(image.xsize(), image.ysize(), format));
    231  const ImageF* channels[3];
    232  for (int c = 0; c < 3; ++c) {
    233    channels[c] = &image.Plane(c);
    234  }
    235  bool float_samples = ppf.info.exponent_bits_per_sample > 0;
    236  JXL_RETURN_IF_ERROR(ConvertChannelsToExternal(
    237      channels, 3, ppf.info.bits_per_sample, float_samples, format.endianness,
    238      frame.color.stride, pool, frame.color.pixels(0, 0, 0),
    239      frame.color.pixels_size, PixelCallback(), Orientation::kIdentity));
    240  ppf.frames.emplace_back(std::move(frame));
    241  return ppf;
    242 }
    243 
    244 // Allows converting from internal CodecInOut to external PackedPixelFile
    245 Status ConvertCodecInOutToPackedPixelFile(const CodecInOut& io,
    246                                          const JxlPixelFormat& pixel_format,
    247                                          const ColorEncoding& c_desired,
    248                                          ThreadPool* pool,
    249                                          PackedPixelFile* ppf) {
    250  JxlMemoryManager* memory_manager = io.memory_manager;
    251  const bool has_alpha = io.metadata.m.HasAlpha();
    252  JXL_ENSURE(!io.frames.empty());
    253 
    254  if (has_alpha) {
    255    JXL_ENSURE(io.metadata.m.GetAlphaBits() ==
    256               io.metadata.m.bit_depth.bits_per_sample);
    257    const auto* alpha_channel = io.metadata.m.Find(ExtraChannel::kAlpha);
    258    JXL_ENSURE(alpha_channel->bit_depth.exponent_bits_per_sample ==
    259               io.metadata.m.bit_depth.exponent_bits_per_sample);
    260    ppf->info.alpha_bits = alpha_channel->bit_depth.bits_per_sample;
    261    ppf->info.alpha_exponent_bits =
    262        alpha_channel->bit_depth.exponent_bits_per_sample;
    263    ppf->info.alpha_premultiplied =
    264        TO_JXL_BOOL(alpha_channel->alpha_associated);
    265  }
    266 
    267  // Convert the image metadata
    268  ppf->info.xsize = io.metadata.size.xsize();
    269  ppf->info.ysize = io.metadata.size.ysize();
    270  ppf->info.num_color_channels = io.metadata.m.color_encoding.Channels();
    271  ppf->info.bits_per_sample = io.metadata.m.bit_depth.bits_per_sample;
    272  ppf->info.exponent_bits_per_sample =
    273      io.metadata.m.bit_depth.exponent_bits_per_sample;
    274 
    275  ppf->info.intensity_target = io.metadata.m.tone_mapping.intensity_target;
    276  ppf->info.linear_below = io.metadata.m.tone_mapping.linear_below;
    277  ppf->info.min_nits = io.metadata.m.tone_mapping.min_nits;
    278  ppf->info.relative_to_max_display =
    279      TO_JXL_BOOL(io.metadata.m.tone_mapping.relative_to_max_display);
    280 
    281  ppf->info.uses_original_profile = TO_JXL_BOOL(!io.metadata.m.xyb_encoded);
    282  JXL_ENSURE(0 < io.metadata.m.orientation && io.metadata.m.orientation <= 8);
    283  ppf->info.orientation =
    284      static_cast<JxlOrientation>(io.metadata.m.orientation);
    285  ppf->info.num_color_channels = io.metadata.m.color_encoding.Channels();
    286 
    287  // Convert animation metadata
    288  JXL_ENSURE(io.frames.size() == 1 || io.metadata.m.have_animation);
    289  ppf->info.have_animation = TO_JXL_BOOL(io.metadata.m.have_animation);
    290  ppf->info.animation.tps_numerator = io.metadata.m.animation.tps_numerator;
    291  ppf->info.animation.tps_denominator = io.metadata.m.animation.tps_denominator;
    292  ppf->info.animation.num_loops = io.metadata.m.animation.num_loops;
    293 
    294  // Convert the color encoding
    295  ppf->icc.assign(c_desired.ICC().begin(), c_desired.ICC().end());
    296  ppf->primary_color_representation =
    297      c_desired.WantICC() ? PackedPixelFile::kIccIsPrimary
    298                          : PackedPixelFile::kColorEncodingIsPrimary;
    299  ppf->color_encoding = c_desired.ToExternal();
    300 
    301  // Convert the extra blobs
    302  ppf->metadata.exif = io.blobs.exif;
    303  ppf->metadata.iptc = io.blobs.iptc;
    304  ppf->metadata.jhgm = io.blobs.jhgm;
    305  ppf->metadata.jumbf = io.blobs.jumbf;
    306  ppf->metadata.xmp = io.blobs.xmp;
    307  const bool float_out = pixel_format.data_type == JXL_TYPE_FLOAT ||
    308                         pixel_format.data_type == JXL_TYPE_FLOAT16;
    309  // Convert the pixels
    310  ppf->frames.clear();
    311  for (const auto& frame : io.frames) {
    312    JXL_ENSURE(frame.metadata()->bit_depth.bits_per_sample != 0);
    313    // It is ok for the frame.color().kNumPlanes to not match the
    314    // number of channels on the image.
    315    const uint32_t alpha_channels = has_alpha ? 1 : 0;
    316    const uint32_t num_channels =
    317        frame.metadata()->color_encoding.Channels() + alpha_channels;
    318    JxlPixelFormat format{/*num_channels=*/num_channels,
    319                          /*data_type=*/pixel_format.data_type,
    320                          /*endianness=*/pixel_format.endianness,
    321                          /*align=*/pixel_format.align};
    322 
    323    JXL_ASSIGN_OR_RETURN(PackedFrame packed_frame,
    324                         PackedFrame::Create(frame.oriented_xsize(),
    325                                             frame.oriented_ysize(), format));
    326    JXL_RETURN_IF_ERROR(PackedImage::ValidateDataType(pixel_format.data_type));
    327    const size_t bits_per_sample =
    328        float_out ? packed_frame.color.BitsPerChannel(pixel_format.data_type)
    329                  : ppf->info.bits_per_sample;
    330    packed_frame.name = frame.name;
    331    packed_frame.frame_info.name_length = frame.name.size();
    332    // Color transform
    333    JXL_ASSIGN_OR_RETURN(ImageBundle ib, frame.Copy());
    334    const ImageBundle* to_color_transform = &ib;
    335    ImageMetadata metadata = io.metadata.m;
    336    ImageBundle store(memory_manager, &metadata);
    337    const ImageBundle* transformed;
    338    // TODO(firsching): handle the transform here.
    339    JXL_RETURN_IF_ERROR(TransformIfNeeded(*to_color_transform, c_desired,
    340                                          *JxlGetDefaultCms(), pool, &store,
    341                                          &transformed));
    342 
    343    JXL_RETURN_IF_ERROR(ConvertToExternal(
    344        *transformed, bits_per_sample, float_out, format.num_channels,
    345        format.endianness,
    346        /* stride_out=*/packed_frame.color.stride, pool,
    347        packed_frame.color.pixels(), packed_frame.color.pixels_size,
    348        /*out_callback=*/{}, frame.metadata()->GetOrientation()));
    349 
    350    // TODO(firsching): Convert the extra channels, beside one potential alpha
    351    // channel. FIXME!
    352    JXL_ENSURE(frame.extra_channels().size() <= (has_alpha ? 1 : 0));
    353    ppf->frames.push_back(std::move(packed_frame));
    354  }
    355 
    356  return true;
    357 }
    358 }  // namespace extras
    359 }  // namespace jxl