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 <limits>
9 #include <utility>
10
11 #include "lib/jxl/alpha.h"
12 #include "lib/jxl/base/byte_order.h"
13 #include "lib/jxl/base/padded_bytes.h"
14 #include "lib/jxl/base/profiler.h"
15 #include "lib/jxl/codec_in_out.h"
16 #include "lib/jxl/enc_color_management.h"
17 #include "lib/jxl/fields.h"
18 #include "lib/jxl/image_bundle.h"
19 #include "lib/jxl/luminance.h"
20
21 namespace jxl {
22
23 namespace {
24
25 // Copies ib:rect, converts, and copies into out.
26 template <typename T>
CopyToT(const ImageMetadata * metadata,const ImageBundle * ib,const Rect & rect,const ColorEncoding & c_desired,ThreadPool * pool,Image3<T> * out)27 Status CopyToT(const ImageMetadata* metadata, const ImageBundle* ib,
28 const Rect& rect, const ColorEncoding& c_desired,
29 ThreadPool* pool, Image3<T>* out) {
30 PROFILER_FUNC;
31 static_assert(
32 std::is_same<T, float>::value || std::numeric_limits<T>::min() == 0,
33 "CopyToT implemented only for float and unsigned types");
34 ColorSpaceTransform c_transform;
35 // Changing IsGray is probably a bug.
36 JXL_CHECK(ib->IsGray() == c_desired.IsGray());
37 #if JPEGXL_ENABLE_SKCMS
38 bool is_gray = false;
39 #else
40 bool is_gray = ib->IsGray();
41 #endif
42 if (out->xsize() < rect.xsize() || out->ysize() < rect.ysize()) {
43 *out = Image3<T>(rect.xsize(), rect.ysize());
44 } else {
45 out->ShrinkTo(rect.xsize(), rect.ysize());
46 }
47 RunOnPool(
48 pool, 0, rect.ysize(),
49 [&](size_t num_threads) {
50 return c_transform.Init(ib->c_current(), c_desired,
51 metadata->IntensityTarget(), rect.xsize(),
52 num_threads);
53 },
54 [&](const int y, const int thread) {
55 float* mutable_src_buf = c_transform.BufSrc(thread);
56 const float* src_buf = mutable_src_buf;
57 // Interleave input.
58 if (is_gray) {
59 src_buf = rect.ConstPlaneRow(ib->color(), 0, y);
60 } else {
61 const float* JXL_RESTRICT row_in0 =
62 rect.ConstPlaneRow(ib->color(), 0, y);
63 const float* JXL_RESTRICT row_in1 =
64 rect.ConstPlaneRow(ib->color(), 1, y);
65 const float* JXL_RESTRICT row_in2 =
66 rect.ConstPlaneRow(ib->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 DoColorSpaceTransform(&c_transform, thread, src_buf, dst_buf);
75 T* JXL_RESTRICT row_out0 = out->PlaneRow(0, y);
76 T* JXL_RESTRICT row_out1 = out->PlaneRow(1, y);
77 T* JXL_RESTRICT row_out2 = out->PlaneRow(2, y);
78 // De-interleave output and convert type.
79 if (std::is_same<float, T>::value) { // deinterleave to float.
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 } else {
94 // Convert to T, doing clamping.
95 float max = std::numeric_limits<T>::max();
96 auto cvt = [max](float in) {
97 float v = std::max(0.0f, std::min(max, in * max));
98 return static_cast<T>(v < 0 ? v - 0.5f : v + 0.5f);
99 };
100 if (is_gray) {
101 for (size_t x = 0; x < rect.xsize(); x++) {
102 row_out0[x] = cvt(dst_buf[x]);
103 row_out1[x] = cvt(dst_buf[x]);
104 row_out2[x] = cvt(dst_buf[x]);
105 }
106 } else {
107 for (size_t x = 0; x < rect.xsize(); x++) {
108 row_out0[x] = cvt(dst_buf[3 * x + 0]);
109 row_out1[x] = cvt(dst_buf[3 * x + 1]);
110 row_out2[x] = cvt(dst_buf[3 * x + 2]);
111 }
112 }
113 }
114 },
115 "Colorspace transform");
116 return true;
117 }
118
119 } // namespace
120
TransformTo(const ColorEncoding & c_desired,ThreadPool * pool)121 Status ImageBundle::TransformTo(const ColorEncoding& c_desired,
122 ThreadPool* pool) {
123 PROFILER_FUNC;
124 JXL_RETURN_IF_ERROR(CopyTo(Rect(color_), c_desired, &color_, pool));
125 c_current_ = c_desired;
126 return true;
127 }
128
CopyTo(const Rect & rect,const ColorEncoding & c_desired,Image3B * out,ThreadPool * pool) const129 Status ImageBundle::CopyTo(const Rect& rect, const ColorEncoding& c_desired,
130 Image3B* out, ThreadPool* pool) const {
131 return CopyToT(metadata_, this, rect, c_desired, pool, out);
132 }
CopyTo(const Rect & rect,const ColorEncoding & c_desired,Image3F * out,ThreadPool * pool) const133 Status ImageBundle::CopyTo(const Rect& rect, const ColorEncoding& c_desired,
134 Image3F* out, ThreadPool* pool) const {
135 return CopyToT(metadata_, this, rect, c_desired, pool, out);
136 }
137
CopyToSRGB(const Rect & rect,Image3B * out,ThreadPool * pool) const138 Status ImageBundle::CopyToSRGB(const Rect& rect, Image3B* out,
139 ThreadPool* pool) const {
140 return CopyTo(rect, ColorEncoding::SRGB(IsGray()), out, pool);
141 }
142
TransformIfNeeded(const ImageBundle & in,const ColorEncoding & c_desired,ThreadPool * pool,ImageBundle * store,const ImageBundle ** out)143 Status TransformIfNeeded(const ImageBundle& in, const ColorEncoding& c_desired,
144 ThreadPool* pool, ImageBundle* store,
145 const ImageBundle** out) {
146 if (in.c_current().SameColorEncoding(c_desired)) {
147 *out = ∈
148 return true;
149 }
150 // TODO(janwas): avoid copying via createExternal+copyBackToIO
151 // instead of copy+createExternal+copyBackToIO
152 store->SetFromImage(CopyImage(in.color()), in.c_current());
153
154 // Must at least copy the alpha channel for use by external_image.
155 if (in.HasExtraChannels()) {
156 std::vector<ImageF> extra_channels;
157 for (const ImageF& extra_channel : in.extra_channels()) {
158 extra_channels.emplace_back(CopyImage(extra_channel));
159 }
160 store->SetExtraChannels(std::move(extra_channels));
161 }
162
163 if (!store->TransformTo(c_desired, pool)) {
164 return false;
165 }
166 *out = store;
167 return true;
168 }
169
170 } // namespace jxl
171