Coverage Report

Created: 2026-06-13 08:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libjxl/lib/extras/enc/pgx.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/extras/enc/pgx.h"
7
8
#include <jxl/codestream_header.h>
9
#include <jxl/types.h>
10
11
#include <cstdint>
12
#include <cstdio>
13
#include <cstring>
14
#include <memory>
15
#include <vector>
16
17
#include "lib/extras/enc/encode.h"
18
#include "lib/extras/packed_image.h"
19
#include "lib/jxl/base/byte_order.h"
20
#include "lib/jxl/base/common.h"
21
#include "lib/jxl/base/data_parallel.h"
22
#include "lib/jxl/base/status.h"
23
24
namespace jxl {
25
namespace extras {
26
namespace {
27
28
constexpr size_t kMaxHeaderSize = 200;
29
30
Status EncodeHeader(const JxlBasicInfo& info, char* header,
31
0
                    int* chars_written) {
32
0
  if (info.alpha_bits > 0) {
33
0
    return JXL_FAILURE("PGX: can't store alpha");
34
0
  }
35
0
  if (info.num_color_channels != 1) {
36
0
    return JXL_FAILURE("PGX: must be grayscale");
37
0
  }
38
  // TODO(lode): verify other bit depths: for other bit depths such as 1 or 4
39
  // bits, have a test case to verify it works correctly. For bits > 16, we may
40
  // need to change the way external_image works.
41
0
  if (info.bits_per_sample != 8 && info.bits_per_sample != 16) {
42
0
    return JXL_FAILURE("PGX: bits other than 8 or 16 not yet supported");
43
0
  }
44
45
  // Use ML (Big Endian), LM may not be well supported by all decoders.
46
0
  *chars_written = snprintf(header, kMaxHeaderSize, "PG ML + %u %u %u\n",
47
0
                            info.bits_per_sample, info.xsize, info.ysize);
48
0
  JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) <
49
0
                      kMaxHeaderSize);
50
0
  return true;
51
0
}
52
53
Status EncodeImagePGX(const PackedFrame& frame, const JxlBasicInfo& info,
54
0
                      std::vector<uint8_t>* bytes) {
55
0
  char header[kMaxHeaderSize];
56
0
  int header_size = 0;
57
0
  JXL_RETURN_IF_ERROR(EncodeHeader(info, header, &header_size));
58
59
0
  const PackedImage& color = frame.color;
60
  // num_samples below is derived from `info`, but pixel data is read from
61
  // `color.pixels()`, which is sized for the frame's actual dimensions.
62
  // VerifyImageSize (in encode.cc) only checks the frame's internal
63
  // consistency and channel count; it does not enforce that the frame
64
  // dimensions equal the basic-info dimensions. If `info` carries
65
  // dimensions larger than the frame (e.g. a cropped sub-frame in an
66
  // animated grayscale APNG, where ppf.info stores the full-canvas size
67
  // while each PackedFrame stores its viewport size), the memcpy below
68
  // would read past the end of the frame buffer.
69
0
  if (color.xsize != info.xsize || color.ysize != info.ysize) {
70
0
    return JXL_FAILURE("PGX: frame dimensions do not match basic info");
71
0
  }
72
0
  const JxlPixelFormat format = color.format;
73
0
  const uint8_t* in = reinterpret_cast<const uint8_t*>(color.pixels());
74
0
  JXL_RETURN_IF_ERROR(PackedImage::ValidateDataType(format.data_type));
75
0
  size_t data_bits_per_sample = PackedImage::BitsPerChannel(format.data_type);
76
0
  size_t bytes_per_sample = data_bits_per_sample / kBitsPerByte;
77
0
  size_t num_samples = static_cast<size_t>(info.xsize) * info.ysize;
78
79
0
  if (info.bits_per_sample != data_bits_per_sample) {
80
0
    return JXL_FAILURE("Bit depth does not match pixel data type");
81
0
  }
82
83
0
  std::vector<uint8_t> pixels(num_samples * bytes_per_sample);
84
85
0
  if (format.data_type == JXL_TYPE_UINT8) {
86
0
    memcpy(pixels.data(), in, num_samples * bytes_per_sample);
87
0
  } else if (format.data_type == JXL_TYPE_UINT16) {
88
0
    if (format.endianness != JXL_BIG_ENDIAN) {
89
0
      const uint8_t* p_in = in;
90
0
      uint8_t* p_out = pixels.data();
91
0
      for (size_t i = 0; i < num_samples; ++i, p_in += 2, p_out += 2) {
92
0
        StoreBE16(LoadLE16(p_in), p_out);
93
0
      }
94
0
    } else {
95
0
      memcpy(pixels.data(), in, num_samples * bytes_per_sample);
96
0
    }
97
0
  } else {
98
0
    return JXL_FAILURE("Unsupported pixel data type");
99
0
  }
100
101
0
  bytes->resize(static_cast<size_t>(header_size) + pixels.size());
102
0
  memcpy(bytes->data(), header, static_cast<size_t>(header_size));
103
0
  memcpy(bytes->data() + header_size, pixels.data(), pixels.size());
104
105
0
  return true;
106
0
}
107
108
class PGXEncoder : public Encoder {
109
 public:
110
0
  std::vector<JxlPixelFormat> AcceptedFormats() const override {
111
0
    std::vector<JxlPixelFormat> formats;
112
0
    for (const JxlDataType data_type : {JXL_TYPE_UINT8, JXL_TYPE_UINT16}) {
113
0
      for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) {
114
0
        formats.push_back(JxlPixelFormat{/*num_channels=*/1,
115
0
                                         /*data_type=*/data_type,
116
0
                                         /*endianness=*/endianness,
117
0
                                         /*align=*/0});
118
0
      }
119
0
    }
120
0
    return formats;
121
0
  }
122
  Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image,
123
0
                ThreadPool* pool) const override {
124
0
    JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info));
125
0
    encoded_image->icc.assign(ppf.icc.begin(), ppf.icc.end());
126
0
    encoded_image->bitstreams.clear();
127
0
    encoded_image->bitstreams.reserve(ppf.frames.size());
128
0
    for (const auto& frame : ppf.frames) {
129
0
      JXL_RETURN_IF_ERROR(VerifyPackedImage(frame.color, ppf.info));
130
0
      encoded_image->bitstreams.emplace_back();
131
0
      JXL_RETURN_IF_ERROR(
132
0
          EncodeImagePGX(frame, ppf.info, &encoded_image->bitstreams.back()));
133
0
    }
134
0
    return true;
135
0
  }
136
};
137
138
}  // namespace
139
140
0
std::unique_ptr<Encoder> GetPGXEncoder() {
141
0
  return jxl::make_unique<PGXEncoder>();
142
0
}
143
144
}  // namespace extras
145
}  // namespace jxl