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