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