Coverage Report

Created: 2025-06-16 07:00

/src/libjxl/lib/jxl/dec_external_image.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/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
1.52M
void StoreLEFloat(float value, uint8_t* p) {
111
1.52M
  uint32_t u;
112
1.52M
  memcpy(&u, &value, 4);
113
1.52M
  StoreLE32(u, p);
114
1.52M
}
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
6.16k
                   size_t xsize, uint8_t* JXL_RESTRICT out) {
252
512k
  for (size_t x = 0; x < xsize; ++x) {
253
2.02M
    for (size_t c = 0; c < num_channels; c++) {
254
1.52M
      StoreFunc(rows_in[c][x], out + (num_channels * x + c) * sizeof(float));
255
1.52M
    }
256
506k
  }
257
6.16k
}
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
6.16k
                   size_t xsize, uint8_t* JXL_RESTRICT out) {
252
512k
  for (size_t x = 0; x < xsize; ++x) {
253
2.02M
    for (size_t c = 0; c < num_channels; c++) {
254
1.52M
      StoreFunc(rows_in[c][x], out + (num_channels * x + c) * sizeof(float));
255
1.52M
    }
256
506k
  }
257
6.16k
}
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
97
                                 jxl::Orientation undo_orientation) {
270
97
  JXL_ENSURE(num_channels != 0 && num_channels <= kConvertMaxChannels);
271
97
  JXL_ENSURE(in_channels[0] != nullptr);
272
97
  JxlMemoryManager* memory_manager = in_channels[0]->memory_manager();
273
97
  JXL_ENSURE(float_out ? bits_per_sample == 16 || bits_per_sample == 32
274
97
                       : bits_per_sample > 0 && bits_per_sample <= 16);
275
97
  const bool has_out_image = (out_image != nullptr);
276
97
  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
97
  std::vector<const ImageF*> channels;
281
97
  channels.assign(in_channels, in_channels + num_channels);
282
283
97
  const size_t bytes_per_channel = DivCeil(bits_per_sample, jxl::kBitsPerByte);
284
97
  const size_t bytes_per_pixel = num_channels * bytes_per_channel;
285
286
97
  std::vector<std::vector<uint8_t>> row_out_callback;
287
97
  const auto FreeCallbackOpaque = [&out_callback](void* p) {
288
0
    out_callback.destroy(p);
289
0
  };
290
97
  std::unique_ptr<void, decltype(FreeCallbackOpaque)> out_run_opaque(
291
97
      nullptr, FreeCallbackOpaque);
292
97
  auto InitOutCallback = [&](size_t num_threads) -> Status {
293
97
    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
97
    return true;
302
97
  };
303
304
  // Channels used to store the transformed original channels if needed.
305
97
  ImageF temp_channels[kConvertMaxChannels];
306
97
  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
97
  size_t xsize = channels[0]->xsize();
318
97
  size_t ysize = channels[0]->ysize();
319
97
  if (stride < bytes_per_pixel * xsize) {
320
0
    return JXL_FAILURE("stride is smaller than scanline width in bytes: %" PRIuS
321
0
                       " vs %" PRIuS,
322
0
                       stride, bytes_per_pixel * xsize);
323
0
  }
324
97
  if (!out_callback.IsPresent() &&
325
97
      out_size < (ysize - 1) * stride + bytes_per_pixel * xsize) {
326
0
    return JXL_FAILURE("out_size is too small to store image");
327
0
  }
328
329
97
  const bool little_endian =
330
97
      endianness == JXL_LITTLE_ENDIAN ||
331
97
      (endianness == JXL_NATIVE_ENDIAN && IsLittleEndian());
332
333
  // Handle the case where a channel is nullptr by creating a single row with
334
  // ones to use instead.
335
97
  ImageF ones;
336
388
  for (size_t c = 0; c < num_channels; ++c) {
337
291
    if (!channels[c]) {
338
0
      JXL_ASSIGN_OR_RETURN(ones, ImageF::Create(memory_manager, xsize, 1));
339
0
      FillImage(1.0f, &ones);
340
0
      break;
341
0
    }
342
291
  }
343
344
97
  if (float_out) {
345
97
    if (bits_per_sample == 16) {
346
0
      bool swap_endianness = little_endian != IsLittleEndian();
347
0
      Plane<hwy::float16_t> f16_cache;
348
0
      const auto init_cache = [&](size_t num_threads) -> Status {
349
0
        JXL_ASSIGN_OR_RETURN(
350
0
            f16_cache, Plane<hwy::float16_t>::Create(
351
0
                           memory_manager, xsize, num_channels * num_threads));
352
0
        JXL_RETURN_IF_ERROR(InitOutCallback(num_threads));
353
0
        return true;
354
0
      };
355
0
      const auto process_row = [&](const uint32_t task,
356
0
                                   const size_t thread) -> Status {
357
0
        const int64_t y = task;
358
0
        const float* JXL_RESTRICT row_in[kConvertMaxChannels];
359
0
        for (size_t c = 0; c < num_channels; c++) {
360
0
          row_in[c] = channels[c] ? channels[c]->Row(y) : ones.Row(0);
361
0
        }
362
0
        hwy::float16_t* JXL_RESTRICT row_f16[kConvertMaxChannels];
363
0
        for (size_t c = 0; c < num_channels; c++) {
364
0
          row_f16[c] = f16_cache.Row(c + thread * num_channels);
365
0
          HWY_DYNAMIC_DISPATCH(FloatToF16)
366
0
          (row_in[c], row_f16[c], xsize);
367
0
        }
368
0
        uint8_t* row_out =
369
0
            out_callback.IsPresent()
370
0
                ? row_out_callback[thread].data()
371
0
                : &(reinterpret_cast<uint8_t*>(out_image))[stride * y];
372
        // interleave the one scanline
373
0
        hwy::float16_t* row_f16_out =
374
0
            reinterpret_cast<hwy::float16_t*>(row_out);
375
0
        for (size_t x = 0; x < xsize; x++) {
376
0
          for (size_t c = 0; c < num_channels; c++) {
377
0
            row_f16_out[x * num_channels + c] = row_f16[c][x];
378
0
          }
379
0
        }
380
0
        if (swap_endianness) {
381
0
          size_t size = xsize * num_channels * 2;
382
0
          for (size_t i = 0; i < size; i += 2) {
383
0
            std::swap(row_out[i + 0], row_out[i + 1]);
384
0
          }
385
0
        }
386
0
        if (out_callback.IsPresent()) {
387
0
          out_callback.run(out_run_opaque.get(), thread, 0, y, xsize, row_out);
388
0
        }
389
0
        return true;
390
0
      };
391
0
      JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, static_cast<uint32_t>(ysize),
392
0
                                    init_cache, process_row, "ConvertF16"));
393
97
    } else if (bits_per_sample == 32) {
394
97
      const auto init_cache = [&](size_t num_threads) -> Status {
395
97
        JXL_RETURN_IF_ERROR(InitOutCallback(num_threads));
396
97
        return true;
397
97
      };
398
97
      const auto process_row = [&](const uint32_t task,
399
6.16k
                                   const size_t thread) -> Status {
400
6.16k
        const int64_t y = task;
401
6.16k
        uint8_t* row_out =
402
6.16k
            out_callback.IsPresent()
403
6.16k
                ? row_out_callback[thread].data()
404
6.16k
                : &(reinterpret_cast<uint8_t*>(out_image))[stride * y];
405
6.16k
        const float* JXL_RESTRICT row_in[kConvertMaxChannels];
406
24.6k
        for (size_t c = 0; c < num_channels; c++) {
407
18.5k
          row_in[c] = channels[c] ? channels[c]->Row(y) : ones.Row(0);
408
18.5k
        }
409
6.16k
        if (little_endian) {
410
6.16k
          StoreFloatRow<StoreLEFloat>(row_in, num_channels, xsize, row_out);
411
6.16k
        } else {
412
0
          StoreFloatRow<StoreBEFloat>(row_in, num_channels, xsize, row_out);
413
0
        }
414
6.16k
        if (out_callback.IsPresent()) {
415
0
          out_callback.run(out_run_opaque.get(), thread, 0, y, xsize, row_out);
416
0
        }
417
6.16k
        return true;
418
6.16k
      };
419
97
      JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, static_cast<uint32_t>(ysize),
420
97
                                    init_cache, process_row, "ConvertFloat"));
