Coverage Report

Created: 2026-02-14 07:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libjxl/lib/jxl/enc_image_bundle.cc
Line
Count
Source
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
1.86k
                           Image3F* out) {
34
1.86k
  ColorSpaceTransform c_transform(cms);
35
  // Changing IsGray is probably a bug.
36
1.86k
  JXL_ENSURE(c_current.IsGray() == c_desired.IsGray());
37
1.86k
  bool is_gray = c_current.IsGray();
38
1.86k
  JxlMemoryManager* memory_amanger = color.memory_manager();
39
1.86k
  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
1.86k
  } else {
43
1.86k
    JXL_RETURN_IF_ERROR(out->ShrinkTo(rect.xsize(), rect.ysize()));
44
1.86k
  }
45
1.86k
  const auto init = [&](const size_t num_threads) -> Status {
46
1.86k
    JXL_RETURN_IF_ERROR(c_transform.Init(c_current, c_desired, intensity_target,
47
1.86k
                                         rect.xsize(), num_threads));
48
1.86k
    return true;
49
1.86k
  };
50
1.86k
  const auto transform_row = [&](const uint32_t y,
51
1.09M
                                 const size_t thread) -> Status {
52
1.09M
    float* mutable_src_buf = c_transform.BufSrc(thread);
53
1.09M
    const float* src_buf = mutable_src_buf;
54
    // Interleave input.
55
1.09M
    if (is_gray) {
56
117k
      src_buf = rect.ConstPlaneRow(color, 0, y);
57
981k
    } 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
981k
    } else {
72
981k
      const float* JXL_RESTRICT row_in0 = rect.ConstPlaneRow(color, 0, y);
73
981k
      const float* JXL_RESTRICT row_in1 = rect.ConstPlaneRow(color, 1, y);
74
981k
      const float* JXL_RESTRICT row_in2 = rect.ConstPlaneRow(color, 2, y);
75
214M
      for (size_t x = 0; x < rect.xsize(); x++) {
76
213M
        mutable_src_buf[3 * x + 0] = row_in0[x];
77
213M
        mutable_src_buf[3 * x + 1] = row_in1[x];
78
213M
        mutable_src_buf[3 * x + 2] = row_in2[x];
79
213M
      }
80
981k
    }
81
1.09M
    float* JXL_RESTRICT dst_buf = c_transform.BufDst(thread);
82
1.09M
    JXL_RETURN_IF_ERROR(
83
1.09M
        c_transform.Run(thread, src_buf, dst_buf, rect.xsize()));
84
1.09M
    float* JXL_RESTRICT row_out0 = out->PlaneRow(0, y);
85
1.09M
    float* JXL_RESTRICT row_out1 = out->PlaneRow(1, y);
86
1.09M
    float* JXL_RESTRICT row_out2 = out->PlaneRow(2, y);
87
    // De-interleave output and convert type.
88
1.09M
    if (is_gray) {
89
1.98M
      for (size_t x = 0; x < rect.xsize(); x++) {
90
1.87M
        row_out0[x] = dst_buf[x];
91
1.87M
        row_out1[x] = dst_buf[x];
92
1.87M
        row_out2[x] = dst_buf[x];
93
1.87M
      }
94
981k
    } else {
95
214M
      for (size_t x = 0; x < rect.xsize(); x++) {
96
213M
        row_out0[x] = dst_buf[3 * x + 0];
97
213M
        row_out1[x] = dst_buf[3 * x + 1];
98
213M
        row_out2[x] = dst_buf[3 * x + 2];
99
213M
      }
100
981k
    }
101
1.09M
    return true;
102
1.09M
  };
103
1.86k
  JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, rect.ysize(), init, transform_row,
104
1.86k
                                "Colorspace transform"));
105
1.86k
  return true;
106
1.86k
}
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
664
               const JxlCmsInterface& cms, ThreadPool* pool, Image3F* out) {
114
664
  return ApplyColorTransform(ib->c_current(), metadata->IntensityTarget(),
115
664
                             ib->color(), ib->black(), rect, c_desired, cms,
116
664
                             pool, out);
117
664
}
118
119
}  // namespace
120
121
Status ImageBundle::TransformTo(const ColorEncoding& c_desired,
122
664
                                const JxlCmsInterface& cms, ThreadPool* pool) {
123
664
  JXL_RETURN_IF_ERROR(CopyTo(Rect(color_), c_desired, cms, &color_, pool));
124
664
  c_current_ = c_desired;
125
664
  return true;
126
664
}
127
Status ImageBundle::CopyTo(const Rect& rect, const ColorEncoding& c_desired,
128
                           const JxlCmsInterface& cms, Image3F* out,
129
664
                           ThreadPool* pool) const {
130
664
  return CopyToT(metadata_, this, rect, c_desired, cms, pool, out);
131
664
}
132
Status TransformIfNeeded(const ImageBundle& in, const ColorEncoding& c_desired,
133
                         const JxlCmsInterface& cms, ThreadPool* pool,
134
664
                         ImageBundle* store, const ImageBundle** out) {
135
664
  if (in.c_current().SameColorEncoding(c_desired) && !in.HasBlack()) {
136
0
    *out = &in;
137
0
    return true;
138
0
  }
139
664
  JxlMemoryManager* memory_manager = in.memory_manager();
140
  // TODO(janwas): avoid copying via createExternal+copyBackToIO
141
  // instead of copy+createExternal+copyBackToIO
142
664
  JXL_ASSIGN_OR_RETURN(
143
664
      Image3F color,
144
664
      Image3F::Create(memory_manager, in.color().xsize(), in.color().ysize()));
145
664
  JXL_RETURN_IF_ERROR(CopyImageTo(in.color(), &color));
146
664
  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
664
  if (in.HasExtraChannels()) {
150
112
    std::vector<ImageF> extra_channels;
151
112
    for (const ImageF& extra_channel : in.extra_channels()) {
152
112
      JXL_ASSIGN_OR_RETURN(ImageF ec,
153
112
                           ImageF::Create(memory_manager, extra_channel.xsize(),
154
112
                                          extra_channel.ysize()));
155
112
      JXL_RETURN_IF_ERROR(CopyImageTo(extra_channel, &ec));
156
112
      extra_channels.emplace_back(std::move(ec));
157
112
    }
158
112
    JXL_RETURN_IF_ERROR(store->SetExtraChannels(std::move(extra_channels)));
159
112
  }
160
161
664
  if (!store->TransformTo(c_desired, cms, pool)) {
162
0
    return false;
163
0
  }
164
664
  *out = store;
165
664
  return true;
166
664
}
167
168
}  // namespace jxl