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