Coverage Report

Created: 2025-07-23 07:47

/src/libjxl/lib/extras/enc/encode.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/encode.h"
7
8
#include <jxl/codestream_header.h>
9
#include <jxl/types.h>
10
11
#include <algorithm>
12
#include <cctype>
13
#include <cstddef>
14
#include <cstdint>
15
#include <locale>
16
#include <memory>
17
#include <string>
18
#include <vector>
19
20
#include "lib/extras/enc/apng.h"
21
#include "lib/extras/enc/exr.h"
22
#include "lib/extras/enc/jpg.h"
23
#include "lib/extras/enc/npy.h"
24
#include "lib/extras/enc/pgx.h"
25
#include "lib/extras/enc/pnm.h"
26
#include "lib/extras/packed_image.h"
27
#include "lib/jxl/base/common.h"
28
#include "lib/jxl/base/data_parallel.h"
29
#include "lib/jxl/base/status.h"
30
31
namespace jxl {
32
namespace extras {
33
34
0
Status Encoder::VerifyBasicInfo(const JxlBasicInfo& info) {
35
0
  if (info.xsize == 0 || info.ysize == 0) {
36
0
    return JXL_FAILURE("Empty image");
37
0
  }
38
0
  if (info.num_color_channels != 1 && info.num_color_channels != 3) {
39
0
    return JXL_FAILURE("Invalid number of color channels");
40
0
  }
41
0
  if (info.alpha_bits > 0 && info.alpha_bits != info.bits_per_sample) {
42
0
    return JXL_FAILURE("Alpha bit depth does not match image bit depth");
43
0
  }
44
0
  if (info.orientation != JXL_ORIENT_IDENTITY) {
45
0
    return JXL_FAILURE("Orientation must be identity");
46
0
  }
47
0
  return true;
48
0
}
49
50
0
Status Encoder::VerifyFormat(const JxlPixelFormat& format) const {
51
0
  for (auto f : AcceptedFormats()) {
52
0
    if (f.num_channels != format.num_channels) continue;
53
0
    if (f.data_type != format.data_type) continue;
54
0
    if (f.data_type == JXL_TYPE_UINT8 || f.endianness == format.endianness) {
55
0
      return true;
56
0
    }
57
0
  }
58
0
  return JXL_FAILURE("Format is not in the list of accepted formats.");
59
0
}
60
61
Status Encoder::VerifyBitDepth(JxlDataType data_type, uint32_t bits_per_sample,
62
0
                               uint32_t exponent_bits) {
63
0
  if ((data_type == JXL_TYPE_UINT8 &&
64
0
       (bits_per_sample == 0 || bits_per_sample > 8 || exponent_bits != 0)) ||
65
0
      (data_type == JXL_TYPE_UINT16 &&
66
0
       (bits_per_sample <= 8 || bits_per_sample > 16 || exponent_bits != 0)) ||
67
0
      (data_type == JXL_TYPE_FLOAT16 &&
68
0
       (bits_per_sample > 16 || exponent_bits > 5))) {
69
0
    return JXL_FAILURE(
70
0
        "Incompatible data_type %d and bit depth %u with exponent bits %u",
71
0
        static_cast<int>(data_type), bits_per_sample, exponent_bits);
72
0
  }
73
0
  return true;
74
0
}
75
76
Status Encoder::VerifyImageSize(const PackedImage& image,
77
0
                                const JxlBasicInfo& info) {
78
0
  if (image.pixels() == nullptr) {
79
0
    return JXL_FAILURE("Invalid image.");
80
0
  }
81
0
  if (image.stride != image.xsize * image.pixel_stride()) {
82
0
    return JXL_FAILURE("Invalid image stride.");
83
0
  }
84
0
  if (image.pixels_size != image.ysize * image.stride) {
85
0
    return JXL_FAILURE("Invalid image size.");
86
0
  }
87
0
  size_t info_num_channels =
88
0
      (info.num_color_channels + (info.alpha_bits > 0 ? 1 : 0));
89
  // TODO(jon): frames do not necessarily have to match image size,
90
  // but probably this is still assumed in some encoders
91
0
  if (  // image.xsize != info.xsize || image.ysize != info.ysize ||
92
0
      image.format.num_channels != info_num_channels) {
93
0
    return JXL_FAILURE("Frame size does not match image size");
94
0
  }
95
0
  return true;
96
0
}
97
98
Status Encoder::VerifyPackedImage(const PackedImage& image,
99
0
                                  const JxlBasicInfo& info) const {
100
0
  JXL_RETURN_IF_ERROR(VerifyImageSize(image, info));
101
0
  JXL_RETURN_IF_ERROR(VerifyFormat(image.format));
102
0
  JXL_RETURN_IF_ERROR(VerifyBitDepth(image.format.data_type,
103
0
                                     info.bits_per_sample,
104
0
                                     info.exponent_bits_per_sample));
105
0
  return true;
106
0
}
107
108
template <int metadata>
109
class MetadataEncoder : public Encoder {
110
 public:
111
0
  std::vector<JxlPixelFormat> AcceptedFormats() const override {
112
0
    std::vector<JxlPixelFormat> formats;
113
    // empty, i.e. no need for actual pixel data
114
0
    return formats;
115
0
  }
Unexecuted instantiation: jxl::extras::MetadataEncoder<0>::AcceptedFormats() const
Unexecuted instantiation: jxl::extras::MetadataEncoder<1>::AcceptedFormats() const
Unexecuted instantiation: jxl::extras::MetadataEncoder<2>::AcceptedFormats() const
116
117
  Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded,
118
0
                ThreadPool* pool) const override {
119
0
    JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info));
120
0
    encoded->icc.clear();
121
0
    encoded->bitstreams.resize(1);
122
0
    if (metadata == 0) encoded->bitstreams.front() = ppf.metadata.exif;
123
0
    if (metadata == 1) encoded->bitstreams.front() = ppf.metadata.xmp;
124
0
    if (metadata == 2) encoded->bitstreams.front() = ppf.metadata.jumbf;
125
0
    return true;
126
0
  }
