/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 | 0 | const JxlPixelFormat format = color.format; |
61 | 0 | const uint8_t* in = reinterpret_cast<const uint8_t*>(color.pixels()); |
62 | 0 | JXL_RETURN_IF_ERROR(PackedImage::ValidateDataType(format.data_type)); |
63 | 0 | size_t data_bits_per_sample = PackedImage::BitsPerChannel(format.data_type); |
64 | 0 | size_t bytes_per_sample = data_bits_per_sample / kBitsPerByte; |
65 | 0 | size_t num_samples = static_cast<size_t>(info.xsize) * info.ysize; |
66 | |
|
67 | 0 | if (info.bits_per_sample != data_bits_per_sample) { |
68 | 0 | return JXL_FAILURE("Bit depth does not match pixel data type"); |
69 | 0 | } |
70 | | |
71 | 0 | std::vector<uint8_t> pixels(num_samples * bytes_per_sample); |
72 | |
|
73 | 0 | if (format.data_type == JXL_TYPE_UINT8) { |
74 | 0 | memcpy(pixels.data(), in, num_samples * bytes_per_sample); |
75 | 0 | } else if (format.data_type == JXL_TYPE_UINT16) { |
76 | 0 | if (format.endianness != JXL_BIG_ENDIAN) { |
77 | 0 | const uint8_t* p_in = in; |
78 | 0 | uint8_t* p_out = pixels.data(); |
79 | 0 | for (size_t i = 0; i < num_samples; ++i, p_in += 2, p_out += 2) { |
80 | 0 | StoreBE16(LoadLE16(p_in), p_out); |
81 | 0 | } |
82 | 0 | } else { |
83 | 0 | memcpy(pixels.data(), in, num_samples * bytes_per_sample); |
84 | 0 | } |
85 | 0 | } else { |
86 | 0 | return JXL_FAILURE("Unsupported pixel data type"); |
87 | 0 | } |
88 | | |
89 | 0 | bytes->resize(static_cast<size_t>(header_size) + pixels.size()); |
90 | 0 | memcpy(bytes->data(), header, static_cast<size_t>(header_size)); |
91 | 0 | memcpy(bytes->data() + header_size, pixels.data(), pixels.size()); |
92 | |
|
93 | 0 | return true; |
94 | 0 | } |
95 | | |
96 | | class PGXEncoder : public Encoder { |
97 | | public: |
98 | 0 | std::vector<JxlPixelFormat> AcceptedFormats() const override { |
99 | 0 | std::vector<JxlPixelFormat> formats; |
100 | 0 | for (const JxlDataType data_type : {JXL_TYPE_UINT8, JXL_TYPE_UINT16}) { |
101 | 0 | for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) { |
102 | 0 | formats.push_back(JxlPixelFormat{/*num_channels=*/1, |
103 | 0 | /*data_type=*/data_type, |
104 | 0 | /*endianness=*/endianness, |
105 | 0 | /*align=*/0}); |
106 | 0 | } |
107 | 0 | } |
108 | 0 | return formats; |
109 | 0 | } |
110 | | Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image, |
111 | 0 | ThreadPool* pool) const override { |
112 | 0 | JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info)); |
113 | 0 | encoded_image->icc.assign(ppf.icc.begin(), ppf.icc.end()); |
114 | 0 | encoded_image->bitstreams.clear(); |
115 | 0 | encoded_image->bitstreams.reserve(ppf.frames.size()); |
116 | 0 | for (const auto& frame : ppf.frames) { |
117 | 0 | JXL_RETURN_IF_ERROR(VerifyPackedImage(frame.color, ppf.info)); |
118 | 0 | encoded_image->bitstreams.emplace_back(); |
119 | 0 | JXL_RETURN_IF_ERROR( |
120 | 0 | EncodeImagePGX(frame, ppf.info, &encoded_image->bitstreams.back())); |
121 | 0 | } |
122 | 0 | return true; |
123 | 0 | } |
124 | | }; |
125 | | |
126 | | } // namespace |
127 | | |
128 | 0 | std::unique_ptr<Encoder> GetPGXEncoder() { |
129 | 0 | return jxl::make_unique<PGXEncoder>(); |
130 | 0 | } |
131 | | |
132 | | } // namespace extras |
133 | | } // namespace jxl |