libjxl

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

packed_image_convert.cc (14788B)


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