Unexecuted instantiation: jxl::extras::MetadataEncoder<0>::Encode(jxl::extras::PackedPixelFile const&, jxl::extras::EncodedImage*, jxl::ThreadPool*) const
Unexecuted instantiation: jxl::extras::MetadataEncoder<1>::Encode(jxl::extras::PackedPixelFile const&, jxl::extras::EncodedImage*, jxl::ThreadPool*) const
Unexecuted instantiation: jxl::extras::MetadataEncoder<2>::Encode(jxl::extras::PackedPixelFile const&, jxl::extras::EncodedImage*, jxl::ThreadPool*) const
127
};
128
129
0
std::unique_ptr<Encoder> Encoder::FromExtension(std::string extension) {
130
0
  std::transform(
131
0
      extension.begin(), extension.end(), extension.begin(),
132
0
      [](char c) { return std::tolower(c, std::locale::classic()); });
133
0
  if (extension == ".png" || extension == ".apng") return GetAPNGEncoder();
134
0
  if (extension == ".jpg") return GetJPEGEncoder();
135
0
  if (extension == ".jpeg") return GetJPEGEncoder();
136
0
  if (extension == ".npy") return GetNumPyEncoder();
137
0
  if (extension == ".pgx") return GetPGXEncoder();
138
0
  if (extension == ".pam") return GetPAMEncoder();
139
0
  if (extension == ".pgm") return GetPGMEncoder();
140
0
  if (extension == ".ppm") return GetPPMEncoder();
141
0
  if (extension == ".pnm") return GetPNMEncoder();
142
0
  if (extension == ".pfm") return GetPFMEncoder();
143
0
  if (extension == ".exr") return GetEXREncoder();
144
0
  if (extension == ".exif") return jxl::make_unique<MetadataEncoder<0>>();
145
0
  if (extension == ".xmp") return jxl::make_unique<MetadataEncoder<1>>();
146
0
  if (extension == ".xml") return jxl::make_unique<MetadataEncoder<1>>();
147
0
  if (extension == ".jumbf") return jxl::make_unique<MetadataEncoder<2>>();
148
0
  if (extension == ".jumb") return jxl::make_unique<MetadataEncoder<2>>();
149
150
0
  return nullptr;
151
0
}
152
153
0
std::string ListOfEncodeCodecs() {
154
0
  std::string list_of_codecs("PPM, PNM, PFM, PAM, PGX");
155
0
  if (GetAPNGEncoder()) list_of_codecs.append(", PNG, APNG");
156
0
  if (GetJPEGEncoder()) list_of_codecs.append(", JPEG");
157
0
  if (GetEXREncoder()) list_of_codecs.append(", EXR");
158
0
  return list_of_codecs;
159
0
}
160
161
}  // namespace extras
162
}  // namespace jxl