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