Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added code for reading and writing pngs #14

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,25 @@ project(gillwald-geometry)
add_compile_options(-std=c++17 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-missing-field-initializers)

find_package(GTest REQUIRED)
include_directories(include ${GTEST_INCLUDE_DIRS})
include_directories(third_party include ${GTEST_INCLUDE_DIRS})
add_library(liblodepng third_party/lodepng/lodepng.cpp)

add_library(gillwald_geometry src/geometry.cpp)

target_link_libraries(gillwald_geometry liblodepng)

add_executable(geometry_tests test/geometry_tests.cpp)
target_link_libraries(geometry_tests
${GTEST_LIBRARIES}
gmock
pthread
gillwald_geometry)

# include_directories(third_party)
add_library(gillwald_pngEncodeDecode src/pngEncodeDecode.cpp third_party/lodepng/lodepng.cpp)
add_executable(pngEncodeDecode_tests test/pngEncodeDecode_tests.cpp)
target_link_libraries(pngEncodeDecode_tests
${GTEST_LIBRARIES}
gmock
pthread
gillwald_pngEncodeDecode)
50 changes: 50 additions & 0 deletions include/util/pngEncodeDecode.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#ifndef PNGENCODEDECODE_H
#define PNGENCODEDECODE_H

// C++ standard headers
#include <vector>

// For Cell definition
#include <gillwald/geometry.hpp>

namespace geometry{

namespace utility {

class Image {

public:

// Constructs an image object that takes in the raw image data, width, and height of the image
Image(std::vector<unsigned char> image, unsigned width, unsigned height): imageData_(image), width_(width), height_(height) {};

// Constructs an image object that reads a png file and updates the object fields with the information
Image(const char* filename){this->decode(filename);};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's have the header be declaration only


// TODO: Casting operator for data to eigin matrix
// operator Eigen::MatrixXi() const {};

// Returns a vector of cells that indicate the pixel values that are greater than threshVal
std::vector<Cell> threshold_Cell(int threshVal);

bool encode(const char* filename);

void decode(const char* filename);

std::vector<unsigned char> getImageData() {return this->imageData_;};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need the this-> style with underscored members

bool getDecodeStatus() {return this->decodeFail_;}
const unsigned& width(){return this->width_;};
const unsigned& height(){return this->height_;};

private:

std::vector<unsigned char> imageData_;
unsigned width_;
unsigned height_;
bool decodeFail_ = false;

};

};
};
#endif
51 changes: 51 additions & 0 deletions src/pngEncodeDecode.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#include <util/pngEncodeDecode.hpp>
#include <iostream>

// Lodepng file
#include <lodepng/lodepng.h>

namespace geometry {

namespace utility {

std::vector<Cell> Image::threshold_Cell(int threshVal) {
std::vector<Cell> threshVec;
int count = 0;
for (const auto& pixel : this->imageData_) {
if (pixel >= threshVal) {
threshVec.push_back({(count % (int)(this->width_)),(count / (int)(this->width_))});
}
count++;
}
return threshVec;
}

bool Image::encode(const char* filename) {
//Encode the image
// This will encode a grayscale image. Image is expected to be size width*height.
// Each value of image is expected to be from 0-255, and it rolls over
unsigned error = lodepng::encode(filename, this->imageData_, this->width_, this->height_, LCT_GREY, 8);

//if there's an error, display it
if (error) {
std::cout << "encoder error " << error << ": "<< lodepng_error_text(error) << std::endl;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove logging statements. Result is observable through return value.

return false;
} else {
return true;
}
}

void Image::decode(const char* filename) {
// decode the image as a grayscale vector of pixels, where each pixel ranges from 0-255x
unsigned error = lodepng::decode(this->imageData_, this->width_, this->height_, filename, LCT_GREY, 8);

//if there's an error, display it and return false
if (error) {
this->decodeFail_ = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to remember the result. Just return a bool as in with the encode function

std::cout << "decoder error " << error << ": " << lodepng_error_text(error) << std::endl;
}
}

};

}
176 changes: 176 additions & 0 deletions test/pngEncodeDecode_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// C++ Standard Library
#include <vector>
#include <iostream>

// C Standard Library
#include <stdio.h>

// UNIX stuff?
#include <sys/stat.h>

// Gtest
#include <gtest/gtest.h>
#include <gmock/gmock.h>

// code to test
#include <util/pngEncodeDecode.hpp>

using ::testing::ElementsAreArray;

