diff --git a/.gitmodules b/.gitmodules index e49f5b2..5db3c92 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,12 @@ [submodule "homeworks/homework_4/no_strings_attached/external/googletest"] path = homeworks/homework_4/no_strings_attached/external/googletest url = https://github.com/google/googletest.git +[submodule "homeworks/homework_5/pixelator/external/ftxui"] + path = homeworks/homework_5/pixelator/external/ftxui + url = https://github.com/ArthurSonzogni/FTXUI.git +[submodule "homeworks/homework_5/pixelator/external/googletest"] + path = homeworks/homework_5/pixelator/external/googletest + url = https://github.com/google/googletest.git +[submodule "homeworks/homework_5/pixelator/external/stb"] + path = homeworks/homework_5/pixelator/external/stb + url = https://github.com/nothings/stb.git diff --git a/homeworks/homework_5/pixelator/CMakeLists.txt b/homeworks/homework_5/pixelator/CMakeLists.txt new file mode 100644 index 0000000..f213f7a --- /dev/null +++ b/homeworks/homework_5/pixelator/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.16..3.24) +project(pixelator VERSION 1 + DESCRIPTION "Pixelate images in terminal" + LANGUAGES CXX) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE) +endif() + +message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") + +# Create a pseudo-library to propagate the needed flags. +add_library(cxx_setup INTERFACE) +target_compile_options(cxx_setup INTERFACE -Wall -Wpedantic -Wextra) +target_compile_features(cxx_setup INTERFACE cxx_std_17) +target_include_directories(cxx_setup INTERFACE ${PROJECT_SOURCE_DIR}) + +# Update the submodules here +include(external/UpdateSubmodules.cmake) + +# Enable testing for this project +include(CTest) + +# Add code in subdirectories +add_subdirectory(external) +add_subdirectory(${PROJECT_NAME}) +add_subdirectory(examples) diff --git a/homeworks/homework_5/pixelator/examples/CMakeLists.txt b/homeworks/homework_5/pixelator/examples/CMakeLists.txt new file mode 100644 index 0000000..8dbd95e --- /dev/null +++ b/homeworks/homework_5/pixelator/examples/CMakeLists.txt @@ -0,0 +1,11 @@ +add_executable(use_ftxui use_ftxui.cpp) +target_link_libraries(use_ftxui PRIVATE cxx_setup ftxui::screen) + +add_executable(use_stb_image use_stb_image.cpp) +target_link_libraries(use_stb_image PRIVATE cxx_setup stb::stb) + +add_executable(pixelate pixelate.cpp) +target_link_libraries(pixelate PRIVATE cxx_setup stb_image_data_view drawer pixelate_image) + +# TODO: Add your binaries here +# There must be a binary `pixelate` here diff --git a/homeworks/homework_5/pixelator/examples/pixelate.cpp b/homeworks/homework_5/pixelator/examples/pixelate.cpp new file mode 100644 index 0000000..3f67ff4 --- /dev/null +++ b/homeworks/homework_5/pixelator/examples/pixelate.cpp @@ -0,0 +1,28 @@ +#include "pixelator/drawer.hpp" +#include "pixelator/pixelate_image.hpp" +#include "pixelator/stb_image_data_view.hpp" + +#include +#include +#include +namespace { +using pixelator::Drawer; +using pixelator::PixelateImage; +using pixelator::StbImageDataView; +} // namespace + +int main(int argc, char **argv) { + if (argc < 2) { std::cerr << "No image provided." << std::endl; } + + StbImageDataView image{argv[1]}; + if (image.empty()) { + std::cerr << "Image could not be loaded" << std::endl; + exit(1); + } + + Drawer drawer{ftxui::Dimension::Full()}; + + drawer.Set(PixelateImage(image, drawer.size())); + drawer.Draw(); + return 0; +} diff --git a/homeworks/homework_5/pixelator/examples/use_ftxui.cpp b/homeworks/homework_5/pixelator/examples/use_ftxui.cpp new file mode 100644 index 0000000..312f727 --- /dev/null +++ b/homeworks/homework_5/pixelator/examples/use_ftxui.cpp @@ -0,0 +1,19 @@ +#include "ftxui/screen/color.hpp" +#include "ftxui/screen/screen.hpp" + +namespace { +const ftxui::Color kYellowishColor = ftxui::Color::RGB(255, 200, 100); +} + +int main() { + const ftxui::Dimensions dimensions{ftxui::Dimension::Full()}; + ftxui::Screen screen{ftxui::Screen::Create(dimensions)}; + auto &pixel_left = screen.PixelAt(10, 10); + pixel_left.background_color = kYellowishColor; + pixel_left.character = ' '; + auto &pixel_right = screen.PixelAt(11, 10); + pixel_right.background_color = kYellowishColor; + pixel_right.character = ' '; + screen.Print(); + return 0; +} diff --git a/homeworks/homework_5/pixelator/examples/use_stb_image.cpp b/homeworks/homework_5/pixelator/examples/use_stb_image.cpp new file mode 100644 index 0000000..f32c059 --- /dev/null +++ b/homeworks/homework_5/pixelator/examples/use_stb_image.cpp @@ -0,0 +1,64 @@ +// Make sure to have this in EXACTLY one cpp file +// The best place for this is the cpp file of your library +// that holds a class that wraps around the stb_image data +// For more see here: https://github.com/nothings/stb#faq +#define STB_IMAGE_IMPLEMENTATION +#include "stb/stb_image.h" + +#include +#include + +namespace { +static constexpr auto kLoadAllChannels{0}; + +// A dummy color structure. Use ftxui::Color in actual code. +struct Color { + int red; + int green; + int blue; +}; + +} // namespace + +int main(int argc, char **argv) { + if (argc < 2) { std::cerr << "No image provided.\n"; } + const std::filesystem::path image_path{argv[1]}; + if (!std::filesystem::exists(image_path)) { + std::cerr << "No image file: " << image_path << std::endl; + std::exit(1); + } + + // Load the data + int rows{}; + int cols{}; + int channels{}; + // This call also populates rows, cols, channels. + auto image_data{ + stbi_load(image_path.c_str(), &cols, &rows, &channels, kLoadAllChannels)}; + std::cout << "Loaded image of size: [" << rows << ", " << cols << "] with " + << channels << " channels\n"; + if (!image_data) { + std::cerr << "Failed to load image data from file: " << image_path + << std::endl; + std::exit(1); + } + + // The data is stored sequentially, in this order per pixel: red, green, blue, + // alpha This patterns repeats for every pixel of the image, so the resulting + // data layout is: [rgbargbargba...] + int query_row = 10; + int query_col = 15; + const auto index{channels * (query_row * cols + query_col)}; + const Color color{ + image_data[index], image_data[index + 1], image_data[index + 2]}; + std::cout << "Color at pixel: [" << query_row << ", " << query_col + << "] = RGB: (" << color.red << ", " << color.green << ", " + << color.blue << ")\n"; + + // We must explicitly free the memory allocated for this image. + // The reason for this is that stb_image is a C library, + // which has no classes and no RAII in the form about which we talked before. + // Now you see why people want to write C++ and not C? ;) + stbi_image_free(image_data); + return 0; +} diff --git a/homeworks/homework_5/pixelator/external/CMakeLists.txt b/homeworks/homework_5/pixelator/external/CMakeLists.txt new file mode 100644 index 0000000..76e97b4 --- /dev/null +++ b/homeworks/homework_5/pixelator/external/CMakeLists.txt @@ -0,0 +1,3 @@ +include(gtest.cmake) +include(ftxui.cmake) +include(stb.cmake) diff --git a/homeworks/homework_5/pixelator/external/UpdateSubmodules.cmake b/homeworks/homework_5/pixelator/external/UpdateSubmodules.cmake new file mode 100644 index 0000000..53ecc98 --- /dev/null +++ b/homeworks/homework_5/pixelator/external/UpdateSubmodules.cmake @@ -0,0 +1,27 @@ +find_package(Git QUIET) + +if(GIT_FOUND) + option(UPDATE_SUBMODULES "Check submodules during build" ON) + + if(NOT UPDATE_SUBMODULES) + return() + endif() + + execute_process(COMMAND ${GIT_EXECUTABLE} submodule + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + OUTPUT_VARIABLE EXISTING_SUBMODULES + RESULT_VARIABLE RETURN_CODE) + + message(STATUS "Updating git submodules:\n${EXISTING_SUBMODULES}") + + execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE RETURN_CODE) + + if(NOT RETURN_CODE EQUAL "0") + message(WARNING "Cannot update submodules. Git command failed with ${RETURN_CODE}.") + return() + endif() + + message(STATUS "Git submodules updated successfully.") +endif() diff --git a/homeworks/homework_5/pixelator/external/ftxui b/homeworks/homework_5/pixelator/external/ftxui new file mode 160000 index 0000000..e03a079 --- /dev/null +++ b/homeworks/homework_5/pixelator/external/ftxui @@ -0,0 +1 @@ +Subproject commit e03a0797be167e12a3809cd45c95ad86a134e832 diff --git a/homeworks/homework_5/pixelator/external/ftxui.cmake b/homeworks/homework_5/pixelator/external/ftxui.cmake new file mode 100644 index 0000000..66590db --- /dev/null +++ b/homeworks/homework_5/pixelator/external/ftxui.cmake @@ -0,0 +1,4 @@ +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +add_subdirectory(ftxui) diff --git a/homeworks/homework_5/pixelator/external/googletest b/homeworks/homework_5/pixelator/external/googletest new file mode 160000 index 0000000..5197b1a --- /dev/null +++ b/homeworks/homework_5/pixelator/external/googletest @@ -0,0 +1 @@ +Subproject commit 5197b1a8e6a1ef9f214f4aa537b0be17cbf91946 diff --git a/homeworks/homework_5/pixelator/external/gtest.cmake b/homeworks/homework_5/pixelator/external/gtest.cmake new file mode 100644 index 0000000..badab93 --- /dev/null +++ b/homeworks/homework_5/pixelator/external/gtest.cmake @@ -0,0 +1,5 @@ +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +set(INSTALL_GTEST OFF) +add_subdirectory(googletest) diff --git a/homeworks/homework_5/pixelator/external/stb b/homeworks/homework_5/pixelator/external/stb new file mode 160000 index 0000000..ae721c5 --- /dev/null +++ b/homeworks/homework_5/pixelator/external/stb @@ -0,0 +1 @@ +Subproject commit ae721c50eaf761660b4f90cc590453cdb0c2acd0 diff --git a/homeworks/homework_5/pixelator/external/stb.cmake b/homeworks/homework_5/pixelator/external/stb.cmake new file mode 100644 index 0000000..48b5676 --- /dev/null +++ b/homeworks/homework_5/pixelator/external/stb.cmake @@ -0,0 +1,5 @@ +add_library(stb INTERFACE) +target_include_directories(stb INTERFACE ${PROJECT_SOURCE_DIR}/external/) +if (NOT TARGET stb::stb) + add_library(stb::stb ALIAS stb) +endif() diff --git a/homeworks/homework_5/pixelator/pixelator/CMakeLists.txt b/homeworks/homework_5/pixelator/pixelator/CMakeLists.txt new file mode 100644 index 0000000..6acfe30 --- /dev/null +++ b/homeworks/homework_5/pixelator/pixelator/CMakeLists.txt @@ -0,0 +1,46 @@ +# TODO: Add your libraries here +# You must have the following libraries: +# - stb_image_data_view +# - drawer +# - image +# - pixelate_image + +add_library(stb_image_data_view stb_image_data_view.cpp) +target_link_libraries(stb_image_data_view PUBLIC cxx_setup stb::stb ftxui::screen) + +add_library(image image.cpp) +target_link_libraries(image PUBLIC cxx_setup ftxui::screen stb_image_data_view) + +add_library(drawer drawer.cpp) +target_link_libraries(drawer PUBLIC cxx_setup ftxui::screen image) + +add_library(pixelate_image INTERFACE) +target_link_libraries(pixelate_image INTERFACE image stb_image_data_view) + +add_library(test_global_variables INTERFACE) +target_link_libraries(test_global_variables INTERFACE image) + + +if(BUILD_TESTING) + # TODO: Add your tests executable with all your tests here + # Name it pixelator_tests. + # Also use gtest_discover_tests(pixelator_tests) + # to register them with CTest + add_executable(pixelate_image_test pixelate_image_test.cpp) + target_link_libraries(pixelate_image_test PRIVATE pixelate_image test_global_variables GTest::gtest_main) + + add_executable(drawer_test drawer_test.cpp) + target_link_libraries(drawer_test PRIVATE drawer GTest::gtest_main) + + add_executable(image_test image_test.cpp) + target_link_libraries(image_test PRIVATE image GTest::gtest_main) + + add_executable(stb_image_data_view_test stb_image_data_view_test.cpp) + target_link_libraries(stb_image_data_view_test PRIVATE stb_image_data_view test_global_variables GTest::gtest_main) + + include(GoogleTest) + gtest_discover_tests(pixelate_image_test) + gtest_discover_tests(drawer_test) + gtest_discover_tests(image_test) + gtest_discover_tests(stb_image_data_view_test) +endif() diff --git a/homeworks/homework_5/pixelator/pixelator/drawer.cpp b/homeworks/homework_5/pixelator/pixelator/drawer.cpp new file mode 100644 index 0000000..ce563ad --- /dev/null +++ b/homeworks/homework_5/pixelator/pixelator/drawer.cpp @@ -0,0 +1,60 @@ +#include "pixelator/drawer.hpp" +#include +#include + +pixelator::Drawer::Drawer(const ftxui::Dimensions dimensions) { + if (dimensions.dimx == ftxui::Dimension::Full().dimx && + dimensions.dimy == ftxui::Dimension::Full().dimy) { + rows_ = ftxui::Dimension::Full().dimx; + cols_ = ftxui::Dimension::Full().dimy; + } else { + rows_ = dimensions.dimx; + cols_ = rows_ * 2; + } + screen_ = ftxui::Screen::Create({rows_, cols_}); +} + +int pixelator::Drawer::rows() const { return rows_; } + +int pixelator::Drawer::cols() const { return cols_; } + +const pixelator::Size pixelator::Drawer::size() const { + return pixelator::Size{rows_, cols_}; +} + +void pixelator::Drawer::Set(pixelator::Image image) { + for (int i = 0; i < image.rows(); i++) { + for (int j = 0; j < image.cols(); j++) { + auto &pixel_left = screen_.PixelAt(j * 2, i); + pixel_left.background_color = image.at(i, j); + pixel_left.character = ' '; + auto &pixel_right = screen_.PixelAt(j * 2 + 1, i); + pixel_right.background_color = image.at(i, j); + pixel_right.character = ' '; + } + } + + return; +} + +void pixelator::Drawer::Draw() const { + screen_.Print(); + return; +} + +std::string pixelator::Drawer::ToString() const { + std::string string_image = screen_.ToString(); + + // Remove new line characters from the end of the string + // TODO: I added removing of the new line characters here since I was getting + // testing errors in github but not on local machine. I did not perform + // extensive test when I added this. + while (!string_image.empty() && + (string_image[string_image.size() - 1] == '\r' || + string_image[string_image.size() - 1] == '\n' || + string_image[string_image.size() - 1] == ' ')) { + string_image.erase(string_image.size() - 1); + } + + return string_image; +} \ No newline at end of file diff --git a/homeworks/homework_5/pixelator/pixelator/drawer.hpp b/homeworks/homework_5/pixelator/pixelator/drawer.hpp new file mode 100644 index 0000000..ec7b8e8 --- /dev/null +++ b/homeworks/homework_5/pixelator/pixelator/drawer.hpp @@ -0,0 +1,22 @@ +#pragma once +#include "ftxui/screen/color.hpp" +#include "ftxui/screen/screen.hpp" +#include "pixelator/image.hpp" +namespace pixelator { +class Drawer { +private: + ftxui::Screen screen_{0, 0}; + int rows_{}; + int cols_{}; + +public: + Drawer() = default; + Drawer(const ftxui::Dimensions dimensions); + int rows() const; + int cols() const; + const Size size() const; + void Set(pixelator::Image image); + void Draw() const; + std::string ToString() const; +}; +} // namespace pixelator diff --git a/homeworks/homework_5/pixelator/pixelator/drawer_test.cpp b/homeworks/homework_5/pixelator/pixelator/drawer_test.cpp new file mode 100644 index 0000000..f539fef --- /dev/null +++ b/homeworks/homework_5/pixelator/pixelator/drawer_test.cpp @@ -0,0 +1,13 @@ +#include "pixelator/drawer.hpp" +#include + +TEST(drawer_tests, test2) { + pixelator::Drawer full_screen_drawer{ftxui::Dimension::Full()}; + pixelator::Drawer fixed_screen_drawer{ftxui::Dimension::Fixed(42)}; + + EXPECT_EQ(ftxui::Terminal::Size().dimx, full_screen_drawer.size().rows); + EXPECT_EQ(ftxui::Terminal::Size().dimy, full_screen_drawer.size().cols); + + EXPECT_EQ(42, fixed_screen_drawer.size().rows); + EXPECT_EQ(84, fixed_screen_drawer.size().cols); +} \ No newline at end of file diff --git a/homeworks/homework_5/pixelator/pixelator/image.cpp b/homeworks/homework_5/pixelator/pixelator/image.cpp new file mode 100644 index 0000000..96bc437 --- /dev/null +++ b/homeworks/homework_5/pixelator/pixelator/image.cpp @@ -0,0 +1,67 @@ +#include "pixelator/image.hpp" + +pixelator::Image::Image(pixelator::Size size) + : rows_{size.rows}, cols_{size.cols}{ + image_data_.resize(size.rows * size.cols * channels_, ftxui::Color{}); +}; + +pixelator::Image::Image(const int rows, const int cols) + : rows_{rows}, cols_{cols}{ + image_data_.resize(rows_ * cols_ * channels_, ftxui::Color{}); +}; + +pixelator::Image::Image(pixelator::Image &&other_image) + : rows_{other_image.rows_}, cols_{other_image.cols_}, + channels_{other_image.channels_}, image_data_{other_image.image_data_} { + other_image.image_data_.clear(); + other_image.rows_ = 0; + other_image.cols_ = 0; + other_image.channels_ = 0; +} + +pixelator::Image::Image(pixelator::Image &other_image) + : rows_{other_image.rows_}, cols_{other_image.cols_}, + channels_{other_image.channels_}, image_data_{other_image.image_data_} {} + +bool pixelator::Image::empty() const { + return image_data_.empty(); +} + +int pixelator::Image::rows() const { return rows_; } + +int pixelator::Image::cols() const { return cols_; } + +pixelator::Size pixelator::Image::size() { + return pixelator::Size{rows_, cols_}; +} + +const pixelator::Size pixelator::Image::size() const{ + return pixelator::Size{rows_, cols_}; +} + +ftxui::Color &pixelator::Image::at(int row, int col) { + return image_data_.at(channels_ * (row * cols_ + col)); +} + +const ftxui::Color &pixelator::Image::at(int row, int col) const { + return image_data_.at(channels_ * (row * cols_ + col)); +} + +pixelator::Image &pixelator::Image::operator=(pixelator::Image &&other_image) { + if (this == &other_image) { + return *this; + } + if (!image_data_.empty()) { + image_data_.clear(); + } + + image_data_ = other_image.image_data_; + rows_ = other_image.rows_; + cols_ = other_image.cols_; + channels_ = other_image.channels_; + other_image.rows_ = 0; + other_image.cols_ = 0; + other_image.channels_ = 0; + + return *this; +} \ No newline at end of file diff --git a/homeworks/homework_5/pixelator/pixelator/image.hpp b/homeworks/homework_5/pixelator/pixelator/image.hpp new file mode 100644 index 0000000..58a756f --- /dev/null +++ b/homeworks/homework_5/pixelator/pixelator/image.hpp @@ -0,0 +1,34 @@ +#pragma once +#include "ftxui/screen/color.hpp" +#include "pixelator/stb_image_data_view.hpp" +#include + +namespace pixelator { + +class Image { +private: + int rows_{}; + int cols_{}; + int channels_{3}; + std::vector image_data_{}; + +public: + Image() = default; + Image(const pixelator::Size size); + // Move constructor + Image(Image &&other_image); + // copy constructor + Image(Image &other_image); + Image(const int rows, const int cols); + bool empty() const; + int rows() const; + int cols() const; + pixelator::Size size(); + const pixelator::Size size() const; + const ftxui::Color &at(int row, int col) const; + ftxui::Color &at(int row, int col); + // Move assignment operator + Image &operator=(Image &&other_image); +}; + +} // namespace pixelator \ No newline at end of file diff --git a/homeworks/homework_5/pixelator/pixelator/image_test.cpp b/homeworks/homework_5/pixelator/pixelator/image_test.cpp new file mode 100644 index 0000000..88a829a --- /dev/null +++ b/homeworks/homework_5/pixelator/pixelator/image_test.cpp @@ -0,0 +1,40 @@ +#include "pixelator/image.hpp" +#include + +TEST(image_tests, test1) { + pixelator::Image empty_image{}; + + const auto rows{42}; + const auto cols{23}; + pixelator::Image image{rows, cols}; + + EXPECT_EQ(empty_image.rows(), 0); + EXPECT_EQ(empty_image.cols(), 0); + EXPECT_EQ(empty_image.empty(), true); + + EXPECT_EQ(image.rows(), 42); + EXPECT_EQ(image.cols(), 23); + EXPECT_EQ(image.empty(), false); + EXPECT_EQ(image.size().rows, 42); + EXPECT_EQ(image.size().cols, 23); +} + +TEST(image_tests, test2) { + pixelator::Image image{10, 10}; + const ftxui::Color yellowish{ftxui::Color::RGB(255, 200, 100)}; + image.at(4, 2) = yellowish; + const pixelator::Image image_copy{image}; + + EXPECT_EQ(image.at(4, 2), yellowish); + EXPECT_EQ(image_copy.at(4, 2), yellowish); +} + +TEST(image_tests, test3) { + pixelator::Image image{10, 10}; + const ftxui::Color yellowish{ftxui::Color::RGB(255, 200, 100)}; + image.at(4, 2) = yellowish; + + const pixelator::Image image_moved{std::move(image)}; + + EXPECT_EQ(image_moved.at(4, 2), yellowish); +} \ No newline at end of file diff --git a/homeworks/homework_5/pixelator/pixelator/pixelate_image.hpp b/homeworks/homework_5/pixelator/pixelator/pixelate_image.hpp new file mode 100644 index 0000000..002c32b --- /dev/null +++ b/homeworks/homework_5/pixelator/pixelator/pixelate_image.hpp @@ -0,0 +1,50 @@ +#pragma once +#include "pixelator/image.hpp" +#include "pixelator/stb_image_data_view.hpp" +#include +namespace pixelator { + +int Scale(int number, float factor) { + return static_cast(number * factor); +} + +pixelator::Image PixelateImage(pixelator::StbImageDataView &image_view, + pixelator::Size new_size) { + const auto factor_cols = + new_size.cols / static_cast(image_view.cols()); + const auto factor_rows = + new_size.rows / static_cast(image_view.rows()); + const auto smallest_factor = std::min(factor_cols, factor_rows); + + const auto new_rows = Scale(image_view.rows(), smallest_factor); + const auto new_cols = Scale(image_view.cols(), smallest_factor); + + pixelator::Image results(new_rows, new_cols); + + // New size is bigger than original size. Dont resize. Return the original + // image + // TODO: Check keeping the aspect retion carefully. there is a problem + if (new_size.rows > image_view.size().rows && + new_size.cols > image_view.size().cols) { + for (int i = 0; i < image_view.rows(); i++) { + for (int j = 0; j < image_view.cols(); j++) { + results.at(i, j) = image_view.at(i, j); + } + } + return results; + } + + for (int i = 0; i < new_rows; i++) { + for (int j = 0; j < new_cols; j++) { + // Calculate the corresponding coordinates in the original image + const int original_row = static_cast(i / smallest_factor); + const int original_col = static_cast(j / smallest_factor); + + // Set the color of the pixel in the pixelated image + results.at(i, j) = image_view.at(original_row, original_col); + } + } + + return results; +} +} // namespace pixelator \ No newline at end of file diff --git a/homeworks/homework_5/pixelator/pixelator/pixelate_image_test.cpp b/homeworks/homework_5/pixelator/pixelator/pixelate_image_test.cpp new file mode 100644 index 0000000..f761a9e --- /dev/null +++ b/homeworks/homework_5/pixelator/pixelator/pixelate_image_test.cpp @@ -0,0 +1,21 @@ +#include "pixelator/pixelate_image.hpp" +#include "pixelator/test_global_variables.hpp" +#include + +TEST(pixelator_tests, test1) { + pixelator::StbImageDataView image{pixelator_tests::pixelator_image_path}; + + // Perform pixelation + pixelator::Image pixelated_image = pixelator::PixelateImage( + image, pixelator::Size{pixelator_tests::pixelator_test_new_size, + pixelator_tests::pixelator_test_new_size}); + + EXPECT_EQ(pixelated_image.at(0, 0), ftxui::Color::RGB(0, 0, 0)); + EXPECT_EQ(pixelated_image.at(0, 1), ftxui::Color::RGB(0, 0, 0)); + + EXPECT_EQ(pixelated_image.at(1, 0), ftxui::Color::RGB(0, 0, 0)); + EXPECT_EQ(pixelated_image.at(1, 1), ftxui::Color::RGB(0, 0, 0)); + + EXPECT_EQ(pixelated_image.at(2, 0), ftxui::Color::RGB(255, 255, 255)); + EXPECT_EQ(pixelated_image.at(2, 1), ftxui::Color::RGB(255, 255, 255)); +} \ No newline at end of file diff --git a/homeworks/homework_5/pixelator/pixelator/stb_image_data_view.cpp b/homeworks/homework_5/pixelator/pixelator/stb_image_data_view.cpp new file mode 100644 index 0000000..6a3517f --- /dev/null +++ b/homeworks/homework_5/pixelator/pixelator/stb_image_data_view.cpp @@ -0,0 +1,69 @@ +#define STB_IMAGE_IMPLEMENTATION +#include "pixelator/stb_image_data_view.hpp" +#include "stb/stb_image.h" +#include + +pixelator::StbImageDataView::StbImageDataView(const std::string &image_path) { + image_data_ = stbi_load(image_path.c_str(), &cols_, &rows_, + &channels_, 0); + if (!image_data_) { + std::cerr << "Failed to load image data from file: " << image_path + << std::endl; + rows_ = 0; + cols_ = 0; + channels_ = 0; + } +}; + +pixelator::StbImageDataView::StbImageDataView( + pixelator::StbImageDataView &&other_image) + : rows_{other_image.rows_}, cols_{other_image.cols_}, channels_{other_image.channels_}, image_data_{other_image.image_data_} { + other_image.image_data_ = nullptr; + other_image.rows_ = 0; + other_image.cols_ = 0; + other_image.channels_ = 0; +} + +pixelator::Size pixelator::StbImageDataView::size() const { return pixelator::Size{rows_,cols_}; } + +bool pixelator::StbImageDataView::empty() const { + return (image_data_ == nullptr) ? true : false; +} + +int pixelator::StbImageDataView::rows() const { return rows_; } + +int pixelator::StbImageDataView::cols() const { return cols_; } + +ftxui::Color pixelator::StbImageDataView::at(const int &row, + const int &col) const { + const auto index{channels_ * (row * cols_ + col)}; + return ftxui::Color::RGB(image_data_[index], image_data_[index + 1], + image_data_[index + 2]); +} + +pixelator::StbImageDataView & +pixelator::StbImageDataView::operator=(StbImageDataView &&object) { + if (this == &object) { + return *this; + } + if (image_data_ != nullptr) { + stbi_image_free(image_data_); + } + image_data_ = object.image_data_; + rows_ = object.rows_; + cols_ = object.cols_; + channels_ = object.channels_; + + object.rows_ = 0; + object.cols_ = 0; + object.channels_ = 0; + object.image_data_ = nullptr; + + return *this; +} + +pixelator::StbImageDataView::~StbImageDataView() { + if (image_data_ != nullptr) { + stbi_image_free(image_data_); + } +} \ No newline at end of file diff --git a/homeworks/homework_5/pixelator/pixelator/stb_image_data_view.hpp b/homeworks/homework_5/pixelator/pixelator/stb_image_data_view.hpp new file mode 100644 index 0000000..bf2c734 --- /dev/null +++ b/homeworks/homework_5/pixelator/pixelator/stb_image_data_view.hpp @@ -0,0 +1,52 @@ +#pragma once +#include "ftxui/screen/color.hpp" +#include +#include + +namespace pixelator { +// A struct to store image size +struct Size { + int rows; + int cols; +}; + +// A struct to store colors +struct Color { + int red; + int green; + int blue; +}; + +class StbImageDataView { +private: + // Image size member function initialized with all values 0 + int rows_{}; + int cols_{}; + int channels_{}; + // Image data (pixel color values) + unsigned char *image_data_{nullptr}; + +public: + // Default constructor initialize member variables. + StbImageDataView() = default; + // constructor which gets an image path and loads the image if exists + StbImageDataView(const std::string &image_path); + // Move constructor + StbImageDataView(StbImageDataView &&other_image); + // Return image size + Size size() const; + // Return true if image data is null + bool empty() const; + // Return the number of rows of the image + int rows() const; + // Return the number of cols of the image + int cols() const; + // Return the color at a specific index + ftxui::Color at(const int &row, const int &col) const; + // Move assignment operator + StbImageDataView &operator=(StbImageDataView &&object); + // Destructor + ~StbImageDataView(); +}; + +}; // namespace pixelator \ No newline at end of file diff --git a/homeworks/homework_5/pixelator/pixelator/stb_image_data_view_test.cpp b/homeworks/homework_5/pixelator/pixelator/stb_image_data_view_test.cpp new file mode 100644 index 0000000..9174cc2 --- /dev/null +++ b/homeworks/homework_5/pixelator/pixelator/stb_image_data_view_test.cpp @@ -0,0 +1,34 @@ +#include "pixelator/stb_image_data_view.hpp" +#include "pixelator/test_global_variables.hpp" +#include +#include + +TEST(stb_image_data_view_tests, test1) { + pixelator::StbImageDataView image{pixelator_tests::image_path}; + pixelator::StbImageDataView empty_image{}; + const pixelator::Size size = image.size(); + ftxui::Color color = + image.at(pixelator_tests::test_pixel_x, pixelator_tests::test_pixel_y); + + EXPECT_EQ(size.rows, pixelator_tests::test_image_rows); + EXPECT_EQ(size.cols, pixelator_tests::test_image_cols); + EXPECT_EQ(image.empty(), false); + EXPECT_EQ(empty_image.empty(), true); + EXPECT_EQ(color, ftxui::Color::RGB(pixelator_tests::test_pixel_color.red, + pixelator_tests::test_pixel_color.green, + pixelator_tests::test_pixel_color.blue)); +} + +TEST(stb_image_data_view_tests, test2) { + pixelator::StbImageDataView image{pixelator_tests::image_path}; + pixelator::StbImageDataView image_copy = std::move(image); + ftxui::Color color = image_copy.at(pixelator_tests::test_pixel_x, + pixelator_tests::test_pixel_y); + + EXPECT_EQ(color, ftxui::Color::RGB(pixelator_tests::test_pixel_color.red, + pixelator_tests::test_pixel_color.green, + pixelator_tests::test_pixel_color.blue)); + EXPECT_EQ(image.rows(), 0); + EXPECT_EQ(image.cols(), 0); + EXPECT_EQ(image.empty(), true); +} \ No newline at end of file diff --git a/homeworks/homework_5/pixelator/pixelator/test_data/test.png b/homeworks/homework_5/pixelator/pixelator/test_data/test.png new file mode 100644 index 0000000..00c8682 Binary files /dev/null and b/homeworks/homework_5/pixelator/pixelator/test_data/test.png differ diff --git a/homeworks/homework_5/pixelator/pixelator/test_data/test_grumpy.png b/homeworks/homework_5/pixelator/pixelator/test_data/test_grumpy.png new file mode 100644 index 0000000..b770dc8 Binary files /dev/null and b/homeworks/homework_5/pixelator/pixelator/test_data/test_grumpy.png differ diff --git a/homeworks/homework_5/pixelator/pixelator/test_global_variables.hpp b/homeworks/homework_5/pixelator/pixelator/test_global_variables.hpp new file mode 100644 index 0000000..ce0040d --- /dev/null +++ b/homeworks/homework_5/pixelator/pixelator/test_global_variables.hpp @@ -0,0 +1,22 @@ + +#include "pixelator/image.hpp" + +namespace pixelator_tests { +const std::filesystem::path image_path{ + "../../pixelator/test_data/test_grumpy.png"}; +const std::filesystem::path pixelator_image_path{ + "../../pixelator/test_data/test.png"}; +const int pixelator_test_new_size = 3; + +// coordinates of test pixel in test image +const int test_pixel_x = 100; +const int test_pixel_y = 150; + +// Pixel coller of test image at position 100x15 +const pixelator::Color test_pixel_color{155, 140, 133}; + +// Resolution of the test image +const int test_image_rows = 268; +const int test_image_cols = 273; + +} // namespace pixelator_tests \ No newline at end of file