/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 |