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