Coverage Report

Created: 2025-06-13 07:37

/src/libjxl/lib/extras/enc/pnm.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/extras/enc/pnm.h"
7
8
#include <jxl/codestream_header.h>
9
#include <jxl/types.h>
10
11
#include <cstddef>
12
#include <cstdint>
13
#include <cstdio>
14
#include <cstring>
15
#include <memory>
16
#include <string>
17
#include <vector>
18
19
#include "lib/extras/enc/encode.h"
20
#include "lib/extras/packed_image.h"
21
#include "lib/jxl/base/common.h"
22
#include "lib/jxl/base/data_parallel.h"
23
#include "lib/jxl/base/printf_macros.h"
24
#include "lib/jxl/base/status.h"
25
26
namespace jxl {
27
namespace extras {
28
namespace {
29
30
constexpr size_t kMaxHeaderSize = 2000;
31
32
class BasePNMEncoder : public Encoder {
33
 public:
34
  Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image,
35
0
                ThreadPool* pool) const override {
36
0
    JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info));
37
0
    if (!ppf.metadata.exif.empty() || !ppf.metadata.iptc.empty() ||
38
0
        !ppf.metadata.jumbf.empty() || !ppf.metadata.xmp.empty()) {
39
0
      JXL_WARNING("PNM encoder ignoring metadata - use a different codec");
40
0
    }
41
0
    encoded_image->icc = ppf.icc;
42
0
    encoded_image->bitstreams.clear();
43
0
    encoded_image->bitstreams.reserve(ppf.frames.size());
44
0
    for (const auto& frame : ppf.frames) {
45
0
      JXL_RETURN_IF_ERROR(VerifyPackedImage(frame.color, ppf.info));
46
0
      encoded_image->bitstreams.emplace_back();
47
0
      JXL_RETURN_IF_ERROR(
48
0
          EncodeFrame(ppf, frame, &encoded_image->bitstreams.back()));
49
0
    }
50
0
    for (size_t i = 0; i < ppf.extra_channels_info.size(); ++i) {
51
0
      const auto& ec_info = ppf.extra_channels_info[i].ec_info;
52
0
      encoded_image->extra_channel_bitstreams.emplace_back();
53
0
      auto& ec_bitstreams = encoded_image->extra_channel_bitstreams.back();
54
0
      for (const auto& frame : ppf.frames) {
55
0
        ec_bitstreams.emplace_back();
56
0
        JXL_RETURN_IF_ERROR(EncodeExtraChannel(frame.extra_channels[i],
57
0
                                               ec_info.bits_per_sample,
58
0
                                               &ec_bitstreams.back()));
59
0
      }
60
0
    }
61
0
    return true;
62
0
  }
63
64
 protected:
65
  virtual Status EncodeFrame(const PackedPixelFile& ppf,
66
                             const PackedFrame& frame,
67
                             std::vector<uint8_t>* bytes) const = 0;
68
  virtual Status EncodeExtraChannel(const PackedImage& image,
69
                                    size_t bits_per_sample,
70
                                    std::vector<uint8_t>* bytes) const = 0;
71
};
72
73
class PNMEncoder : public BasePNMEncoder {
74
 public:
75
  static const std::vector<JxlPixelFormat> kAcceptedFormats;
76
77
0
  std::vector<JxlPixelFormat> AcceptedFormats() const override {
78
0
    return kAcceptedFormats;
79
0
  }
80
81
  Status EncodeFrame(const PackedPixelFile& ppf, const PackedFrame& frame,
82
0
                     std::vector<uint8_t>* bytes) const override {
83
0
    return EncodeImage(frame.color, ppf.info.bits_per_sample, bytes);
84
0
  }
85
  Status EncodeExtraChannel(const PackedImage& image, size_t bits_per_sample,
86
0
                            std::vector<uint8_t>* bytes) const override {
87
0
    return EncodeImage(image, bits_per_sample, bytes);
88
0
  }
89
90
 private:
91
  static Status EncodeImage(const PackedImage& image, size_t bits_per_sample,
92
0
                            std::vector<uint8_t>* bytes) {
93
0
    uint32_t maxval = (1u << bits_per_sample) - 1;
94
0
    char type = image.format.num_channels == 1 ? '5' : '6';
95
0
    char header[kMaxHeaderSize];
96
0
    size_t header_size =
97
0
        snprintf(header, kMaxHeaderSize, "P%c\n%" PRIuS " %" PRIuS "\n%u\n",
98
0
                 type, image.xsize, image.ysize, maxval);
99
0
    JXL_RETURN_IF_ERROR(header_size < kMaxHeaderSize);
100
0
    bytes->resize(header_size + image.pixels_size);
101
0
    memcpy(bytes->data(), header, header_size);
102
0
    memcpy(bytes->data() + header_size,
103
0
           reinterpret_cast<uint8_t*>(image.pixels()), image.pixels_size);
104
0
    return true;
105
0
  }
106
};
107
108
class PGMEncoder : public PNMEncoder {
109
 public:
110
  static const std::vector<JxlPixelFormat> kAcceptedFormats;
111
112
0
  std::vector<JxlPixelFormat> AcceptedFormats() const override {
113
0
    return kAcceptedFormats;
114
0
  }
115
};
116
117
const std::vector<JxlPixelFormat> PGMEncoder::kAcceptedFormats = {
118
    JxlPixelFormat{1, JXL_TYPE_UINT8, JXL_BIG_ENDIAN, 0},
119
    JxlPixelFormat{1, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}};
