From 5ff32ab42c248fdfb328a80215f14589666d1a2b Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Fri, 12 Nov 2021 12:13:46 +0900 Subject: [PATCH] Impl PNG decoding and PPM saving --- src/image.cc | 156 +++++++++++++++++++++++++++++++++++++++++++++++---- src/image.h | 14 +++++ src/main.cc | 6 ++ 3 files changed, 166 insertions(+), 10 deletions(-) diff --git a/src/image.cc b/src/image.cc index 8f8d9b9..05fc0d7 100644 --- a/src/image.cc +++ b/src/image.cc @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -14,23 +15,37 @@ Image::Image(const std::string &filename) : data_(), width_(0), height_(0), is_grayscale_(true) { if (filename.compare(filename.size() - 4, filename.size(), ".png") == 0) { // filename expected to be .png + std::cout << "INFO: PNG filename extension detected, decoding..." + << std::endl; DecodePNG(filename); } else if (filename.compare(filename.size() - 4, filename.size(), ".pgm") == 0) { // filename expected to be .pgm + std::cout << "INFO: PGM filename extension detected, decoding..." + << std::endl; DecodePGM(filename); } else if (filename.compare(filename.size() - 4, filename.size(), ".ppm") == 0) { // filename expected to be .ppm + std::cout << "INFO: PPM filename extension detected, decoding..." + << std::endl; DecodePPM(filename); } else { // unknown filename extension + std::cout << "ERROR: Unknown filename extension" << std::endl; return; } } bool Image::IsValid() const { - return !data_.empty() && width_ > 0 && height_ > 0; + if (!data_.empty() && width_ > 0 && height_ > 0) { + if (is_grayscale_ && data_.size() == width_ * height_) { + return true; + } else if (!is_grayscale_ && data_.size() == 4 * width_ * height_) { + return true; + } + } + return false; } uint8_t *Image::GetData() { return data_.data(); } @@ -45,6 +60,68 @@ unsigned int Image::GetHeight() const { return height_; } bool Image::IsGrayscale() const { return is_grayscale_; } +bool Image::SaveAsPPM(const std::string &filename, bool overwrite, + bool packed) { + if (!IsValid()) { + std::cout << "ERROR: Image is not valid" << std::endl; + return false; + } + + if (!overwrite) { + std::ifstream ifs(filename); + if (ifs.is_open()) { + std::cout << "ERROR: file with name \"" << filename + << "\" already exists and overwite is not set to true" + << std::endl; + return false; + } + } + + std::ofstream ofs(filename); + if (packed) { + ofs << "P6\n" << width_ << ' ' << height_ << "\n255\n"; + for (unsigned int j = 0; j < height_; ++j) { + for (unsigned int i = 0; i < width_; ++i) { + if (is_grayscale_) { + for (unsigned int c = 0; c < 3; ++c) { + ofs.put(data_.at(i + j * width_)); + } + } else { + // data is stored as rgba, but ppm is rgb + for (unsigned int c = 0; c < 3; ++c) { + ofs.put(data_.at(c + i * 4 + j * width_ * 4)); + } + } + } + } + } else { + ofs << "P3\n" << width_ << ' ' << height_ << "\n255\n"; + for (unsigned int j = 0; j < height_; ++j) { + for (unsigned int i = 0; i < width_; ++i) { + if (is_grayscale_) { + int value = data_.at(i + j * width_); + for (unsigned int c = 0; c < 3; ++c) { + ofs << value << ' '; + } + } else { + // data is stored as rgba, but ppm is rgb + for (unsigned int c = 0; c < 3; ++c) { + int value = data_.at(c + i * 4 + j * width_ * 4); + ofs << value << ' '; + } + } + } + ofs << '\n'; + } + } + + return true; +} + +bool Image::SaveAsPPM(const char *filename, bool overwrite, bool packed) { + return SaveAsPPM(std::string(filename), overwrite, packed); +} + void Image::DecodePNG(const std::string &filename) { FILE *file = std::fopen(filename.c_str(), "rb"); @@ -52,14 +129,16 @@ void Image::DecodePNG(const std::string &filename) { { std::array buf; if (std::fread(buf.data(), 1, 8, file) != 8) { + std::fclose(file); std::cout << "ERROR: File \"" << filename << "\" is smaller than 8 bytes" << std::endl; - std::fclose(file); return; - } else if (!png_sig_cmp(reinterpret_cast(buf.data()), 0, - 8)) { + } else if (png_sig_cmp(reinterpret_cast(buf.data()), 0, + 8) != 0) { // not png file, do nothing std::fclose(file); + std::cout << "ERROR: File \"" << filename << "\" is not a png file" + << std::endl; return; } } @@ -109,17 +188,74 @@ void Image::DecodePNG(const std::string &filename) { png_init_io(png_ptr, file); // TODO BEGIN - //// have libpng process the png data - // png_read_png(png_ptr, png_info_ptr, PNG_TRANSFORM_IDENTITY, nullptr); + // have libpng process the png data + png_read_png(png_ptr, png_info_ptr, PNG_TRANSFORM_IDENTITY, nullptr); - //// get rows of pixels from libpng - // png_bytep row_pointer = nullptr; - // png_read_row(png_ptr, row_pointer, nullptr); + // get image width (in pixels) + width_ = png_get_image_width(png_ptr, png_info_ptr); - // TODO END + // get image height (in pixels) + height_ = png_get_image_height(png_ptr, png_info_ptr); + + // get channel count of image + unsigned int channels = png_get_channels(png_ptr, png_info_ptr); + if (channels == 1) { + is_grayscale_ = true; + } else { + is_grayscale_ = false; + } + + if (height_ > PNG_UINT_32_MAX) { + png_error(png_ptr, "Image is too tall to process in memory"); + } else if (width_ > PNG_UINT_32_MAX) { + png_error(png_ptr, "Image is too wide to process in memory"); + } + + png_byte **row_pointers = png_get_rows(png_ptr, png_info_ptr); + + data_.clear(); + if (channels == 3 || channels == 4) { + data_.reserve(width_ * 4 * height_); + } else if (channels == 1) { + data_.reserve(width_ * height_); + } else { + std::cout << "ERROR: PNG has invalid channel count == " << channels + << std::endl; + png_destroy_read_struct(&png_ptr, &png_info_ptr, &png_end_info_ptr); + return; + } + for (unsigned int y = 0; y < height_; ++y) { + for (unsigned int x = 0; x < width_; ++x) { + if (is_grayscale_) { + data_.push_back(row_pointers[y][x]); + } else if (channels == 3) { + for (unsigned int c = 0; c < channels; ++c) { + data_.push_back(row_pointers[y][x * channels + c]); + } + data_.push_back(255); + } else /* if (channels == 4) */ { + for (unsigned int c = 0; c < channels; ++c) { + data_.push_back(row_pointers[y][x * channels + c]); + } + } + } + } // cleanup png_destroy_read_struct(&png_ptr, &png_info_ptr, &png_end_info_ptr); + + // verify + if (is_grayscale_) { + if (data_.size() != width_ * height_) { + std::cout << "WARNING: data_.size() doesn't match width_ * height_" + << std::endl; + } + } else { + if (data_.size() != 4 * width_ * height_) { + std::cout << "WARNING: data_.size() doesn't match 4 * width_ * height_" + << std::endl; + } + } } void Image::DecodePGM(const std::string &filename) { diff --git a/src/image.h b/src/image.h index db5730a..6a30902 100644 --- a/src/image.h +++ b/src/image.h @@ -58,6 +58,20 @@ class Image { /// Returns true if the image is grayscale. If false, then the image is RGBA. bool IsGrayscale() const; + /*! + * \brief Saves the current image data as a PPM file. + * + * Returns false if the filename already exists and overwrite is false, or if + * saving failed. + * + * If packed is true, then the data is stored in binary format, otherwise the + * data is stored as ascii format. + */ + bool SaveAsPPM(const std::string &filename, bool overwrite, + bool packed = true); + /// Same as SaveAsPPM(const std::string &filename) + bool SaveAsPPM(const char *filename, bool overwrite, bool packed = true); + private: std::vector data_; unsigned int width_; diff --git a/src/main.cc b/src/main.cc index 5c2fa9b..4eb1d82 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1,3 +1,9 @@ +#include "image.h" + int main(int argc, char **argv) { + Image image("testin.png"); + + image.SaveAsPPM("testout.ppm", true, true); + return 0; }