421
97
    } else {
422
0
      return JXL_FAILURE("float other than 16-bit and 32-bit not supported");
423
0
    }
424
97
  } else {
425
    // Multiplier to convert from floating point 0-1 range to the integer
426
    // range.
427
0
    float mul = (1ull << bits_per_sample) - 1;
428
0
    Plane<uint32_t> u32_cache;
429
0
    const auto init_cache = [&](size_t num_threads) -> Status {
430
0
      JXL_ASSIGN_OR_RETURN(u32_cache,
431
0
                           Plane<uint32_t>::Create(memory_manager, xsize,
432
0
                                                   num_channels * num_threads));
433
0
      JXL_RETURN_IF_ERROR(InitOutCallback(num_threads));
434
0
      return true;
435
0
    };
436
0
    const auto process_row = [&](const uint32_t task,
437
0
                                 const size_t thread) -> Status {
438
0
      const int64_t y = task;
439
0
      uint8_t* row_out =
440
0
          out_callback.IsPresent()
441
0
              ? row_out_callback[thread].data()
442
0
              : &(reinterpret_cast<uint8_t*>(out_image))[stride * y];
443
0
      const float* JXL_RESTRICT row_in[kConvertMaxChannels];
444
0
      for (size_t c = 0; c < num_channels; c++) {
445
0
        row_in[c] = channels[c] ? channels[c]->Row(y) : ones.Row(0);
446
0
      }
447
0
      uint32_t* JXL_RESTRICT row_u32[kConvertMaxChannels];
448
0
      for (size_t c = 0; c < num_channels; c++) {
449
0
        row_u32[c] = u32_cache.Row(c + thread * num_channels);
450
        // row_u32[] is a per-thread temporary row storage, this isn't
451
        // intended to be initialized on a previous run.
452
0
        msan::PoisonMemory(row_u32[c], xsize * sizeof(row_u32[c][0]));
453
0
        HWY_DYNAMIC_DISPATCH(FloatToU32)
454
0
        (row_in[c], row_u32[c], xsize, mul, bits_per_sample);
455
0
      }
456
0
      if (bits_per_sample <= 8) {
457
0
        StoreUintRow<Store8>(row_u32, num_channels, xsize, 1, row_out);
458
0
      } else {
459
0
        if (little_endian) {
460
0
          StoreUintRow<StoreLE16>(row_u32, num_channels, xsize, 2, row_out);
461
0
        } else {
462
0
          StoreUintRow<StoreBE16>(row_u32, num_channels, xsize, 2, row_out);
463
0
        }
464
0
      }
465
0
      if (out_callback.IsPresent()) {
466
0
        out_callback.run(out_run_opaque.get(), thread, 0, y, xsize, row_out);
467
0
      }
468
0
      return true;
469
0
    };
470
0
    JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, static_cast<uint32_t>(ysize),
471
0
                                  init_cache, process_row, "ConvertUint"));
