pnm.cc (12419B)
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 <cstddef> 9 #include <cstdint> 10 #include <cstring> 11 #include <memory> 12 #include <string> 13 #include <vector> 14 15 #include "lib/extras/packed_image.h" 16 #include "lib/jxl/base/common.h" 17 #include "lib/jxl/base/printf_macros.h" 18 #include "lib/jxl/base/status.h" 19 20 namespace jxl { 21 namespace extras { 22 namespace { 23 24 constexpr size_t kMaxHeaderSize = 2000; 25 26 class BasePNMEncoder : public Encoder { 27 public: 28 Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image, 29 ThreadPool* pool) const override { 30 JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info)); 31 if (!ppf.metadata.exif.empty() || !ppf.metadata.iptc.empty() || 32 !ppf.metadata.jumbf.empty() || !ppf.metadata.xmp.empty()) { 33 JXL_WARNING("PNM encoder ignoring metadata - use a different codec"); 34 } 35 encoded_image->icc = ppf.icc; 36 encoded_image->bitstreams.clear(); 37 encoded_image->bitstreams.reserve(ppf.frames.size()); 38 for (const auto& frame : ppf.frames) { 39 JXL_RETURN_IF_ERROR(VerifyPackedImage(frame.color, ppf.info)); 40 encoded_image->bitstreams.emplace_back(); 41 JXL_RETURN_IF_ERROR( 42 EncodeFrame(ppf, frame, &encoded_image->bitstreams.back())); 43 } 44 for (size_t i = 0; i < ppf.extra_channels_info.size(); ++i) { 45 const auto& ec_info = ppf.extra_channels_info[i].ec_info; 46 encoded_image->extra_channel_bitstreams.emplace_back(); 47 auto& ec_bitstreams = encoded_image->extra_channel_bitstreams.back(); 48 for (const auto& frame : ppf.frames) { 49 ec_bitstreams.emplace_back(); 50 JXL_RETURN_IF_ERROR(EncodeExtraChannel(frame.extra_channels[i], 51 ec_info.bits_per_sample, 52 &ec_bitstreams.back())); 53 } 54 } 55 return true; 56 } 57 58 protected: 59 virtual Status EncodeFrame(const PackedPixelFile& ppf, 60 const PackedFrame& frame, 61 std::vector<uint8_t>* bytes) const = 0; 62 virtual Status EncodeExtraChannel(const PackedImage& image, 63 size_t bits_per_sample, 64 std::vector<uint8_t>* bytes) const = 0; 65 }; 66 67 class PNMEncoder : public BasePNMEncoder { 68 public: 69 static const std::vector<JxlPixelFormat> kAcceptedFormats; 70 71 std::vector<JxlPixelFormat> AcceptedFormats() const override { 72 return kAcceptedFormats; 73 } 74 75 Status EncodeFrame(const PackedPixelFile& ppf, const PackedFrame& frame, 76 std::vector<uint8_t>* bytes) const override { 77 return EncodeImage(frame.color, ppf.info.bits_per_sample, bytes); 78 } 79 Status EncodeExtraChannel(const PackedImage& image, size_t bits_per_sample, 80 std::vector<uint8_t>* bytes) const override { 81 return EncodeImage(image, bits_per_sample, bytes); 82 } 83 84 private: 85 static Status EncodeImage(const PackedImage& image, size_t bits_per_sample, 86 std::vector<uint8_t>* bytes) { 87 uint32_t maxval = (1u << bits_per_sample) - 1; 88 char type = image.format.num_channels == 1 ? '5' : '6'; 89 char header[kMaxHeaderSize]; 90 size_t header_size = 91 snprintf(header, kMaxHeaderSize, "P%c\n%" PRIuS " %" PRIuS "\n%u\n", 92 type, image.xsize, image.ysize, maxval); 93 JXL_RETURN_IF_ERROR(header_size < kMaxHeaderSize); 94 bytes->resize(header_size + image.pixels_size); 95 memcpy(bytes->data(), header, header_size); 96 memcpy(bytes->data() + header_size, 97 reinterpret_cast<uint8_t*>(image.pixels()), image.pixels_size); 98 return true; 99 } 100 }; 101 102 class PGMEncoder : public PNMEncoder { 103 public: 104 static const std::vector<JxlPixelFormat> kAcceptedFormats; 105 106 std::vector<JxlPixelFormat> AcceptedFormats() const override { 107 return kAcceptedFormats; 108 } 109 }; 110 111 const std::vector<JxlPixelFormat> PGMEncoder::kAcceptedFormats = { 112 JxlPixelFormat{1, JXL_TYPE_UINT8, JXL_BIG_ENDIAN, 0}, 113 JxlPixelFormat{1, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}}; 114 115 class PPMEncoder : public PNMEncoder { 116 public: 117 static const std::vector<JxlPixelFormat> kAcceptedFormats; 118 119 std::vector<JxlPixelFormat> AcceptedFormats() const override { 120 return kAcceptedFormats; 121 } 122 }; 123 124 const std::vector<JxlPixelFormat> PPMEncoder::kAcceptedFormats = { 125 JxlPixelFormat{3, JXL_TYPE_UINT8, JXL_BIG_ENDIAN, 0}, 126 JxlPixelFormat{3, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}}; 127 128 const std::vector<JxlPixelFormat> PNMEncoder::kAcceptedFormats = [] { 129 std::vector<JxlPixelFormat> combined = PPMEncoder::kAcceptedFormats; 130 combined.insert(combined.end(), PGMEncoder::kAcceptedFormats.begin(), 131 PGMEncoder::kAcceptedFormats.end()); 132 return combined; 133 }(); 134 135 class PFMEncoder : public BasePNMEncoder { 136 public: 137 std::vector<JxlPixelFormat> AcceptedFormats() const override { 138 std::vector<JxlPixelFormat> formats; 139 for (const uint32_t num_channels : {1, 3}) { 140 for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) { 141 formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels, 142 /*data_type=*/JXL_TYPE_FLOAT, 143 /*endianness=*/endianness, 144 /*align=*/0}); 145 } 146 } 147 return formats; 148 } 149 Status EncodeFrame(const PackedPixelFile& ppf, const PackedFrame& frame, 150 std::vector<uint8_t>* bytes) const override { 151 return EncodeImage(frame.color, bytes); 152 } 153 Status EncodeExtraChannel(const PackedImage& image, size_t bits_per_sample, 154 std::vector<uint8_t>* bytes) const override { 155 return EncodeImage(image, bytes); 156 } 157 158 private: 159 static Status EncodeImage(const PackedImage& image, 160 std::vector<uint8_t>* bytes) { 161 char type = image.format.num_channels == 1 ? 'f' : 'F'; 162 double scale = image.format.endianness == JXL_LITTLE_ENDIAN ? -1.0 : 1.0; 163 char header[kMaxHeaderSize]; 164 size_t header_size = 165 snprintf(header, kMaxHeaderSize, "P%c\n%" PRIuS " %" PRIuS "\n%.1f\n", 166 type, image.xsize, image.ysize, scale); 167 JXL_RETURN_IF_ERROR(header_size < kMaxHeaderSize); 168 bytes->resize(header_size + image.pixels_size); 169 memcpy(bytes->data(), header, header_size); 170 const uint8_t* in = reinterpret_cast<const uint8_t*>(image.pixels()); 171 uint8_t* out = bytes->data() + header_size; 172 for (size_t y = 0; y < image.ysize; ++y) { 173 size_t y_out = image.ysize - 1 - y; 174 const uint8_t* row_in = &in[y * image.stride]; 175 uint8_t* row_out = &out[y_out * image.stride]; 176 memcpy(row_out, row_in, image.stride); 177 } 178 return true; 179 } 180 }; 181 182 class PAMEncoder : public BasePNMEncoder { 183 public: 184 std::vector<JxlPixelFormat> AcceptedFormats() const override { 185 std::vector<JxlPixelFormat> formats; 186 for (const uint32_t num_channels : {1, 2, 3, 4}) { 187 for (const JxlDataType data_type : {JXL_TYPE_UINT8, JXL_TYPE_UINT16}) { 188 formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels, 189 /*data_type=*/data_type, 190 /*endianness=*/JXL_BIG_ENDIAN, 191 /*align=*/0}); 192 } 193 } 194 return formats; 195 } 196 Status EncodeFrame(const PackedPixelFile& ppf, const PackedFrame& frame, 197 std::vector<uint8_t>* bytes) const override { 198 const PackedImage& color = frame.color; 199 const auto& ec_info = ppf.extra_channels_info; 200 JXL_RETURN_IF_ERROR(frame.extra_channels.size() == ec_info.size()); 201 for (const auto& ec : frame.extra_channels) { 202 if (ec.xsize != color.xsize || ec.ysize != color.ysize) { 203 return JXL_FAILURE("Extra channel and color size mismatch."); 204 } 205 if (ec.format.data_type != color.format.data_type || 206 ec.format.endianness != color.format.endianness) { 207 return JXL_FAILURE("Extra channel and color format mismatch."); 208 } 209 } 210 if (ppf.info.alpha_bits && 211 (ppf.info.bits_per_sample != ppf.info.alpha_bits)) { 212 return JXL_FAILURE("Alpha bit depth does not match image bit depth"); 213 } 214 for (const auto& it : ec_info) { 215 if (it.ec_info.bits_per_sample != ppf.info.bits_per_sample) { 216 return JXL_FAILURE( 217 "Extra channel bit depth does not match image bit depth"); 218 } 219 } 220 const char* kColorTypes[4] = {"GRAYSCALE", "GRAYSCALE_ALPHA", "RGB", 221 "RGB_ALPHA"}; 222 uint32_t maxval = (1u << ppf.info.bits_per_sample) - 1; 223 uint32_t depth = color.format.num_channels + ec_info.size(); 224 char header[kMaxHeaderSize]; 225 size_t pos = 0; 226 pos += snprintf(header + pos, kMaxHeaderSize - pos, 227 "P7\nWIDTH %" PRIuS "\nHEIGHT %" PRIuS 228 "\nDEPTH %u\n" 229 "MAXVAL %u\nTUPLTYPE %s\n", 230 color.xsize, color.ysize, depth, maxval, 231 kColorTypes[color.format.num_channels - 1]); 232 JXL_RETURN_IF_ERROR(pos < kMaxHeaderSize); 233 for (const auto& info : ec_info) { 234 pos += snprintf(header + pos, kMaxHeaderSize - pos, "TUPLTYPE %s\n", 235 ExtraChannelTypeName(info.ec_info.type).c_str()); 236 JXL_RETURN_IF_ERROR(pos < kMaxHeaderSize); 237 } 238 pos += snprintf(header + pos, kMaxHeaderSize - pos, "ENDHDR\n"); 239 JXL_RETURN_IF_ERROR(pos < kMaxHeaderSize); 240 size_t total_size = color.pixels_size; 241 for (const auto& ec : frame.extra_channels) { 242 total_size += ec.pixels_size; 243 } 244 bytes->resize(pos + total_size); 245 memcpy(bytes->data(), header, pos); 246 // If we have no extra channels, just copy color pixel data over. 247 if (frame.extra_channels.empty()) { 248 memcpy(bytes->data() + pos, reinterpret_cast<uint8_t*>(color.pixels()), 249 color.pixels_size); 250 return true; 251 } 252 // Interleave color and extra channels. 253 const uint8_t* in = reinterpret_cast<const uint8_t*>(color.pixels()); 254 std::vector<const uint8_t*> ec_in(frame.extra_channels.size()); 255 for (size_t i = 0; i < frame.extra_channels.size(); ++i) { 256 ec_in[i] = 257 reinterpret_cast<const uint8_t*>(frame.extra_channels[i].pixels()); 258 } 259 uint8_t* out = bytes->data() + pos; 260 JXL_RETURN_IF_ERROR(PackedImage::ValidateDataType(color.format.data_type)); 261 size_t pwidth = PackedImage::BitsPerChannel(color.format.data_type) / 8; 262 for (size_t y = 0; y < color.ysize; ++y) { 263 for (size_t x = 0; x < color.xsize; ++x) { 264 memcpy(out, in, color.pixel_stride()); 265 out += color.pixel_stride(); 266 in += color.pixel_stride(); 267 for (auto& p : ec_in) { 268 memcpy(out, p, pwidth); 269 out += pwidth; 270 p += pwidth; 271 } 272 } 273 } 274 return true; 275 } 276 Status EncodeExtraChannel(const PackedImage& image, size_t bits_per_sample, 277 std::vector<uint8_t>* bytes) const override { 278 return true; 279 } 280 281 private: 282 static std::string ExtraChannelTypeName(JxlExtraChannelType type) { 283 switch (type) { 284 case JXL_CHANNEL_ALPHA: 285 return std::string("Alpha"); 286 case JXL_CHANNEL_DEPTH: 287 return std::string("Depth"); 288 case JXL_CHANNEL_SPOT_COLOR: 289 return std::string("SpotColor"); 290 case JXL_CHANNEL_SELECTION_MASK: 291 return std::string("SelectionMask"); 292 case JXL_CHANNEL_BLACK: 293 return std::string("Black"); 294 case JXL_CHANNEL_CFA: 295 return std::string("CFA"); 296 case JXL_CHANNEL_THERMAL: 297 return std::string("Thermal"); 298 case JXL_CHANNEL_UNKNOWN: 299 return std::string("Unknown"); 300 case JXL_CHANNEL_OPTIONAL: 301 return std::string("Optional"); 302 default: 303 return std::string("UNKNOWN"); 304 } 305 } 306 }; 307 308 } // namespace 309 310 std::unique_ptr<Encoder> GetPPMEncoder() { 311 return jxl::make_unique<PPMEncoder>(); 312 } 313 314 std::unique_ptr<Encoder> GetPNMEncoder() { 315 return jxl::make_unique<PNMEncoder>(); 316 } 317 318 std::unique_ptr<Encoder> GetPFMEncoder() { 319 return jxl::make_unique<PFMEncoder>(); 320 } 321 322 std::unique_ptr<Encoder> GetPGMEncoder() { 323 return jxl::make_unique<PGMEncoder>(); 324 } 325 326 std::unique_ptr<Encoder> GetPAMEncoder() { 327 return jxl::make_unique<PAMEncoder>(); 328 } 329 330 } // namespace extras 331 } // namespace jxl