diff options
Diffstat (limited to 'lib/extras/enc/pnm.cc')
-rw-r--r-- | lib/extras/enc/pnm.cc | 340 |
1 files changed, 227 insertions, 113 deletions
diff --git a/lib/extras/enc/pnm.cc b/lib/extras/enc/pnm.cc index 9b5f6cb..4183900 100644 --- a/lib/extras/enc/pnm.cc +++ b/lib/extras/enc/pnm.cc @@ -5,7 +5,6 @@ #include "lib/extras/enc/pnm.h" -#include <stdio.h> #include <string.h> #include <string> @@ -14,12 +13,9 @@ #include "lib/extras/packed_image.h" #include "lib/jxl/base/byte_order.h" #include "lib/jxl/base/compiler_specific.h" -#include "lib/jxl/base/file_io.h" #include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/status.h" -#include "lib/jxl/color_management.h" #include "lib/jxl/dec_external_image.h" -#include "lib/jxl/enc_color_management.h" #include "lib/jxl/enc_external_image.h" #include "lib/jxl/enc_image_bundle.h" #include "lib/jxl/fields.h" // AllDefault @@ -32,66 +28,7 @@ namespace { constexpr size_t kMaxHeaderSize = 200; -Status EncodeHeader(const PackedImage& image, size_t bits_per_sample, - bool little_endian, char* header, int* chars_written) { - size_t num_channels = image.format.num_channels; - bool is_gray = num_channels <= 2; - bool has_alpha = num_channels == 2 || num_channels == 4; - if (has_alpha) { // PAM - if (bits_per_sample > 16) return JXL_FAILURE("PNM cannot have > 16 bits"); - const uint32_t max_val = (1U << bits_per_sample) - 1; - *chars_written = - snprintf(header, kMaxHeaderSize, - "P7\nWIDTH %" PRIuS "\nHEIGHT %" PRIuS - "\nDEPTH %u\nMAXVAL %u\nTUPLTYPE %s\nENDHDR\n", - image.xsize, image.ysize, is_gray ? 2 : 4, max_val, - is_gray ? "GRAYSCALE_ALPHA" : "RGB_ALPHA"); - JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) < - kMaxHeaderSize); - } else if (bits_per_sample == 32) { // PFM - const char type = is_gray ? 'f' : 'F'; - const double scale = little_endian ? -1.0 : 1.0; - *chars_written = - snprintf(header, kMaxHeaderSize, "P%c\n%" PRIuS " %" PRIuS "\n%.1f\n", - type, image.xsize, image.ysize, scale); - JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) < - kMaxHeaderSize); - } else { // PGM/PPM - if (bits_per_sample > 16) return JXL_FAILURE("PNM cannot have > 16 bits"); - const uint32_t max_val = (1U << bits_per_sample) - 1; - const char type = is_gray ? '5' : '6'; - *chars_written = - snprintf(header, kMaxHeaderSize, "P%c\n%" PRIuS " %" PRIuS "\n%u\n", - type, image.xsize, image.ysize, max_val); - JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) < - kMaxHeaderSize); - } - return true; -} - -Status EncodeImagePNM(const PackedImage& image, size_t bits_per_sample, - std::vector<uint8_t>* bytes) { - // Choose native for PFM; PGM/PPM require big-endian - bool is_little_endian = bits_per_sample > 16 && IsLittleEndian(); - char header[kMaxHeaderSize]; - int header_size = 0; - JXL_RETURN_IF_ERROR(EncodeHeader(image, bits_per_sample, is_little_endian, - header, &header_size)); - bytes->resize(static_cast<size_t>(header_size) + image.pixels_size); - memcpy(bytes->data(), header, static_cast<size_t>(header_size)); - const bool flipped_y = bits_per_sample == 32; // PFMs are flipped - const uint8_t* in = reinterpret_cast<const uint8_t*>(image.pixels()); - uint8_t* out = bytes->data() + header_size; - for (size_t y = 0; y < image.ysize; ++y) { - size_t y_out = flipped_y ? image.ysize - 1 - y : y; - const uint8_t* row_in = &in[y * image.stride]; - uint8_t* row_out = &out[y_out * image.stride]; - memcpy(row_out, row_in, image.stride); - } - return true; -} - -class PNMEncoder : public Encoder { +class BasePNMEncoder : public Encoder { public: Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image, ThreadPool* pool = nullptr) const override { @@ -106,8 +43,8 @@ class PNMEncoder : public Encoder { for (const auto& frame : ppf.frames) { JXL_RETURN_IF_ERROR(VerifyPackedImage(frame.color, ppf.info)); encoded_image->bitstreams.emplace_back(); - JXL_RETURN_IF_ERROR(EncodeImagePNM(frame.color, ppf.info.bits_per_sample, - &encoded_image->bitstreams.back())); + JXL_RETURN_IF_ERROR( + EncodeFrame(ppf, frame, &encoded_image->bitstreams.back())); } for (size_t i = 0; i < ppf.extra_channels_info.size(); ++i) { const auto& ec_info = ppf.extra_channels_info[i].ec_info; @@ -115,85 +52,258 @@ class PNMEncoder : public Encoder { auto& ec_bitstreams = encoded_image->extra_channel_bitstreams.back(); for (const auto& frame : ppf.frames) { ec_bitstreams.emplace_back(); - JXL_RETURN_IF_ERROR(EncodeImagePNM(frame.extra_channels[i], - ec_info.bits_per_sample, - &ec_bitstreams.back())); + JXL_RETURN_IF_ERROR(EncodeExtraChannel(frame.extra_channels[i], + ec_info.bits_per_sample, + &ec_bitstreams.back())); } } return true; } + + protected: + virtual Status EncodeFrame(const PackedPixelFile& ppf, + const PackedFrame& frame, + std::vector<uint8_t>* bytes) const = 0; + virtual Status EncodeExtraChannel(const PackedImage& image, + size_t bits_per_sample, + std::vector<uint8_t>* bytes) const = 0; }; +class PNMEncoder : public BasePNMEncoder { + public: + static const std::vector<JxlPixelFormat> kAcceptedFormats; + + std::vector<JxlPixelFormat> AcceptedFormats() const override { + return kAcceptedFormats; + } + + Status EncodeFrame(const PackedPixelFile& ppf, const PackedFrame& frame, + std::vector<uint8_t>* bytes) const override { + return EncodeImage(frame.color, ppf.info.bits_per_sample, bytes); + } + Status EncodeExtraChannel(const PackedImage& image, size_t bits_per_sample, + std::vector<uint8_t>* bytes) const override { + return EncodeImage(image, bits_per_sample, bytes); + } + + private: + Status EncodeImage(const PackedImage& image, size_t bits_per_sample, + std::vector<uint8_t>* bytes) const { + uint32_t maxval = (1u << bits_per_sample) - 1; + char type = image.format.num_channels == 1 ? '5' : '6'; + char header[kMaxHeaderSize]; + size_t header_size = + snprintf(header, kMaxHeaderSize, "P%c\n%" PRIuS " %" PRIuS "\n%u\n", + type, image.xsize, image.ysize, maxval); + JXL_RETURN_IF_ERROR(header_size < kMaxHeaderSize); + bytes->resize(header_size + image.pixels_size); + memcpy(bytes->data(), header, header_size); + memcpy(bytes->data() + header_size, + reinterpret_cast<uint8_t*>(image.pixels()), image.pixels_size); + return true; + } +}; + +class PGMEncoder : public PNMEncoder { + public: + static const std::vector<JxlPixelFormat> kAcceptedFormats; + + std::vector<JxlPixelFormat> AcceptedFormats() const override { + return kAcceptedFormats; + } +}; + +const std::vector<JxlPixelFormat> PGMEncoder::kAcceptedFormats = { + JxlPixelFormat{1, JXL_TYPE_UINT8, JXL_BIG_ENDIAN, 0}, + JxlPixelFormat{1, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}}; + class PPMEncoder : public PNMEncoder { public: + static const std::vector<JxlPixelFormat> kAcceptedFormats; + std::vector<JxlPixelFormat> AcceptedFormats() const override { - std::vector<JxlPixelFormat> formats; - for (const uint32_t num_channels : {1, 2, 3, 4}) { - for (const JxlDataType data_type : {JXL_TYPE_UINT8, JXL_TYPE_UINT16}) { - for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) { - formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels, - /*data_type=*/data_type, - /*endianness=*/endianness, - /*align=*/0}); - } - } - } - return formats; + return kAcceptedFormats; } }; -class PFMEncoder : public PNMEncoder { +const std::vector<JxlPixelFormat> PPMEncoder::kAcceptedFormats = { + JxlPixelFormat{3, JXL_TYPE_UINT8, JXL_BIG_ENDIAN, 0}, + JxlPixelFormat{3, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}}; + +const std::vector<JxlPixelFormat> PNMEncoder::kAcceptedFormats = [] { + std::vector<JxlPixelFormat> combined = PPMEncoder::kAcceptedFormats; + combined.insert(combined.end(), PGMEncoder::kAcceptedFormats.begin(), + PGMEncoder::kAcceptedFormats.end()); + return combined; +}(); + +class PFMEncoder : public BasePNMEncoder { public: std::vector<JxlPixelFormat> AcceptedFormats() const override { std::vector<JxlPixelFormat> formats; for (const uint32_t num_channels : {1, 3}) { - for (const JxlDataType data_type : {JXL_TYPE_FLOAT16, JXL_TYPE_FLOAT}) { - for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) { - formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels, - /*data_type=*/data_type, - /*endianness=*/endianness, - /*align=*/0}); - } + for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) { + formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels, + /*data_type=*/JXL_TYPE_FLOAT, + /*endianness=*/endianness, + /*align=*/0}); } } return formats; } -}; + Status EncodeFrame(const PackedPixelFile& ppf, const PackedFrame& frame, + std::vector<uint8_t>* bytes) const override { + return EncodeImage(frame.color, bytes); + } + Status EncodeExtraChannel(const PackedImage& image, size_t bits_per_sample, + std::vector<uint8_t>* bytes) const override { + return EncodeImage(image, bytes); + } -class PGMEncoder : public PPMEncoder { - public: - std::vector<JxlPixelFormat> AcceptedFormats() const override { - std::vector<JxlPixelFormat> formats = PPMEncoder::AcceptedFormats(); - for (auto it = formats.begin(); it != formats.end();) { - if (it->num_channels > 2) { - it = formats.erase(it); - } else { - ++it; - } + private: + Status EncodeImage(const PackedImage& image, + std::vector<uint8_t>* bytes) const { + char type = image.format.num_channels == 1 ? 'f' : 'F'; + double scale = image.format.endianness == JXL_LITTLE_ENDIAN ? -1.0 : 1.0; + char header[kMaxHeaderSize]; + size_t header_size = + snprintf(header, kMaxHeaderSize, "P%c\n%" PRIuS " %" PRIuS "\n%.1f\n", + type, image.xsize, image.ysize, scale); + JXL_RETURN_IF_ERROR(header_size < kMaxHeaderSize); + bytes->resize(header_size + image.pixels_size); + memcpy(bytes->data(), header, header_size); + const uint8_t* in = reinterpret_cast<const uint8_t*>(image.pixels()); + uint8_t* out = bytes->data() + header_size; + for (size_t y = 0; y < image.ysize; ++y) { + size_t y_out = image.ysize - 1 - y; + const uint8_t* row_in = &in[y * image.stride]; + uint8_t* row_out = &out[y_out * image.stride]; + memcpy(row_out, row_in, image.stride); } - return formats; + return true; } }; -class PAMEncoder : public PPMEncoder { +class PAMEncoder : public BasePNMEncoder { public: std::vector<JxlPixelFormat> AcceptedFormats() const override { - std::vector<JxlPixelFormat> formats = PPMEncoder::AcceptedFormats(); - for (auto it = formats.begin(); it != formats.end();) { - if (it->num_channels != 2 && it->num_channels != 4) { - it = formats.erase(it); - } else { - ++it; + std::vector<JxlPixelFormat> formats; + for (const uint32_t num_channels : {1, 2, 3, 4}) { + for (const JxlDataType data_type : {JXL_TYPE_UINT8, JXL_TYPE_UINT16}) { + formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels, + /*data_type=*/data_type, + /*endianness=*/JXL_BIG_ENDIAN, + /*align=*/0}); } } return formats; } -}; + Status EncodeFrame(const PackedPixelFile& ppf, const PackedFrame& frame, + std::vector<uint8_t>* bytes) const override { + const PackedImage& color = frame.color; + const auto& ec_info = ppf.extra_channels_info; + JXL_RETURN_IF_ERROR(frame.extra_channels.size() == ec_info.size()); + for (const auto& ec : frame.extra_channels) { + if (ec.xsize != color.xsize || ec.ysize != color.ysize) { + return JXL_FAILURE("Extra channel and color size mismatch."); + } + if (ec.format.data_type != color.format.data_type || + ec.format.endianness != color.format.endianness) { + return JXL_FAILURE("Extra channel and color format mismatch."); + } + } + if (ppf.info.alpha_bits && + (ppf.info.bits_per_sample != ppf.info.alpha_bits)) { + return JXL_FAILURE("Alpha bit depth does not match image bit depth"); + } + for (const auto& it : ec_info) { + if (it.ec_info.bits_per_sample != ppf.info.bits_per_sample) { + return JXL_FAILURE( + "Extra channel bit depth does not match image bit depth"); + } + } + const char* kColorTypes[4] = {"GRAYSCALE", "GRAYSCALE_ALPHA", "RGB", + "RGB_ALPHA"}; + uint32_t maxval = (1u << ppf.info.bits_per_sample) - 1; + uint32_t depth = color.format.num_channels + ec_info.size(); + char header[kMaxHeaderSize]; + size_t pos = 0; + pos += snprintf(header + pos, kMaxHeaderSize - pos, + "P7\nWIDTH %" PRIuS "\nHEIGHT %" PRIuS + "\nDEPTH %u\n" + "MAXVAL %u\nTUPLTYPE %s\n", + color.xsize, color.ysize, depth, maxval, + kColorTypes[color.format.num_channels - 1]); + JXL_RETURN_IF_ERROR(pos < kMaxHeaderSize); + for (const auto& info : ec_info) { + pos += snprintf(header + pos, kMaxHeaderSize - pos, "TUPLTYPE %s\n", + ExtraChannelTypeName(info.ec_info.type).c_str()); + JXL_RETURN_IF_ERROR(pos < kMaxHeaderSize); + } + pos += snprintf(header + pos, kMaxHeaderSize - pos, "ENDHDR\n"); + JXL_RETURN_IF_ERROR(pos < kMaxHeaderSize); + size_t total_size = color.pixels_size; + for (const auto& ec : frame.extra_channels) { + total_size += ec.pixels_size; + } + bytes->resize(pos + total_size); + memcpy(bytes->data(), header, pos); + // If we have no extra channels, just copy color pixel data over. + if (frame.extra_channels.empty()) { + memcpy(bytes->data() + pos, reinterpret_cast<uint8_t*>(color.pixels()), + color.pixels_size); + return true; + } + // Interleave color and extra channels. + const uint8_t* in = reinterpret_cast<const uint8_t*>(color.pixels()); + std::vector<const uint8_t*> ec_in(frame.extra_channels.size()); + for (size_t i = 0; i < frame.extra_channels.size(); ++i) { + ec_in[i] = + reinterpret_cast<const uint8_t*>(frame.extra_channels[i].pixels()); + } + uint8_t* out = bytes->data() + pos; + size_t pwidth = PackedImage::BitsPerChannel(color.format.data_type) / 8; + for (size_t y = 0; y < color.ysize; ++y) { + for (size_t x = 0; x < color.xsize; ++x) { + memcpy(out, in, color.pixel_stride()); + out += color.pixel_stride(); + in += color.pixel_stride(); + for (auto& p : ec_in) { + memcpy(out, p, pwidth); + out += pwidth; + p += pwidth; + } + } + } + return true; + } + Status EncodeExtraChannel(const PackedImage& image, size_t bits_per_sample, + std::vector<uint8_t>* bytes) const override { + return true; + } -Span<const uint8_t> MakeSpan(const char* str) { - return Span<const uint8_t>(reinterpret_cast<const uint8_t*>(str), - strlen(str)); -} + private: + static std::string ExtraChannelTypeName(JxlExtraChannelType type) { + switch (type) { + case JXL_CHANNEL_ALPHA: + return std::string("Alpha"); + case JXL_CHANNEL_DEPTH: + return std::string("Depth"); + case JXL_CHANNEL_SPOT_COLOR: + return std::string("SpotColor"); + case JXL_CHANNEL_SELECTION_MASK: + return std::string("SelectionMask"); + case JXL_CHANNEL_BLACK: + return std::string("Black"); + case JXL_CHANNEL_CFA: + return std::string("CFA"); + case JXL_CHANNEL_THERMAL: + return std::string("Thermal"); + default: + return std::string("UNKNOWN"); + } + } +}; } // namespace @@ -201,6 +311,10 @@ std::unique_ptr<Encoder> GetPPMEncoder() { return jxl::make_unique<PPMEncoder>(); } +std::unique_ptr<Encoder> GetPNMEncoder() { + return jxl::make_unique<PNMEncoder>(); +} + std::unique_ptr<Encoder> GetPFMEncoder() { return jxl::make_unique<PFMEncoder>(); } |