]> git.seodisparate.com - EN605.617.81.FA21_StephenSeo_DitheringProject/commitdiff
Impl PNG decoding and PPM saving
authorStephen Seo <seo.disparate@gmail.com>
Fri, 12 Nov 2021 03:13:46 +0000 (12:13 +0900)
committerStephen Seo <seo.disparate@gmail.com>
Fri, 12 Nov 2021 03:13:46 +0000 (12:13 +0900)
src/image.cc
src/image.h
src/main.cc

index 8f8d9b973007ee3532cc977d418c27d2c81483ae..05fc0d74294584b9252b535d86f81254fbdab50c 100644 (file)
@@ -2,6 +2,7 @@
 
 #include <array>
 #include <cstdio>
+#include <fstream>
 #include <iostream>
 
 #include <png.h>
@@ -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<unsigned char, 8> 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<png_const_bytep>(buf.data()), 0,
-                            8)) {
+    } else if (png_sig_cmp(reinterpret_cast<png_const_bytep>(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) {
index db5730a5a0aac8699a4fdb21d9dd83abddf0e419..6a30902292212bcc6c382251f01973e9181d9f9b 100644 (file)
@@ -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<uint8_t> data_;
   unsigned int width_;
index 5c2fa9bb6a78e27b752659a26b8562d1f33b90f2..4eb1d8273a3fea83d0976c8c2dc8f7b3d57d80aa 100644 (file)
@@ -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;
 }