Coverage Report

Created: 2026-10-28 07:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libjxl/lib/jxl/modular/transform/palette.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/modular/transform/palette.h"
7
8
#include <jxl/memory_manager.h>
9
10
#include <algorithm>
11
#include <cstddef>
12
#include <cstdint>
13
#include <utility>
14
#include <vector>
15
16
#include "lib/jxl/base/common.h"
17
#include "lib/jxl/base/compiler_specific.h"
18
#include "lib/jxl/base/data_parallel.h"
19
#include "lib/jxl/base/status.h"
20
#include "lib/jxl/image.h"
21
#include "lib/jxl/modular/encoding/context_predict.h"
22
#include "lib/jxl/modular/modular_image.h"
23
#include "lib/jxl/modular/options.h"
24
#include "lib/jxl/modular/transform/transform.h"  // CheckEqualChannels
25
26
namespace jxl {
27
28
Status InvPalette(Image &input, uint32_t begin_c, uint32_t nb_colors,
29
                  uint32_t nb_deltas, Predictor predictor,
30
5.56k
                  const weighted::Header &wp_header, ThreadPool *pool) {
31
5.56k
  JxlMemoryManager *memory_manager = input.memory_manager();
32
5.56k
  if (input.nb_meta_channels < 1) {
33
0
    return JXL_FAILURE("Error: Palette transform without palette.");
34
0
  }
35
5.56k
  int nb = input.channel[0].h;
36
5.56k
  uint32_t c0 = begin_c + 1;
37
5.56k
  if (c0 >= input.channel.size()) {
38
0
    return JXL_FAILURE("Channel is out of range.");
39
0
  }
40
5.56k
  size_t w = input.channel[c0].w;
41
5.56k
  size_t h = input.channel[c0].h;
42
5.56k
  if (nb < 1) return JXL_FAILURE("Corrupted transforms");
43
12.0k
  for (int i = 1; i < nb; i++) {
44
6.51k
    JXL_ASSIGN_OR_RETURN(Channel c, Channel::Create(memory_manager, w, h,
45
6.51k
                                                    input.channel[c0].hshift,
46
6.51k
                                                    input.channel[c0].vshift));
47
6.51k
    input.channel.insert(input.channel.begin() + c0 + 1, std::move(c));
48
6.51k
  }
49
5.56k
  const Channel &palette = input.channel[0];
50
5.56k
  const pixel_type *JXL_RESTRICT p_palette = input.channel[0].Row(0);
51
5.56k
  ptrdiff_t onerow = input.channel[0].plane.PixelsPerRow();
52
5.56k
  ptrdiff_t onerow_image = input.channel[c0].plane.PixelsPerRow();
53
5.56k
  const int bit_depth = std::min(input.bitdepth, 24);
54
55
5.56k
  if (w == 0) {
56
    // Nothing to do.
57
    // Avoid touching "empty" channels with non-zero height.
58
5.53k
  } else if (nb_deltas == 0 && predictor == Predictor::Zero) {
59
2.60k
    if (nb == 1) {
60
1.50k
      const auto process_row = [&](const uint32_t task,
61
40.7k
                                   size_t /* thread */) -> Status {
62
40.7k
        const size_t y = task;
63
40.7k
        pixel_type *p = input.channel[c0].Row(y);
64
7.88M
        for (size_t x = 0; x < w; x++) {
65
7.84M
          const int index =
66
7.84M
              Clamp1<int>(p[x], 0, static_cast<pixel_type>(palette.w) - 1);
67
7.84M
          p[x] = palette_internal::GetPaletteValue(p_palette, index, /*c=*/0,
68
7.84M
                                                   /*palette_size=*/palette.w,
69
7.84M
                                                   /*onerow=*/onerow,
70
7.84M
                                                   /*bit_depth=*/bit_depth);
71
7.84M
        }
72
40.7k
        return true;
73
40.7k
      };
74
1.50k
      JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, h, ThreadPool::NoInit, process_row,
75
1.50k
                                    "UndoChannelPalette"));
76
1.50k
    } else {
77
1.10k
      const auto process_row = [&](const uint32_t task,
78
64.4k
                                   size_t /* thread */) -> Status {
79
64.4k
        const size_t y = task;
80
64.4k
        std::vector<pixel_type *> p_out(nb);
81
64.4k
        const pixel_type *p_index = input.channel[c0].Row(y);
82
314k
        for (int c = 0; c < nb; c++) p_out[c] = input.channel[c0 + c].Row(y);
83
8.70M
        for (size_t x = 0; x < w; x++) {
84
8.63M
          const int index = p_index[x];
85
41.8M
          for (int c = 0; c < nb; c++) {
86
33.1M
            p_out[c][x] = palette_internal::GetPaletteValue(
87
33.1M
                p_palette, index, /*c=*/c,
88
33.1M
                /*palette_size=*/palette.w,
89
33.1M
                /*onerow=*/onerow, /*bit_depth=*/bit_depth);
90
33.1M
          }
91
8.63M
        }
92
64.4k
        return true;
93
64.4k
      };
94
1.10k
      JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, h, ThreadPool::NoInit, process_row,
95
1.10k
                                    "UndoPalette"));
96
1.10k
    }
