Coverage Report

Created: 2026-05-16 07:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libjxl/lib/jxl/dec_external_image.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/dec_external_image.h"
7
8
#include <jxl/memory_manager.h>
9
#include <jxl/types.h>
10
11
#include <algorithm>
12
#include <cstdint>
13
#include <cstring>
14
#include <memory>
15
#include <utility>
16
#include <vector>
17
18
#include "lib/jxl/base/data_parallel.h"
19
#include "lib/jxl/base/status.h"
20
#include "lib/jxl/dec_cache.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
#undef HWY_TARGET_INCLUDE
27
#define HWY_TARGET_INCLUDE "lib/jxl/dec_external_image.cc"
28
#include <hwy/foreach_target.h>
29
#include <hwy/highway.h>
30
31
#include "lib/jxl/alpha.h"
32
#include "lib/jxl/base/byte_order.h"
33
#include "lib/jxl/base/common.h"
34
#include "lib/jxl/base/compiler_specific.h"
35
#include "lib/jxl/base/printf_macros.h"
36
#include "lib/jxl/base/sanitizers.h"
37
38
HWY_BEFORE_NAMESPACE();
39
namespace jxl {
40
namespace HWY_NAMESPACE {
41
42
// These templates are not found via ADL.
43
using hwy::HWY_NAMESPACE::Clamp;
44
using hwy::HWY_NAMESPACE::Mul;
45
using hwy::HWY_NAMESPACE::NearestInt;
46
47
// TODO(jon): check if this can be replaced by a FloatToU16 function
48
void FloatToU32(const float* in, uint32_t* out, size_t num, float mul,
49
0
                size_t bits_per_sample) {
50
0
  const HWY_FULL(float) d;
51
0
  const hwy::HWY_NAMESPACE::Rebind<uint32_t, decltype(d)> du;
52
53
  // Unpoison accessing partially-uninitialized vectors with memory sanitizer.
54
  // This is because we run NearestInt() on the vector, which triggers MSAN even
55
  // it is safe to do so since the values are not mixed between lanes.
56
0
  const size_t num_round_up = RoundUpTo(num, Lanes(d));
57
0
  msan::UnpoisonMemory(in + num, sizeof(in[0]) * (num_round_up - num));
58
59
0
  const auto one = Set(d, 1.0f);
60
0
  const auto scale = Set(d, mul);
61
0
  for (size_t x = 0; x < num; x += Lanes(d)) {
62
0
    auto v = Load(d, in + x);
63
    // Clamp turns NaN to 'min'.
64
0
    v = Clamp(v, Zero(d), one);
65
0
    auto i = NearestInt(Mul(v, scale));
66
0
    Store(BitCast(du, i), du, out + x);
67
0
  }
68
69
  // Poison back the output.
70
0
  msan::PoisonMemory(out + num, sizeof(out[0]) * (num_round_up - num));
71
0
}
Unexecuted instantiation: jxl::N_SSE4::FloatToU32(float const*, unsigned int*, unsigned long, float, unsigned long)
Unexecuted instantiation: jxl::N_AVX2::FloatToU32(float const*, unsigned int*, unsigned long, float, unsigned long)
Unexecuted instantiation: jxl::N_SSE2::FloatToU32(float const*, unsigned int*, unsigned long, float, unsigned long)
72
73
0
void FloatToF16(const float* in, hwy::float16_t* out, size_t num) {
74
0
  const HWY_FULL(float) d;
75
0
  const hwy::HWY_NAMESPACE::Rebind<hwy::float16_t, decltype(d)> du;
76
77
  // Unpoison accessing partially-uninitialized vectors with memory sanitizer.
78
  // This is because we run DemoteTo() on the vector which triggers msan.
79
0
  const size_t num_round_up = RoundUpTo(num, Lanes(d));
80
0
  msan::UnpoisonMemory(in + num, sizeof(in[0]) * (num_round_up - num));
81
82
0
  for (size_t x = 0; x < num; x += Lanes(d)) {
83
0
    auto v = Load(d, in + x);
84
0
    auto v16 = DemoteTo(du, v);
85
0
    Store(v16, du, out + x);
86
0
  }
87
88
  // Poison back the output.
89
0
  msan::PoisonMemory(out + num, sizeof(out[0]) * (num_round_up - num));
90
0
}
Unexecuted instantiation: jxl::N_SSE4::FloatToF16(float const*, hwy::float16_t*, unsigned long)
Unexecuted instantiation: jxl::N_AVX2::FloatToF16(float const*, hwy::float16_t*, unsigned long)
Unexecuted instantiation: jxl::N_SSE2::FloatToF16(float const*, hwy::float16_t*, unsigned long)
91
92
// NOLINTNEXTLINE(google-readability-namespace-comments)
93
}  // namespace HWY_NAMESPACE
94
}  // namespace jxl
95
HWY_AFTER_NAMESPACE();
96
97
#if HWY_ONCE
98
99
namespace jxl {
100
namespace {
101
102
// Stores a float in big endian
103
0
void StoreBEFloat(float value, uint8_t* p) {
104
0
  uint32_t u;
105
0
  memcpy(&u, &value, 4);
106
0
  StoreBE32(u, p);
107
0
}
108
109
// Stores a float in little endian
110
12.2M
void StoreLEFloat(float value, uint8_t* p) {
111
12.2M
  uint32_t u;
112
12.2M
  memcpy(&u, &value, 4);
113
12.2M
  StoreLE32(u, p);
114
12.2M
}
115
116
// The orientation may not be identity.
117
// TODO(lode): SIMDify where possible
118
template <typename T>
119
Status UndoOrientation(jxl::Orientation undo_orientation, const Plane<T>& image,
120
0
                       Plane<T>& out, jxl::ThreadPool* pool) {
121
0
  const size_t xsize = image.xsize();
122
0
  const size_t ysize = image.ysize();
123
0
  JxlMemoryManager* memory_manager = image.memory_manager();
124
125
0
  if (undo_orientation == Orientation::kFlipHorizontal) {
126
0
    JXL_ASSIGN_OR_RETURN(out, Plane<T>::Create(memory_manager, xsize, ysize));
127
0
    const auto process_row = [&](const uint32_t task,
128
0
                                 size_t /*thread*/) -> Status {
129
0
      const int64_t y = task;
130
0
      const T* JXL_RESTRICT row_in = image.Row(y);
131
0
      T* JXL_RESTRICT row_out = out.Row(y);
132
0
      for (size_t x = 0; x < xsize; ++x) {
133
0
        row_out[xsize - x - 1] = row_in[x];
134
0
      }
135
0
      return true;
136
0
    };
137
0
    JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, static_cast<uint32_t>(ysize),
138
0
                                  ThreadPool::NoInit, process_row,
139
0
                                  "UndoOrientation"));
140
0
  } else if (undo_orientation == Orientation::kRotate180) {
141
0
    JXL_ASSIGN_OR_RETURN(out, Plane<T>::Create(memory_manager, xsize, ysize));
142
0
    const auto process_row = [&](const uint32_t task,
143
0
                                 size_t /*thread*/) -> Status {
144
0
      const int64_t y = task;
145
0
      const T* JXL_RESTRICT row_in = image.Row(y);
146
0
      T* JXL_RESTRICT row_out = out.Row(ysize - y - 1);
147
0
      for (size_t x = 0; x < xsize; ++x) {
148
0
        row_out[xsize - x - 1] = row_in[x];
149
0
      }
150
0
      return true;
151
0
    };
152
0
    JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, static_cast<uint32_t>(ysize),
153
0
                                  ThreadPool::NoInit, process_row,
154
0
                                  "UndoOrientation"));