120
121
class PPMEncoder : public PNMEncoder {
122
 public:
123
  static const std::vector<JxlPixelFormat> kAcceptedFormats;
124
125
0
  std::vector<JxlPixelFormat> AcceptedFormats() const override {
126
0
    return kAcceptedFormats;
127
0
  }
128
};
129
130
const std::vector<JxlPixelFormat> PPMEncoder::kAcceptedFormats = {
131
    JxlPixelFormat{3, JXL_TYPE_UINT8, JXL_BIG_ENDIAN, 0},
132
    JxlPixelFormat{3, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}};
133
134
2
const std::vector<JxlPixelFormat> PNMEncoder::kAcceptedFormats = [] {
135
2
  std::vector<JxlPixelFormat> combined = PPMEncoder::kAcceptedFormats;
136
2
  combined.insert(combined.end(), PGMEncoder::kAcceptedFormats.begin(),
137
2
                  PGMEncoder::kAcceptedFormats.end());
138
2
  return combined;
139
2
}();
140
141
class PFMEncoder : public BasePNMEncoder {
142
 public:
143
0
  std::vector<JxlPixelFormat> AcceptedFormats() const override {
144
0
    std::vector<JxlPixelFormat> formats;
145
0
    for (const uint32_t num_channels : {1, 3}) {
146
0
      for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) {
147
0
        formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels,
148
0
                                         /*data_type=*/JXL_TYPE_FLOAT,
149
0
                                         /*endianness=*/endianness,
150
0
                                         /*align=*/0});
151
0
      }
152
0
    }
153
0
    return formats;
154
0
  }
155
  Status EncodeFrame(const PackedPixelFile& ppf, const PackedFrame& frame,
156
0
                     std::vector<uint8_t>* bytes) const override {
157
0
    return EncodeImage(frame.color, bytes);
158
0
  }
159
  Status EncodeExtraChannel(const PackedImage& image, size_t bits_per_sample,
160
0
                            std::vector<uint8_t>* bytes) const override {
161
0
    return EncodeImage(image, bytes);
162
0
  }
163
164
 private:
165
  static Status EncodeImage(const PackedImage& image,
166
0
                            std::vector<uint8_t>* bytes) {
167
0
    char type = image.format.num_channels == 1 ? 'f' : 'F';
168
0
    double scale = image.format.endianness == JXL_LITTLE_ENDIAN ? -1.0 : 1.0;
169
0
    char header[kMaxHeaderSize];
170
0
    size_t header_size =
171
0
        snprintf(header, kMaxHeaderSize, "P%c\n%" PRIuS " %" PRIuS "\n%.1f\n",
172
0
                 type, image.xsize, image.ysize, scale);
173
0
    JXL_RETURN_IF_ERROR(header_size < kMaxHeaderSize);
174
0
    bytes->resize(header_size + image.pixels_size);
175
0
    memcpy(bytes->data(), header, header_size);
176
0
    const uint8_t* in = reinterpret_cast<const uint8_t*>(image.pixels());
177
0
    uint8_t* out = bytes->data() + header_size;
178
0
    for (size_t y = 0; y < image.ysize; ++y) {
179
0
      size_t y_out = image.ysize - 1 - y;
180
0
      const uint8_t* row_in = &in[y * image.stride];
181
0
      uint8_t* row_out = &out[y_out * image.stride];
182
0
      memcpy(row_out, row_in, image.stride);
183
0
    }
184
0
    return true;
185
0
  }
