enc_image_bundle.cc (6465B)
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/jxl/enc_image_bundle.h" 7 8 #include <jxl/cms_interface.h> 9 #include <jxl/memory_manager.h> 10 11 #include <utility> 12 13 #include "lib/jxl/base/rect.h" 14 #include "lib/jxl/base/status.h" 15 #include "lib/jxl/color_encoding_internal.h" 16 #include "lib/jxl/image_bundle.h" 17 18 namespace jxl { 19 20 Status ApplyColorTransform(const ColorEncoding& c_current, 21 float intensity_target, const Image3F& color, 22 const ImageF* black, const Rect& rect, 23 const ColorEncoding& c_desired, 24 const JxlCmsInterface& cms, ThreadPool* pool, 25 Image3F* out) { 26 ColorSpaceTransform c_transform(cms); 27 // Changing IsGray is probably a bug. 28 JXL_ENSURE(c_current.IsGray() == c_desired.IsGray()); 29 bool is_gray = c_current.IsGray(); 30 JxlMemoryManager* memory_amanger = color.memory_manager(); 31 if (out->xsize() < rect.xsize() || out->ysize() < rect.ysize()) { 32 JXL_ASSIGN_OR_RETURN( 33 *out, Image3F::Create(memory_amanger, rect.xsize(), rect.ysize())); 34 } else { 35 JXL_RETURN_IF_ERROR(out->ShrinkTo(rect.xsize(), rect.ysize())); 36 } 37 const auto init = [&](const size_t num_threads) -> Status { 38 JXL_RETURN_IF_ERROR(c_transform.Init(c_current, c_desired, intensity_target, 39 rect.xsize(), num_threads)); 40 return true; 41 }; 42 const auto transform_row = [&](const uint32_t y, 43 const size_t thread) -> Status { 44 float* mutable_src_buf = c_transform.BufSrc(thread); 45 const float* src_buf = mutable_src_buf; 46 // Interleave input. 47 if (is_gray) { 48 src_buf = rect.ConstPlaneRow(color, 0, y); 49 } else if (c_current.IsCMYK()) { 50 if (!black) 51 return JXL_FAILURE("Black plane is missing for CMYK transform"); 52 const float* JXL_RESTRICT row_in0 = rect.ConstPlaneRow(color, 0, y); 53 const float* JXL_RESTRICT row_in1 = rect.ConstPlaneRow(color, 1, y); 54 const float* JXL_RESTRICT row_in2 = rect.ConstPlaneRow(color, 2, y); 55 const float* JXL_RESTRICT row_in3 = rect.ConstRow(*black, y); 56 for (size_t x = 0; x < rect.xsize(); x++) { 57 // CMYK convention in JXL: 0 = max ink, 1 = white 58 mutable_src_buf[4 * x + 0] = row_in0[x]; 59 mutable_src_buf[4 * x + 1] = row_in1[x]; 60 mutable_src_buf[4 * x + 2] = row_in2[x]; 61 mutable_src_buf[4 * x + 3] = row_in3[x]; 62 } 63 } else { 64 const float* JXL_RESTRICT row_in0 = rect.ConstPlaneRow(color, 0, y); 65 const float* JXL_RESTRICT row_in1 = rect.ConstPlaneRow(color, 1, y); 66 const float* JXL_RESTRICT row_in2 = rect.ConstPlaneRow(color, 2, y); 67 for (size_t x = 0; x < rect.xsize(); x++) { 68 mutable_src_buf[3 * x + 0] = row_in0[x]; 69 mutable_src_buf[3 * x + 1] = row_in1[x]; 70 mutable_src_buf[3 * x + 2] = row_in2[x]; 71 } 72 } 73 float* JXL_RESTRICT dst_buf = c_transform.BufDst(thread); 74 JXL_RETURN_IF_ERROR( 75 c_transform.Run(thread, src_buf, dst_buf, rect.xsize())); 76 float* JXL_RESTRICT row_out0 = out->PlaneRow(0, y); 77 float* JXL_RESTRICT row_out1 = out->PlaneRow(1, y); 78 float* JXL_RESTRICT row_out2 = out->PlaneRow(2, y); 79 // De-interleave output and convert type. 80 if (is_gray) { 81 for (size_t x = 0; x < rect.xsize(); x++) { 82 row_out0[x] = dst_buf[x]; 83 row_out1[x] = dst_buf[x]; 84 row_out2[x] = dst_buf[x]; 85 } 86 } else { 87 for (size_t x = 0; x < rect.xsize(); x++) { 88 row_out0[x] = dst_buf[3 * x + 0]; 89 row_out1[x] = dst_buf[3 * x + 1]; 90 row_out2[x] = dst_buf[3 * x + 2]; 91 } 92 } 93 return true; 94 }; 95 JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, rect.ysize(), init, transform_row, 96 "Colorspace transform")); 97 return true; 98 } 99 100 namespace { 101 102 // Copies ib:rect, converts, and copies into out. 103 Status CopyToT(const ImageMetadata* metadata, const ImageBundle* ib, 104 const Rect& rect, const ColorEncoding& c_desired, 105 const JxlCmsInterface& cms, ThreadPool* pool, Image3F* out) { 106 return ApplyColorTransform(ib->c_current(), metadata->IntensityTarget(), 107 ib->color(), ib->black(), rect, c_desired, cms, 108 pool, out); 109 } 110 111 } // namespace 112 113 Status ImageBundle::TransformTo(const ColorEncoding& c_desired, 114 const JxlCmsInterface& cms, ThreadPool* pool) { 115 JXL_RETURN_IF_ERROR(CopyTo(Rect(color_), c_desired, cms, &color_, pool)); 116 c_current_ = c_desired; 117 return true; 118 } 119 Status ImageBundle::CopyTo(const Rect& rect, const ColorEncoding& c_desired, 120 const JxlCmsInterface& cms, Image3F* out, 121 ThreadPool* pool) const { 122 return CopyToT(metadata_, this, rect, c_desired, cms, pool, out); 123 } 124 Status TransformIfNeeded(const ImageBundle& in, const ColorEncoding& c_desired, 125 const JxlCmsInterface& cms, ThreadPool* pool, 126 ImageBundle* store, const ImageBundle** out) { 127 if (in.c_current().SameColorEncoding(c_desired) && !in.HasBlack()) { 128 *out = ∈ 129 return true; 130 } 131 JxlMemoryManager* memory_manager = in.memory_manager(); 132 // TODO(janwas): avoid copying via createExternal+copyBackToIO 133 // instead of copy+createExternal+copyBackToIO 134 JXL_ASSIGN_OR_RETURN( 135 Image3F color, 136 Image3F::Create(memory_manager, in.color().xsize(), in.color().ysize())); 137 JXL_RETURN_IF_ERROR(CopyImageTo(in.color(), &color)); 138 JXL_RETURN_IF_ERROR(store->SetFromImage(std::move(color), in.c_current())); 139 140 // Must at least copy the alpha channel for use by external_image. 141 if (in.HasExtraChannels()) { 142 std::vector<ImageF> extra_channels; 143 for (const ImageF& extra_channel : in.extra_channels()) { 144 JXL_ASSIGN_OR_RETURN(ImageF ec, 145 ImageF::Create(memory_manager, extra_channel.xsize(), 146 extra_channel.ysize())); 147 JXL_RETURN_IF_ERROR(CopyImageTo(extra_channel, &ec)); 148 extra_channels.emplace_back(std::move(ec)); 149 } 150 JXL_RETURN_IF_ERROR(store->SetExtraChannels(std::move(extra_channels))); 151 } 152 153 if (!store->TransformTo(c_desired, cms, pool)) { 154 return false; 155 } 156 *out = store; 157 return true; 158 } 159 160 } // namespace jxl