155
0
  } else if (undo_orientation == Orientation::kFlipVertical) {
156
0
    JXL_ASSIGN_OR_RETURN(out, Plane<T>::Create(memory_manager, xsize, ysize));
157
0
    const auto process_row = [&](const uint32_t task,
158
0
                                 size_t /*thread*/) -> Status {
159
0
      const int64_t y = task;
160
0
      const T* JXL_RESTRICT row_in = image.Row(y);
161
0
      T* JXL_RESTRICT row_out = out.Row(ysize - y - 1);
162
0
      for (size_t x = 0; x < xsize; ++x) {
163
0
        row_out[x] = row_in[x];
164
0
      }
165
0
      return true;
166
0
    };
167
0
    JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, static_cast<uint32_t>(ysize),
168
0
                                  ThreadPool::NoInit, process_row,
169
0
                                  "UndoOrientation"));
170
0
  } else if (undo_orientation == Orientation::kTranspose) {
171
0
    JXL_ASSIGN_OR_RETURN(out, Plane<T>::Create(memory_manager, ysize, xsize));
172
0
    const auto process_row = [&](const uint32_t task,
173
0
                                 size_t /*thread*/) -> Status {
174
0
      const int64_t y = task;
175
0
      const T* JXL_RESTRICT row_in = image.Row(y);
176
0
      for (size_t x = 0; x < xsize; ++x) {
177
0
        out.Row(x)[y] = row_in[x];
178
0
      }
179
0
      return true;
180
0
    };
181
0
    JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, static_cast<uint32_t>(ysize),
182
0
                                  ThreadPool::NoInit, process_row,
183
0
                                  "UndoOrientation"));