186
};
187
188
class PAMEncoder : public BasePNMEncoder {
189
 public:
190
0
  std::vector<JxlPixelFormat> AcceptedFormats() const override {
191
0
    std::vector<JxlPixelFormat> formats;
192
0
    for (const uint32_t num_channels : {1, 2, 3, 4}) {
193
0
      for (const JxlDataType data_type : {JXL_TYPE_UINT8, JXL_TYPE_UINT16}) {
194
0
        formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels,
195
0
                                         /*data_type=*/data_type,
196
0
                                         /*endianness=*/JXL_BIG_ENDIAN,
197
0
                                         /*align=*/0});
198
0
      }
199
0
    }
200
0
    return formats;
201
0
  }
202
  Status EncodeFrame(const PackedPixelFile& ppf, const PackedFrame& frame,
203
0
                     std::vector<uint8_t>* bytes) const override {
204
0
    const PackedImage& color = frame.color;
205
0
    const auto& ec_info = ppf.extra_channels_info;
206
0
    JXL_RETURN_IF_ERROR(frame.extra_channels.size() == ec_info.size());
207
0
    for (const auto& ec : frame.extra_channels) {
208
0
      if (ec.xsize != color.xsize || ec.ysize != color.ysize) {
209
0
        return JXL_FAILURE("Extra channel and color size mismatch.");
210
0
      }
211
0
      if (ec.format.data_type != color.format.data_type ||
212
0
          ec.format.endianness != color.format.endianness) {
213
0
        return JXL_FAILURE("Extra channel and color format mismatch.");
214
0
      }
215
0
    }
216
0
    if (ppf.info.alpha_bits &&
217
0
        (ppf.info.bits_per_sample != ppf.info.alpha_bits)) {
218
0
      return JXL_FAILURE("Alpha bit depth does not match image bit depth");
219
0
    }
220
0
    for (const auto& it : ec_info) {
221
0
      if (it.ec_info.bits_per_sample != ppf.info.bits_per_sample) {
222
0
        return JXL_FAILURE(
223
0
            "Extra channel bit depth does not match image bit depth");
224
0
      }
225
0
    }
226
0
    const char* kColorTypes[4] = {"GRAYSCALE", "GRAYSCALE_ALPHA", "RGB",
227
0
                                  "RGB_ALPHA"};
228
0
    uint32_t maxval = (1u << ppf.info.bits_per_sample) - 1;
229
0
    uint32_t depth = color.format.num_channels + ec_info.size();
230
0
    char header[kMaxHeaderSize];
231
0
    size_t pos = 0;
232
0
    pos += snprintf(header + pos, kMaxHeaderSize - pos,
233
0
                    "P7\nWIDTH %" PRIuS "\nHEIGHT %" PRIuS
234
0
                    "\nDEPTH %u\n"
235
0
                    "MAXVAL %u\nTUPLTYPE %s\n",
236
0
                    color.xsize, color.ysize, depth, maxval,
237
0
                    kColorTypes[color.format.num_channels - 1]);
238
0
    JXL_RETURN_IF_ERROR(pos < kMaxHeaderSize);
239
0
    for (const auto& info : ec_info) {
240
0
      pos += snprintf(header + pos, kMaxHeaderSize - pos, "TUPLTYPE %s\n",
241
0
                      ExtraChannelTypeName(info.ec_info.type).c_str());
242
0
      JXL_RETURN_IF_ERROR(pos < kMaxHeaderSize);
243
0
    }
244
0
    pos += snprintf(header + pos, kMaxHeaderSize - pos, "ENDHDR\n");
245
0
    JXL_RETURN_IF_ERROR(pos < kMaxHeaderSize);
246
0
    size_t total_size = color.pixels_size;
247
0
    for (const auto& ec : frame.extra_channels) {
248
0
      total_size += ec.pixels_size;
249
0
    }
250
0
    bytes->resize(pos + total_size);
251
0
    memcpy(bytes->data(), header, pos);
252
    // If we have no extra channels, just copy color pixel data over.
253
0
    if (frame.extra_channels.empty()) {
254
0
      memcpy(bytes->data() + pos, reinterpret_cast<uint8_t*>(color.pixels()),
255
0
             color.pixels_size);
256
0
      return true;
257
0
    }
258
    // Interleave color and extra channels.
259
0
    const uint8_t* in = reinterpret_cast<const uint8_t*>(color.pixels());
260
0
    std::vector<const uint8_t*> ec_in(frame.extra_channels.size());
261
0
    for (size_t i = 0; i < frame.extra_channels.size(); ++i) {
262
0
      ec_in[i] =
263
0
          reinterpret_cast<const uint8_t*>(frame.extra_channels[i].pixels());
264
0
    }
265
0
    uint8_t* out = bytes->data() + pos;
266
0
    JXL_RETURN_IF_ERROR(PackedImage::ValidateDataType(color.format.data_type));
267
0
    size_t pwidth = PackedImage::BitsPerChannel(color.format.data_type) / 8;
268
0
    for (size_t y = 0; y < color.ysize; ++y) {
269
0
      for (size_t x = 0; x < color.xsize; ++x) {
270
0
        memcpy(out, in, color.pixel_stride());
271
0
        out += color.pixel_stride();
272
0
        in += color.pixel_stride();
273
0
        for (auto& p : ec_in) {
274
0
          memcpy(out, p, pwidth);
275
0
          out += pwidth;
276
0
          p += pwidth;
277
0
        }
278
0
      }
279
0
    }
280
0
    return true;
281
0
  }
