Impl PNG decoding and PPM saving

This commit is contained in:
Stephen Seo 2021-11-12 12:13:46 +09:00
parent 7a8ad131ed
commit 5ff32ab42c
3 changed files with 166 additions and 10 deletions

View file

@ -2,6 +2,7 @@
#include <array> #include <array>
#include <cstdio> #include <cstdio>
#include <fstream>
#include <iostream> #include <iostream>
#include <png.h> #include <png.h>
@ -14,23 +15,37 @@ Image::Image(const std::string &filename)
: data_(), width_(0), height_(0), is_grayscale_(true) { : data_(), width_(0), height_(0), is_grayscale_(true) {
if (filename.compare(filename.size() - 4, filename.size(), ".png") == 0) { if (filename.compare(filename.size() - 4, filename.size(), ".png") == 0) {
// filename expected to be .png // filename expected to be .png
std::cout << "INFO: PNG filename extension detected, decoding..."
<< std::endl;
DecodePNG(filename); DecodePNG(filename);
} else if (filename.compare(filename.size() - 4, filename.size(), ".pgm") == } else if (filename.compare(filename.size() - 4, filename.size(), ".pgm") ==
0) { 0) {
// filename expected to be .pgm // filename expected to be .pgm
std::cout << "INFO: PGM filename extension detected, decoding..."
<< std::endl;
DecodePGM(filename); DecodePGM(filename);
} else if (filename.compare(filename.size() - 4, filename.size(), ".ppm") == } else if (filename.compare(filename.size() - 4, filename.size(), ".ppm") ==
0) { 0) {
// filename expected to be .ppm // filename expected to be .ppm
std::cout << "INFO: PPM filename extension detected, decoding..."
<< std::endl;
DecodePPM(filename); DecodePPM(filename);
} else { } else {
// unknown filename extension // unknown filename extension
std::cout << "ERROR: Unknown filename extension" << std::endl;
return; return;
} }
} }
bool Image::IsValid() const { 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(); } 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::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) { void Image::DecodePNG(const std::string &filename) {
FILE *file = std::fopen(filename.c_str(), "rb"); 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; std::array<unsigned char, 8> buf;
if (std::fread(buf.data(), 1, 8, file) != 8) { if (std::fread(buf.data(), 1, 8, file) != 8) {
std::fclose(file);
std::cout << "ERROR: File \"" << filename << "\" is smaller than 8 bytes" std::cout << "ERROR: File \"" << filename << "\" is smaller than 8 bytes"
<< std::endl; << std::endl;
std::fclose(file);
return; return;
} else if (!png_sig_cmp(reinterpret_cast<png_const_bytep>(buf.data()), 0, } else if (png_sig_cmp(reinterpret_cast<png_const_bytep>(buf.data()), 0,
8)) { 8) != 0) {
// not png file, do nothing // not png file, do nothing
std::fclose(file); std::fclose(file);
std::cout << "ERROR: File \"" << filename << "\" is not a png file"
<< std::endl;
return; return;
} }
} }
@ -109,17 +188,74 @@ void Image::DecodePNG(const std::string &filename) {
png_init_io(png_ptr, file); png_init_io(png_ptr, file);
// TODO BEGIN // TODO BEGIN
//// have libpng process the png data // have libpng process the png data
// png_read_png(png_ptr, png_info_ptr, PNG_TRANSFORM_IDENTITY, nullptr); png_read_png(png_ptr, png_info_ptr, PNG_TRANSFORM_IDENTITY, nullptr);
//// get rows of pixels from libpng // get image width (in pixels)
// png_bytep row_pointer = nullptr; width_ = png_get_image_width(png_ptr, png_info_ptr);
// png_read_row(png_ptr, row_pointer, nullptr);
// 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 // cleanup
png_destroy_read_struct(&png_ptr, &png_info_ptr, &png_end_info_ptr); 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) { void Image::DecodePGM(const std::string &filename) {

View file

@ -58,6 +58,20 @@ class Image {
/// Returns true if the image is grayscale. If false, then the image is RGBA. /// Returns true if the image is grayscale. If false, then the image is RGBA.
bool IsGrayscale() const; 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: private:
std::vector<uint8_t> data_; std::vector<uint8_t> data_;
unsigned int width_; unsigned int width_;

View file

@ -1,3 +1,9 @@
#include "image.h"
int main(int argc, char **argv) { int main(int argc, char **argv) {
Image image("testin.png");
image.SaveAsPPM("testout.ppm", true, true);
return 0; return 0;
} }