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