Coverage Report

Created: 2025-06-22 08:04

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