184
0
  } else if (undo_orientation == Orientation::kRotate90) {
185
0
    JXL_ASSIGN_OR_RETURN(out, Plane<T>::Create(memory_manager, ysize, xsize));
186
0
    const auto process_row = [&](const uint32_t task,
187
0
                                 size_t /*thread*/) -> Status {
188
0
      const int64_t y = task;
189
0
      const T* JXL_RESTRICT row_in = image.Row(y);
190
0
      for (size_t x = 0; x < xsize; ++x) {
191
0
        out.Row(x)[ysize - y - 1] = row_in[x];
192
0
      }
193
0
      return true;
194
0
    };
195
0
    JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, static_cast<uint32_t>(ysize),
196
0
                                  ThreadPool::NoInit, process_row,
197
0
                                  "UndoOrientation"));
198
0
  } else if (undo_orientation == Orientation::kAntiTranspose) {
199
0
    JXL_ASSIGN_OR_RETURN(out, Plane<T>::Create(memory_manager, ysize, xsize));
200
0
    const auto process_row = [&](const uint32_t task,
201
0
                                 size_t /*thread*/) -> Status {
202
0
      const int64_t y = task;
203
0
      const T* JXL_RESTRICT row_in = image.Row(y);
204
0
      for (size_t x = 0; x < xsize; ++x) {
205
0
        out.Row(xsize - x - 1)[ysize - y - 1] = row_in[x];
206
0
      }
207
0
      return true;
208
0
    };
209
0
    JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, static_cast<uint32_t>(ysize),
210
0
                                  ThreadPool::NoInit, process_row,
211
0
                                  "UndoOrientation"));
212
0
  } else if (undo_orientation == Orientation::kRotate270) {
213
0
    JXL_ASSIGN_OR_RETURN(out, Plane<T>::Create(memory_manager, ysize, xsize));
214
0
    const auto process_row = [&](const uint32_t task,
215
0
                                 size_t /*thread*/) -> Status {
216
0
      const int64_t y = task;
217
0
      const T* JXL_RESTRICT row_in = image.Row(y);
218
0
      for (size_t x = 0; x < xsize; ++x) {
219
0
        out.Row(xsize - x - 1)[y] = row_in[x];
220
0
      }
221
0
      return true;
222
0
    };
223
0
    JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, static_cast<uint32_t>(ysize),
224
0
                                  ThreadPool::NoInit, process_row,
225
0
                                  "UndoOrientation"));
226
0
  }
227
0
  return true;
