Coverage Report

Created: 2025-06-16 07:00

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