97
2.92k
  } else {
98
    // Parallelized per channel.
99
2.92k
    ImageI indices;
100
2.92k
    ImageI &plane = input.channel[c0].plane;
101
2.92k
    JXL_ASSIGN_OR_RETURN(
102
2.92k
        indices, ImageI::Create(memory_manager, plane.xsize(), plane.ysize()));
103
2.92k
    plane.Swap(indices);
104
2.92k
    if (predictor == Predictor::Weighted) {
105
791
      const auto process_channel = [&](const uint32_t c,
106
2.35k
                                       size_t /* thread */) -> Status {
107
2.35k
        Channel &channel = input.channel[c0 + c];
108
2.35k
        weighted::State wp_state(wp_header, channel.w, channel.h);
109
223k
        for (size_t y = 0; y < channel.h; y++) {
110
221k
          pixel_type *JXL_RESTRICT p = channel.Row(y);
111
221k
          const pixel_type *JXL_RESTRICT idx = indices.Row(y);
112
27.1M
          for (size_t x = 0; x < channel.w; x++) {
113
26.9M
            int index = idx[x];
114
26.9M
            pixel_type_w val = 0;
115
26.9M
            const pixel_type palette_entry = palette_internal::GetPaletteValue(
116
26.9M
                p_palette, index, /*c=*/c,
117
26.9M
                /*palette_size=*/palette.w, /*onerow=*/onerow,
118
26.9M
                /*bit_depth=*/bit_depth);
119
26.9M
            PredictionResult pred = PredictNoTreeWP(
120
26.9M
                channel.w, p + x, onerow_image, x, y, predictor, &wp_state);
121
26.9M
            if (index < static_cast<int32_t>(nb_deltas)) {
122
1.29M
              val = pred.guess + palette_entry;
123
25.6M
            } else {
124
25.6M
              val = palette_entry;
125
25.6M
            }
126
26.9M
            p[x] = val;
127
26.9M
            wp_state.UpdateErrors(p[x], x, y, channel.w);
128
26.9M
          }
129
221k
        }
130
2.35k
        return true;
131
2.35k
      };
132
791
      JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, nb, ThreadPool::NoInit,
133
791
                                    process_channel, "UndoDeltaPaletteWP"));
134
2.13k
    } else {
135
2.13k
      const auto process_channel = [&](const uint32_t c,
136
4.10k
                                       size_t /* thread */) -> Status {
137
4.10k
        Channel &channel = input.channel[c0 + c];
138
200k
        for (size_t y = 0; y < channel.h; y++) {
139
195k
          pixel_type *JXL_RESTRICT p = channel.Row(y);
140
195k
          const pixel_type *JXL_RESTRICT idx = indices.Row(y);
141
22.6M
          for (size_t x = 0; x < channel.w; x++) {
142
22.4M
            int index = idx[x];
143
22.4M
            pixel_type_w val = 0;
144
22.4M
            const pixel_type palette_entry = palette_internal::GetPaletteValue(
145
22.4M
                p_palette, index, /*c=*/c,
146
22.4M
                /*palette_size=*/palette.w,
147
22.4M
                /*onerow=*/onerow, /*bit_depth=*/bit_depth);
148
22.4M
            if (index < static_cast<int32_t>(nb_deltas)) {
149
19.1M
              PredictionResult pred = PredictNoTreeNoWP(
150
19.1M
                  channel.w, p + x, onerow_image, x, y, predictor);
151
19.1M
              val = pred.guess + palette_entry;
152
19.1M
            } else {
153
3.29M
              val = palette_entry;
154
3.29M
            }
155
22.4M
            p[x] = val;
156
22.4M
          }
157
195k
        }
158
4.10k
        return true;
159
4.10k
      };
160
2.13k
      JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, nb, ThreadPool::NoInit,
161
2.13k
                                    process_channel, "UndoDeltaPaletteNoWP"));
162
2.13k
    }
163
2.92k
  }
164
5.56k
  if (c0 >= input.nb_meta_channels) {
165
    // Palette was done on normal channels
166
5.50k
    input.nb_meta_channels--;
167
5.50k
  } else {
168
    // Palette was done on metachannels
169
52
    JXL_ENSURE(static_cast<int>(input.nb_meta_channels) >= 2 - nb);
170
52
    input.nb_meta_channels -= 2 - nb;
171
52
    JXL_ENSURE(begin_c + nb - 1 < input.nb_meta_channels);
172
52
  }
173
5.56k
  input.channel.erase(input.channel.begin(), input.channel.begin() + 1);
174
5.56k
  return true;
175
5.56k
}
176
177
Status MetaPalette(Image &input, uint32_t begin_c, uint32_t end_c,
178
10.0k
                   uint32_t nb_colors, uint32_t nb_deltas, bool lossy) {
179
10.0k
  JXL_RETURN_IF_ERROR(CheckEqualChannels(input, begin_c, end_c));
180
7.77k
  JxlMemoryManager *memory_manager = input.memory_manager();
181
182
7.77k
  size_t nb = end_c - begin_c + 1;
183
7.77k
  if (begin_c >= input.nb_meta_channels) {
184
    // Palette was done on normal channels
185
7.68k
    input.nb_meta_channels++;
186
7.68k
  } else {
187
    // Palette was done on metachannels
188
96
    JXL_ENSURE(end_c < input.nb_meta_channels);
189
    // we remove nb-1 metachannels and add one
190
96
    input.nb_meta_channels += 2 - nb;
191
96
  }
192
7.77k
  input.channel.erase(input.channel.begin() + begin_c + 1,
193
7.77k
                      input.channel.begin() + end_c + 1);
194
7.77k
  JXL_ASSIGN_OR_RETURN(
195
7.77k
      Channel pch, Channel::Create(memory_manager, nb_colors + nb_deltas, nb));
196
7.77k
  pch.hshift = -1;
197
7.77k
  pch.vshift = -1;
198
7.77k
  input.channel.insert(input.channel.begin(), std::move(pch));
199
7.77k
  return true;
200
7.77k
}
201
202
}  // namespace jxl