228
0
}
229
}  // namespace
230
231
HWY_EXPORT(FloatToU32);
232
HWY_EXPORT(FloatToF16);
233
234
namespace {
235
236
using StoreFuncType = void(uint32_t value, uint8_t* dest);
237
template <StoreFuncType StoreFunc>
238
void StoreUintRow(uint32_t* JXL_RESTRICT* rows_u32, size_t num_channels,
239
                  size_t xsize, size_t bytes_per_sample,
240
0
                  uint8_t* JXL_RESTRICT out) {
241
0
  for (size_t x = 0; x < xsize; ++x) {
242
0
    for (size_t c = 0; c < num_channels; c++) {
243
0
      StoreFunc(rows_u32[c][x],
244
0
                out + (num_channels * x + c) * bytes_per_sample);
245
0
    }
246
0
  }
247
0
}
Unexecuted instantiation: dec_external_image.cc:void jxl::(anonymous namespace)::StoreUintRow<&jxl::(anonymous namespace)::Store8>(unsigned int* restrict*, unsigned long, unsigned long, unsigned long, unsigned char*)
Unexecuted instantiation: dec_external_image.cc:void jxl::(anonymous namespace)::StoreUintRow<&(StoreLE16(unsigned int, unsigned char*))>(unsigned int* restrict*, unsigned long, unsigned long, unsigned long, unsigned char*)
Unexecuted instantiation: dec_external_image.cc:void jxl::(anonymous namespace)::StoreUintRow<&(StoreBE16(unsigned int, unsigned char*))>(unsigned int* restrict*, unsigned long, unsigned long, unsigned long, unsigned char*)
248
249
template <void(StoreFunc)(float, uint8_t*)>
250
void StoreFloatRow(const float* JXL_RESTRICT* rows_in, size_t num_channels,
251
58.7k
                   size_t xsize, uint8_t* JXL_RESTRICT out) {
252
4.95M
  for (size_t x = 0; x < xsize; ++x) {
253
17.1M
    for (size_t c = 0; c < num_channels; c++) {
254
12.2M
      StoreFunc(rows_in[c][x], out + (num_channels * x + c) * sizeof(float));
255
12.2M
    }
256
4.89M
  }
257
58.7k
}
dec_external_image.cc:void jxl::(anonymous namespace)::StoreFloatRow<&jxl::(anonymous namespace)::StoreLEFloat>(float const* restrict*, unsigned long, unsigned long, unsigned char*)
Line
Count
Source
251
58.7k
                   size_t xsize, uint8_t* JXL_RESTRICT out) {
252
4.95M
  for (size_t x = 0; x < xsize; ++x) {
253
17.1M
    for (size_t c = 0; c < num_channels; c++) {
254
12.2M
      StoreFunc(rows_in[c][x], out + (num_channels * x + c) * sizeof(float));
255
12.2M
    }
256
4.89M
  }
257
58.7k
}
Unexecuted instantiation: dec_external_image.cc:void jxl::(anonymous namespace)::StoreFloatRow<&jxl::(anonymous namespace)::StoreBEFloat>(float const* restrict*, unsigned long, unsigned long, unsigned char*)
258
259
0
void JXL_INLINE Store8(uint32_t value, uint8_t* dest) { *dest = value & 0xff; }
260
261
}  // namespace
262
263
Status ConvertChannelsToExternal(const ImageF* in_channels[],
264
                                 size_t num_channels, size_t bits_per_sample,
265
                                 bool float_out, JxlEndianness endianness,
266
                                 size_t stride, jxl::ThreadPool* pool,
267
                                 void* out_image, size_t out_size,
268
                                 const PixelCallback& out_callback,
269
1.15k
                                 jxl::Orientation undo_orientation) {
270
1.15k
  JXL_ENSURE(num_channels != 0 && num_channels <= kConvertMaxChannels);
271
1.15k
  JXL_ENSURE(in_channels[0] != nullptr);
272
1.15k
  JxlMemoryManager* memory_manager = in_channels[0]->memory_manager();
273
1.15k
  JXL_ENSURE(float_out ? bits_per_sample == 16 || bits_per_sample == 32
274
1.15k
                       : bits_per_sample > 0 && bits_per_sample <= 16);
275
1.15k
  const bool has_out_image = (out_image != nullptr);
276
1.15k
  if (has_out_image == out_callback.IsPresent()) {
277
0
    return JXL_FAILURE(
278
0
        "Must provide either an out_image or an out_callback, but not both.");
279
0
  }
280
1.15k
  std::vector<const ImageF*> channels;
281
1.15k
  channels.assign(in_channels, in_channels + num_channels);
282
283
1.15k
  const size_t bytes_per_channel = DivCeil(bits_per_sample, jxl::kBitsPerByte);
284
1.15k
  const size_t bytes_per_pixel = num_channels * bytes_per_channel;
285
286
1.15k
  std::vector<std::vector<uint8_t>> row_out_callback;
287
1.15k
  const auto FreeCallbackOpaque = [&out_callback](void* p) {
288
0
    out_callback.destroy(p);
289
0
  };
290
1.15k
  std::unique_ptr<void, decltype(FreeCallbackOpaque)> out_run_opaque(
291
1.15k
      nullptr, FreeCallbackOpaque);
292
1.15k
  auto InitOutCallback = [&](size_t num_threads) -> Status {
293
1.15k
    if (out_callback.IsPresent()) {
294
0
      out_run_opaque.reset(out_callback.Init(num_threads, stride));
295
0
      JXL_RETURN_IF_ERROR(out_run_opaque != nullptr);
296
0
      row_out_callback.resize(num_threads);
297
0
      for (size_t i = 0; i < num_threads; ++i) {
298
0
        row_out_callback[i].resize(stride);
299
0
      }
300
0
    }
301
1.15k
    return true;
302
1.15k
  };
303
304
  // Channels used to store the transformed original channels if needed.
305
1.15k
  ImageF temp_channels[kConvertMaxChannels];
306
1.15k
  if (undo_orientation != Orientation::kIdentity) {
307
0
    for (size_t c = 0; c < num_channels; ++c) {
308
0
      if (channels[c]) {
309
0
        JXL_RETURN_IF_ERROR(UndoOrientation(undo_orientation, *channels[c],
310
0
                                            temp_channels[c], pool));
311
0
        channels[c] = &(temp_channels[c]);
312
0
      }
313
0
    }
314
0
  }
315
316
  // First channel may not be nullptr.
317
1.15k
  size_t xsize = channels[0]->xsize();
318
1.15k
  size_t ysize = channels[0]->ysize();
319
1.15k
  size_t row_size;
320
1.15k
  if (!SafeMul(bytes_per_pixel, xsize, row_size) || stride < row_size) {
321
0
    return JXL_FAILURE("stride is smaller than scanline width in bytes: %" PRIuS
322
0
                       " vs %" PRIuS,
323
0
                       stride, row_size);
324
0
  }
325
1.15k
  if (!out_callback.IsPresent()) {
326
1.15k
    size_t total_size;
327
1.15k
    if (!SafeMul(ysize - 1, stride, total_size) ||
328
1.15k
        !SafeAdd(total_size, row_size, total_size) || out_size < total_size) {
329
0
      return JXL_FAILURE("out_size is too small to store image");
330
0
    }
331
1.15k
  }
332
333
1.15k
  const bool little_endian =
334
1.15k
      endianness == JXL_LITTLE_ENDIAN ||
335
1.15k
      (endianness == JXL_NATIVE_ENDIAN && IsLittleEndian());
336
337
  // Handle the case where a channel is nullptr by creating a single row with
338
  // ones to use instead.
339
1.15k
  ImageF ones;
340
3.94k
  for (size_t c = 0; c < num_channels; ++c) {
341
2.79k
    if (!channels[c]) {
342
0
      JXL_ASSIGN_OR_RETURN(ones, ImageF::Create(memory_manager, xsize, 1));
343
0
      FillImage(1.0f, &ones);
344
0
      break;
345
0
    }
346
2.79k
  }
347
348
1.15k
  if (float_out) {
349
1.15k
    if (bits_per_sample == 16) {
350
0
      bool swap_endianness = little_endian != IsLittleEndian();
351
0
      Plane<hwy::float16_t> f16_cache;
352
0
      const auto init_cache = [&](size_t num_threads) -> Status {
353
0
        JXL_ASSIGN_OR_RETURN(
354
0
            f16_cache, Plane<hwy::float16_t>::Create(
355
0
                           memory_manager, xsize, num_channels * num_threads));
356
0
        JXL_RETURN_IF_ERROR(InitOutCallback(num_threads));
357
0
        return true;
358
0
      };
359
0
      const auto process_row = [&](const uint32_t task,
360
0
                                   const size_t thread) -> Status {
361
0
        const int64_t y = task;
362
0
        const float* JXL_RESTRICT row_in[kConvertMaxChannels];
363
0
        for (size_t c = 0; c < num_channels; c++) {
364
0
          row_in[c] = channels[c] ? channels[c]->Row(y) : ones.Row(0);
365
0
        }
366
0
        hwy::float16_t* JXL_RESTRICT row_f16[kConvertMaxChannels];
367
0
        for (size_t c = 0; c < num_channels; c++) {
368
0
          row_f16[c] = f16_cache.Row(c + thread * num_channels);
369
0
          HWY_DYNAMIC_DISPATCH(FloatToF16)
370
0
          (row_in[c], row_f16[c], xsize);
371
0
        }
372
0
        uint8_t* row_out =
373
0
            out_callback.IsPresent()
374
0
                ? row_out_callback[thread].data()
375
0
                : &(reinterpret_cast<uint8_t*>(out_image))[stride * y];
376
        // interleave the one scanline
377
0
        hwy::float16_t* row_f16_out =
378
0
            reinterpret_cast<hwy::float16_t*>(row_out);
379
0
        for (size_t x = 0; x < xsize; x++) {
380
0
          for (size_t c = 0; c < num_channels; c++) {
381
0
            row_f16_out[x * num_channels + c] = row_f16[c][x];
382
0
          }
383
0
        }
384
0
        if (swap_endianness) {
385
0
          size_t size = xsize * num_channels * 2;
386
0
          for (size_t i = 0; i < size; i += 2) {
387
0
            std::swap(row_out[i + 0], row_out[i + 1]);
388
0
          }
389
0
        }
390
0
        if (out_callback.IsPresent()) {
391
0
          out_callback.run(out_run_opaque.get(), thread, 0, y, xsize, row_out);
392
0
        }
393
0
        return true;
394
0
      };
395
0
      JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, static_cast<uint32_t>(ysize),
396
0
                                    init_cache, process_row, "ConvertF16"));
397
1.15k
    } else if (bits_per_sample == 32) {
398
1.15k
      const auto init_cache = [&](size_t num_threads) -> Status {
399
1.15k
        JXL_RETURN_IF_ERROR(InitOutCallback(num_threads));
400
1.15k
        return true;
401
1.15k
      };
402
1.15k
      const auto process_row = [&](const uint32_t task,
403
58.7k
                                   const size_t thread) -> Status {
404
58.7k
        const int64_t y = task;
405
58.7k
        uint8_t* row_out =
406
58.7k
            out_callback.IsPresent()
407
58.7k
                ? row_out_callback[thread].data()
408
58.7k
                : &(reinterpret_cast<uint8_t*>(out_image))[stride * y];
409
58.7k
        const float* JXL_RESTRICT row_in[kConvertMaxChannels];
410
202k
        for (size_t c = 0; c < num_channels; c++) {
411
143k
          row_in[c] = channels[c] ? channels[c]->Row(y) : ones.Row(0);
412
143k
        }
413
58.7k
        if (little_endian) {
414
58.7k
          StoreFloatRow<StoreLEFloat>(row_in, num_channels, xsize, row_out);
415
58.7k
        } else {
416
0
          StoreFloatRow<StoreBEFloat>(row_in, num_channels, xsize, row_out);
417
0
        }
418
58.7k
        if (out_callback.IsPresent()) {
419
0
          out_callback.run(out_run_opaque.get(), thread, 0, y, xsize, row_out);
420
0
        }
421
58.7k
        return true;
422
58.7k
      };
423
1.15k
      JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, static_cast<uint32_t>(ysize),
424
1.15k
                                    init_cache, process_row, "ConvertFloat"));
