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