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 = &in;
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