472
0
  }
473
97
  return true;
474
97
}
475
476
Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
477
                         bool float_out, size_t num_channels,
478
                         JxlEndianness endianness, size_t stride,
479
                         jxl::ThreadPool* pool, void* out_image,
480
                         size_t out_size, const PixelCallback& out_callback,
481
                         jxl::Orientation undo_orientation,
482
97
                         bool unpremul_alpha) {
483
97
  bool want_alpha = num_channels == 2 || num_channels == 4;
484
97
  size_t color_channels = num_channels <= 2 ? 1 : 3;
485
486
97
  const Image3F* color = &ib.color();
487
97
  JxlMemoryManager* memory_manager = color->memory_manager();
488
  // Undo premultiplied alpha.
489
97
  Image3F unpremul;
490
97
  if (ib.AlphaIsPremultiplied() && ib.HasAlpha() && unpremul_alpha) {
491
0
    JXL_ASSIGN_OR_RETURN(
492
0
        unpremul,
493
0
        Image3F::Create(memory_manager, color->xsize(), color->ysize()));
494
0
    JXL_RETURN_IF_ERROR(CopyImageTo(*color, &unpremul));
495
0
    const ImageF* alpha = ib.alpha();
496
0
    for (size_t y = 0; y < unpremul.ysize(); y++) {
497
0
      UnpremultiplyAlpha(unpremul.PlaneRow(0, y), unpremul.PlaneRow(1, y),
498
0
                         unpremul.PlaneRow(2, y), alpha->Row(y),
499
0
                         unpremul.xsize());
500
0
    }
501
0
    color = &unpremul;
502
0
  }
503
504
97
  const ImageF* channels[kConvertMaxChannels];
505
97
  size_t c = 0;
506
388
  for (; c < color_channels; c++) {
507
291
    channels[c] = &color->Plane(c);
508
291
  }
509
97
  if (want_alpha) {
510
0
    channels[c++] = ib.alpha();
511
0
  }
512
97
  JXL_ENSURE(num_channels == c);
513
514
97
  return ConvertChannelsToExternal(
515
97
      channels, num_channels, bits_per_sample, float_out, endianness, stride,
516
97
      pool, out_image, out_size, out_callback, undo_orientation);
517
97
}
518
519
}  // namespace jxl
520
#endif  // HWY_ONCE