425
1.15k
    } else {
426
0
      return JXL_FAILURE("float other than 16-bit and 32-bit not supported");
427
0
    }
428
1.15k
  } else {
429
    // Multiplier to convert from floating point 0-1 range to the integer
430
    // range.
431
0
    float mul = (1ull << bits_per_sample) - 1;
432
0
    Plane<uint32_t> u32_cache;
433
0
    const auto init_cache = [&](size_t num_threads) -> Status {
434
0
      JXL_ASSIGN_OR_RETURN(u32_cache,
435
0
                           Plane<uint32_t>::Create(memory_manager, xsize,
436
0
                                                   num_channels * num_threads));
437
0
      JXL_RETURN_IF_ERROR(InitOutCallback(num_threads));
438
0
      return true;
439
0
    };
440
0
    const auto process_row = [&](const uint32_t task,
441
0
                                 const size_t thread) -> Status {
442
0
      const int64_t y = task;
443
0
      uint8_t* row_out =
444
0
          out_callback.IsPresent()
445
0
              ? row_out_callback[thread].data()
446
0
              : &(reinterpret_cast<uint8_t*>(out_image))[stride * y];
447
0
      const float* JXL_RESTRICT row_in[kConvertMaxChannels];
448
0
      for (size_t c = 0; c < num_channels; c++) {
449
0
        row_in[c] = channels[c] ? channels[c]->Row(y) : ones.Row(0);
450
0
      }
451
0
      uint32_t* JXL_RESTRICT row_u32[kConvertMaxChannels];
452
0
      for (size_t c = 0; c < num_channels; c++) {
453
0
        row_u32[c] = u32_cache.Row(c + thread * num_channels);
454
        // row_u32[] is a per-thread temporary row storage, this isn't
455
        // intended to be initialized on a previous run.
456
0
        msan::PoisonMemory(row_u32[c], xsize * sizeof(row_u32[c][0]));
457
0
        HWY_DYNAMIC_DISPATCH(FloatToU32)
458
0
        (row_in[c], row_u32[c], xsize, mul, bits_per_sample);
459
0
      }
460
0
      if (bits_per_sample <= 8) {
461
0
        StoreUintRow<Store8>(row_u32, num_channels, xsize, 1, row_out);
462
0
      } else {
463
0
        if (little_endian) {
464
0
          StoreUintRow<StoreLE16>(row_u32, num_channels, xsize, 2, row_out);
465
0
        } else {
466
0
          StoreUintRow<StoreBE16>(row_u32, num_channels, xsize, 2, row_out);
467
0
        }
468
0
      }
469
0
      if (out_callback.IsPresent()) {
470
0
        out_callback.run(out_run_opaque.get(), thread, 0, y, xsize, row_out);
471
0
      }
472
0
      return true;
473
0
    };
474
0
    JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, static_cast<uint32_t>(ysize),
475
0
                                  init_cache, process_row, "ConvertUint"));
