Impl PNG decoding and PPM saving
This commit is contained in:
parent
7a8ad131ed
commit
5ff32ab42c
3 changed files with 166 additions and 10 deletions
156
src/image.cc
156
src/image.cc
|
@ -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) {
|
||||||
|
|
14
src/image.h
14
src/image.h
|
@ -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_;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue