Skip to content

Commit

Permalink
Add text for homework 5 (#64)
Browse files Browse the repository at this point in the history
This homework is about pixelating images
  • Loading branch information
niosus authored Aug 15, 2023
1 parent 1148fc7 commit dccb887
Show file tree
Hide file tree
Showing 18 changed files with 708 additions and 0 deletions.
Binary file added homeworks/homework_5/grumpy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added homeworks/homework_5/grumpy_pixelated.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
438 changes: 438 additions & 0 deletions homeworks/homework_5/homework.md

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions homeworks/homework_5/pixelator/.clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
Language: Cpp
BasedOnStyle: Google
AllowShortBlocksOnASingleLine: true
AllowShortCaseLabelsOnASingleLine: true
AllowShortFunctionsOnASingleLine: All
AllowShortLoopsOnASingleLine: true
AllowShortIfStatementsOnASingleLine: true
BinPackArguments: false
BinPackParameters: false
IncludeBlocks: Preserve
SortIncludes: true
...
27 changes: 27 additions & 0 deletions homeworks/homework_5/pixelator/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
8 changes: 8 additions & 0 deletions homeworks/homework_5/pixelator/examples/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
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)

# TODO: Add your binaries here
# There must be a binary `pixelate` here
29 changes: 29 additions & 0 deletions homeworks/homework_5/pixelator/examples/pixelate.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#include "pixelator/drawer.hpp"
#include "pixelator/pixelate_image.hpp"
#include "pixelator/stb_image_data_view.hpp"

#include <cstddef>
#include <filesystem>
#include <iostream>
#include <utility>

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; }

const 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;
}
19 changes: 19 additions & 0 deletions homeworks/homework_5/pixelator/examples/use_ftxui.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
64 changes: 64 additions & 0 deletions homeworks/homework_5/pixelator/examples/use_stb_image.cpp
Original file line number Diff line number Diff line change
@@ -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 <filesystem>
#include <iostream>

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 = 3;
int query_col = 2;
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;
}
3 changes: 3 additions & 0 deletions homeworks/homework_5/pixelator/external/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
include(gtest.cmake)
include(ftxui.cmake)
include(stb.cmake)
27 changes: 27 additions & 0 deletions homeworks/homework_5/pixelator/external/UpdateSubmodules.cmake
Original file line number Diff line number Diff line change
@@ -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()
4 changes: 4 additions & 0 deletions homeworks/homework_5/pixelator/external/ftxui.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
add_subdirectory(ftxui)
5 changes: 5 additions & 0 deletions homeworks/homework_5/pixelator/external/gtest.cmake
Original file line number Diff line number Diff line change
@@ -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)
5 changes: 5 additions & 0 deletions homeworks/homework_5/pixelator/external/stb.cmake
Original file line number Diff line number Diff line change
@@ -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()
14 changes: 14 additions & 0 deletions homeworks/homework_5/pixelator/pixelator/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# TODO: Add your libraries here
# You must have the following libraries:
# - stb_image_data_view
# - drawer
# - image
# - pixelate_image

if(BUILD_TESTING)
include(GoogleTest)
# 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
endif()
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions homeworks/homework_5/script.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Video script
Hey everyone! In this video I want to introduce a project that we (and by "we" I mean "you") will implement to solidify everything that we've learned until this point.

Here is what it'll do. It will turn a normal picture, like this one, into its pixelated form, like that one.

And not only that, but it will also do all of this in the terminal!

<!-- Intro -->


Before we go into details, I hope that by now you know that you can find the full homework description below this video under a link to the course GitHub repository. I also provide an empty project skeleton with some boilerplate code for you to get started in an easier way.

Now, let's talk details, shall we?

In order to perform the pixelation we have to perform a number of actions.

1. First we have to load the image from disk. We do this using an external library `stb`, or, more concretely a couple of functions from its header `stb_image.h`. In order to use this library, you will have to add it as a submodule. We already talked about submodules before, but I also provide the necessary command in the homework description file I mentioned before.
2. Unfortunately, `stb` is a C library, so memory management is not its strongest side. That means we have to store the data that the `stb` library gave us in a safe way. How lucky for us that we learned about raw pointers and proper move semantics already! So we can implement a class, say `StbImageDataView` that will manage the memory allocated by the `stb` library for us! We should be able to move the objects of this class around and they should free the memory upon destruction. Again, I have an example to ease your way into this in the homework description.
3. Eventually, we will also need a way to manipulate the colors in an image, so we will implement a class `Image` that will allow us to directly access and manipulate the RGB pixels in it. We can represent the colors as a vector of `ftxui::Color` which comes from an awesome library FTXUI, which we will also have to add as a submodule to our code. Once again, I provide an example of how this library can be used in the project skeleton.
4. At this point we should be ready to implement the `PixelateImage` function that would take our `StbImageDataView`, pixelate it and return it as and `Image` instance. This is the most algorithmically intense part of the project but fear not, I'm sure you can manage!
5. Finally, once we have the pixelated image at hand, we would like to show it in the terminal. We will again use the FTXUI library for that. The way we can do it is we wrap the `ftxui::Screen` instance into a class `Drawer`, that takes an `Image` instance and can `Draw()` it to the terminal. And, you guessed it, you'll find an example to guide you through this in the project skeleton.

Oh, and of course we have to test all of this, so we add Googletest as a submodule to the project, write tests for all of the stuff I just talked about and test away!

And that's it! That's all the functionality that you have to implement here. Now, I know it sounds like a lot and, if I'm honest, it might be. But it is still a good project to take on in order to get comfortable with coding in C++.

Once again, all the needed details are in the repo linked in the description below.

And at this, I wish you good luck, have lots of fun implementing it and _do_ take your time on this project. It sure took me a long time to prepare everything. It probably will take you multiple hours (or maybe even days) but there are no shortcuts in learning! If something is really confusing, do start a discussion in the GitHub organization linked under the video.

Thanks again for watching and have fun implementing it all! And if you want a refresher on the move semantics or testing your code, do click on the thumbnails that have just appeared on your screen!
21 changes: 21 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,27 @@ Headers with classes

----------------------------------------------------------
</details>

<details>
<summary><code>Homework</code>: pixelate images in terminal</summary>

----------------------------------------------------------
[![Video thumbnail](https://img.youtube.com/vi/Cj3x51iJdvM/maxresdefault.jpg)](https://youtu.be/Cj3x51iJdvM)

[Homework script](homeworks/homework_5/homework.md)
- You will write a library that allows to pixelate an image
- You will learn how to:
- Work with classes
- Use external libraries
- Read images from disk using `stb_image.h`
- Draw stuff in the terminal using `FTXUI` library
- Manage memory allocated elsewhere correctly
- Writing multiple libraries and binaries and linking them together
- Manage a larger CMake project
----------------------------------------------------------
</details>


---

## PS
Expand Down

0 comments on commit dccb887

Please sign in to comment.