282
  Status EncodeExtraChannel(const PackedImage& image, size_t bits_per_sample,
283
0
                            std::vector<uint8_t>* bytes) const override {
284
0
    return true;
285
0
  }
286
287
 private:
288
0
  static std::string ExtraChannelTypeName(JxlExtraChannelType type) {
289
0
    switch (type) {
290
0
      case JXL_CHANNEL_ALPHA:
291
0
        return std::string("Alpha");
292
0
      case JXL_CHANNEL_DEPTH:
293
0
        return std::string("Depth");
294
0
      case JXL_CHANNEL_SPOT_COLOR:
295
0
        return std::string("SpotColor");
296
0
      case JXL_CHANNEL_SELECTION_MASK:
297
0
        return std::string("SelectionMask");
298
0
      case JXL_CHANNEL_BLACK:
299
0
        return std::string("Black");
300
0
      case JXL_CHANNEL_CFA:
301
0
        return std::string("CFA");
302
0
      case JXL_CHANNEL_THERMAL:
303
0
        return std::string("Thermal");
304
0
      case JXL_CHANNEL_UNKNOWN:
305
0
        return std::string("Unknown");
306
0
      case JXL_CHANNEL_OPTIONAL:
307
0
        return std::string("Optional");
308
0
      default:
309
0
        return std::string("UNKNOWN");
310
0
    }
311
0
  }
312
};
313
314
}  // namespace
315
316
0
std::unique_ptr<Encoder> GetPPMEncoder() {
317
0
  return jxl::make_unique<PPMEncoder>();
318
0
}
319
320
0
std::unique_ptr<Encoder> GetPNMEncoder() {
321
0
  return jxl::make_unique<PNMEncoder>();
322
0
}
323
324
0
std::unique_ptr<Encoder> GetPFMEncoder() {
325
0
  return jxl::make_unique<PFMEncoder>();
326
0
}
327
328
0
std::unique_ptr<Encoder> GetPGMEncoder() {
329
0
  return jxl::make_unique<PGMEncoder>();
330
0
}
331
332
0
std::unique_ptr<Encoder> GetPAMEncoder() {
333
0
  return jxl::make_unique<PAMEncoder>();
334
0
}
335
336
}  // namespace extras
337
}  // namespace jxl