476
0
  }
477
1.15k
  return true;
478
1.15k
}
479
480
Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
481
                         bool float_out, size_t num_channels,
482
                         JxlEndianness endianness, size_t stride,
483
                         jxl::ThreadPool* pool, void* out_image,
484
                         size_t out_size, const PixelCallback& out_callback,
485
                         jxl::Orientation undo_orientation,
486
820
                         bool unpremul_alpha) {
487
820
  bool want_alpha = num_channels == 2 || num_channels == 4;
488
820
  size_t color_channels = num_channels <= 2 ? 1 : 3;
489
490
820
  const Image3F* color = &ib.color();
491
820
  JxlMemoryManager* memory_manager = color->memory_manager();
492
  // Undo premultiplied alpha.
493
820
  Image3F unpremul;
494
820
  if (ib.AlphaIsPremultiplied() && ib.HasAlpha() && unpremul_alpha) {
495
0
    JXL_ASSIGN_OR_RETURN(
496
0
        unpremul,
497
0
        Image3F::Create(memory_manager, color->xsize(), color->ysize()));
498
0
    JXL_RETURN_IF_ERROR(CopyImageTo(*color, &unpremul));
499
0
    const ImageF* alpha = ib.alpha();
500
0
    for (size_t y = 0; y < unpremul.ysize(); y++) {
501
0
      UnpremultiplyAlpha(unpremul.PlaneRow(0, y), unpremul.PlaneRow(1, y),
502
0
                         unpremul.PlaneRow(2, y), alpha->Row(y),
503
0
                         unpremul.xsize());
504
0
    }
505
0
    color = &unpremul;
506
0
  }
507
508
820
  const ImageF* channels[kConvertMaxChannels];
509
820
  size_t c = 0;
510
3.28k
  for (; c < color_channels; c++) {
511
2.46k
    channels[c] = &color->Plane(c);
512
2.46k
  }
513
820
  if (want_alpha) {
514
0
    channels[c++] = ib.alpha();
515
0
  }
516
820
  JXL_ENSURE(num_channels == c);
517
518
820
  return ConvertChannelsToExternal(
519
820
      channels, num_channels, bits_per_sample, float_out, endianness, stride,
520
820
      pool, out_image, out_size, out_callback, undo_orientation);
521
820
}
522
523
}  // namespace jxl
524
#endif  // HWY_ONCE