namespace geometry {

/**
* @brief Overloads the << operator to print out the Cells in a vector of cells
* @param os The ostream object
* @param pixels The vector of cells to be printed
* @returns reference to the ostream object
*/
std::ostream& operator<<(std::ostream& os, const std::vector<geometry::Cell>& pixels){
os << "\n";
for (const auto& pixel:pixels) {
os << "(" << pixel.x << "," << pixel.y << ")\n";
}
os << "\n";
return os;
}

/**
* @brief Overloads the == operator to allow equality check between two Cell objects
* @param lhs The cell on the left side of the == operator
* @param rhs The cell on the right side of the == operator
* @returns boolean on if the x and y values of the Cell are equal
*/
bool operator==(const Cell &lhs,const Cell &rhs) {
return (lhs.x == rhs.x) && (lhs.y == rhs.y);
}

namespace utility {

TEST(encode_test, file_existence){
// GIVEN a file name
const char* filename = "test.png";

// PREPARE by checking if the file exists, and if so deleting the file
struct stat buffer;
if (stat ("test.png", &buffer) == 0) {system("rm ./test.png");}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use
https://en.cppreference.com/w/cpp/io/c/tmpfile
instead.
Do your best to adhere to RAII principles


// WHEN an image is encoded
std::vector<unsigned char> imageData;
unsigned width = 2, height = 2;
imageData.resize(width * height);
for (unsigned y = 0; y < height; y++) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fill with zeros

for (unsigned x = 0; x < width; x++) {
imageData[(y*width) + x] = ((x+y)*64);
}
}

Image testImage(imageData, height, width);

const auto encFlag = testImage.encode(filename);

// THEN check to see if the image was properly written
ASSERT_TRUE(encFlag);

}

TEST(encode_decode_test, read_write) {
// GIVEN a file name and some image data
const char* filename = "test.png";

std::vector<unsigned char> imageData;
unsigned width = 128, height = 4;
imageData.resize(width * height);
int count = 0;
for (unsigned y = 0; y < height; y++) {
for (unsigned x = 0; x < width; x++) {
imageData[count] = count % 256;
count++;
}
}

// PREPARE by checking if the file exists, and if so deleting the file
struct stat buffer;
if (stat ("test.png", &buffer) == 0) {system("rm ./test.png");}

// WHEN an image is succesfully encoded
Image encodeImage(imageData, width, height);

const auto encFlag = encodeImage.encode(filename);

// THEN check to see if the image was properly written
ASSERT_TRUE(encFlag);

// If the image was properly written then decode it
Image decodeImage(filename);

// Check to see if the image was properly decoded
ASSERT_FALSE(decodeImage.getDecodeStatus());

// Width check
EXPECT_EQ(decodeImage.width(), 128);

// Height check
EXPECT_EQ(decodeImage.height(), 4);

// Check to see if the decoded data matches the original data
EXPECT_THAT(imageData, ElementsAreArray(decodeImage.getImageData()));

}

TEST(encode_decode_test, threshold_check) {
// GIVEN a file name and some image data
const char* filename = "test.png";

unsigned width = 8, height = 8;
std::vector<unsigned char> image;
int bytesPerPix = 1;
image.resize(width * height * bytesPerPix); // 1 bytes per pixel
int count = 0;
for(unsigned y = 0; y < height; y++) {
for(unsigned x = 0; x < width; x++) {
count++;
image[(bytesPerPix * width * y) + (bytesPerPix * x) + 0] = x*y*4 - 1; // most significant
}
}

// PREPARE by checking if the file exists, and if so deleting the file
struct stat buffer;
if (stat ("test.png", &buffer) == 0) {system("rm ./test.png");}

// WHEN an image is succesfully encoded
Image encodeImage(image, width, height);

const auto encFlag = encodeImage.encode(filename);

// THEN check to see if the image was properly written
ASSERT_TRUE(encFlag);

// If the image was properly written then decode it
Image decodeImage(filename);

// Check to see if the image was properly decoded
ASSERT_FALSE(decodeImage.getDecodeStatus());

// Width check
EXPECT_EQ(decodeImage.width(), 8);

// Height check
EXPECT_EQ(decodeImage.height(), 8);

// Check the cells returned by the threshold check
const int threshVal = 139;
const std::vector<Cell> expected{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}, {7, 0}, {0, 1}, {0, 2}, {0, 3}, {0, 4}, {0, 5}, {7, 5}, {0, 6}, {6, 6}, {7, 6}, {0, 7}, {5, 7}, {6, 7}, {7, 7}};
EXPECT_THAT(decodeImage.threshold_Cell(threshVal), ElementsAreArray(expected));

}

};

};

int main(int argc, char **argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();

return 0;
}
Loading