summaryrefslogtreecommitdiff
path: root/lib/extras/enc/pnm.cc
diff options
context:
space:
mode:
Diffstat (limited to 'lib/extras/enc/pnm.cc')
-rw-r--r--lib/extras/enc/pnm.cc340
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>();
}