diff --git a/examples/bonus_04_inversion.cpp b/examples/bonus_04_inversion.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3eff3bc8823f753a44e26feb0952d56e22631a33 --- /dev/null +++ b/examples/bonus_04_inversion.cpp @@ -0,0 +1,338 @@ +#include <cstdio> +#include <cerrno> +#include <sstream> +#include <string> +#include <iostream> +#include <chrono> + +#include "simple/support/enum.hpp" +#include "simple/geom/algorithm.hpp" +#include "simple/graphical/initializer.h" +#include "simple/graphical/pixels.hpp" +#include "simple/graphical/software_window.h" +#include "simple/graphical/algorithm/blit.h" + +#define STB_IMAGE_IMPLEMENTATION +#include "external/stb_image.h" + +using namespace std::string_literals; +using namespace simple::graphical; +using namespace color_literals; + +using rgba_pixels = pixel_writer<rgba_pixel, pixel_byte>; +using simple::support::to_integer; + +float2 invert(float2 in, float2 center, float radiance) +{ + auto relative = in - center; + return center + relative * radiance/relative.quadrance(); +} + +const float tau = 2*std::acos(-1); + +surface stb_load_surface(const char* filename); + +enum class commands +{ + help, + invert, + mode, + blend, + radius, + center, + pan, + // TODO: + // zoom, + // size(window), + // view(current state), + // resolution?, + save, + commit, + reset, + timer, + invalid +}; +using command = simple::support::mapped_enum<commands, commands::invalid, 2>; +template <> command::guts::map_type command::guts::map +{{ + { "h"s, "help"s }, + { "i"s, "invert"s }, + { "m"s, "mode"s }, + { "b"s, "blend-toggle"s }, + { "r"s, "radius"s }, + { "c"s, "center"s }, + { "p"s, "pan"s }, + { "s"s, "save"s }, + { "cmt"s, "commit"s }, + { "reset"s, ""s }, + { "t"s, "timer-toggle"s }, +}}; + +enum class modes +{ + invert_floor, + invert_round, + invert_linear, + outvert_floor, + outvert_round, + outvert_linear, + invalid +}; +using mode = simple::support::mapped_enum<modes, modes::invalid, 2>; +template <> mode::guts::map_type mode::guts::map +{{ + { "if"s, "invert-floor"s }, + { "ir"s, "invert-round"s }, + { "il"s, "invert-linear"s }, + { "of"s, "outvert-floor"s }, + { "or"s, "outvert-round"s }, + { "ol"s, "outvert-linear"s }, +}}; + +constexpr auto inverter_func = std::array +{ + +[](rgba_pixels src, rgba_pixels dest, int2 from, float2 to) + { + const float2 size{dest.size()}; + if(float2::zero() <= to && to < size) + dest.set(src.get(from), int2(to)); + }, + +[](rgba_pixels src, rgba_pixels dest, int2 from, float2 to) + { + const float2 size{dest.size()}; + if(float2::zero() <= to && to < size) + dest.set(src.get(from), round(to)); + }, + +[](rgba_pixels src, rgba_pixels dest, int2 from, float2 to) + { + const float2 size{dest.size()}; + if(float2::one(-1) < to && to < size) + dest.set(src.get(from), to); + }, + +[](rgba_pixels src, rgba_pixels dest, int2 from, float2 to) + { + const float2 size{src.size()}; + if(float2::zero() <= to && to < size) + dest.set(src.get(int2(to)), from); + }, + +[](rgba_pixels src, rgba_pixels dest, int2 from, float2 to) + { + const float2 size{src.size()}; + if(float2::zero() <= to && to < size) + dest.set(src.get(round(to)), from); + }, + +[](rgba_pixels src, rgba_pixels dest, int2 from, float2 to) + { + const float2 size{src.size()}; + if(float2::one(-1) < to && to < size) + dest.set(src.get(to), from); + }, +}; + +int main(int argc, char** argv) try +{ + + if(argc < 2) + { + std::puts("Image not specified."); + return -1; + } + + initializer init; + + auto image = stb_load_surface(argv[1]); + std::cout << image.size() << '\n'; + auto pixels = std::get<rgba_pixels>(image.pixels()); + + float2 image_size(image.size()); + + software_window win("inversion", image.size(), window::flags::borderless); + + auto inverted_image = surface(image); + fill(inverted_image, inverted_image.format().color(0x0_rgba)); + auto inverted_pixels = std::get<rgba_pixels>(inverted_image.pixels()); + + auto center = image_size/2; + float radiance = (image_size.x() * image_size.y())/tau; + + std::string current_command; + int2 offset{}; + mode current_mode = modes::outvert_linear; + bool timer = false; + do + { + switch(command(current_command)) + { + case commands::help: + { + auto print = [](const auto& map) + { + for(auto&& i : map) + { + std::cout << '\t'; + for(auto&& j : i) + std::cout << j << ' '; + std::cout << '\n'; + } + }; + std::cout << "Commands:" << '\n'; + print(command::guts::map); + std::cout << "Modes:" << '\n'; + print(mode::guts::map); + } + break; + + case commands::invert: + { + using namespace std::chrono; + auto start = steady_clock::now(); + fill(inverted_image, inverted_image.format().color(0x0_rgba)); + if(inverted_image.blend() != blend_mode::none) + fill(win.surface(), win.surface().format().color(0x0_rgba)); + simple::geom::loop(int2::zero(), image.size(), int2::one(), + [&](auto i) + { + auto inverted = invert(float2(i + offset),center,radiance); + inverter_func[to_integer(current_mode)] + ( + pixels, inverted_pixels, + i, inverted + ); + }); + blit(inverted_image, win.surface()); + win.update(); + if(timer) + std::cout + << duration_cast<milliseconds>( + steady_clock::now() - start).count() + << "ms\n"; + } + break; + + case commands::mode: + { + std::string mode_str; + if(std::cin >> mode_str) + { + const auto new_mode = mode(mode_str); + if(new_mode != modes::invalid) + current_mode = new_mode; + else + std::cerr << "Invalid mode!" << '\n'; + } + else + { + std::cerr << "Invalid command!" << '\n'; + std::cin.clear(); + std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); + } + } + break; + + case commands::blend: + inverted_image.blend( + inverted_image.blend() != blend_mode::none + ? blend_mode::none + : blend_mode::alpha + ); + break; + + + case commands::radius: + { + float radius; + if(std::cin >> radius) + { + radiance = radius * radius; + } + else + { + std::cerr << "Invalid current_command!" << '\n'; + std::cin.clear(); + std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); + } + } + break; + + case commands::center: + if(!(std::cin >> center.x() >> center.y())) + { + std::cerr << "Invalid command!" << '\n'; + std::cin.clear(); + std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); + } + break; + + case commands::pan: + if(!(std::cin >> offset.x() >> offset.y())) + { + std::cerr << "Invalid command!" << '\n'; + std::cin.clear(); + std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); + } + break; + + case commands::save: + { + std::string savename; + if(std::cin >> savename) + inverted_image.save((savename + ".bmp").c_str()); + } + break; + + case commands::commit: + blit(inverted_image, image); + break; + + case commands::reset: + if(image.blend() != blend_mode::none) + fill(win.surface(), win.surface().format().color(0x0_rgba)); + blit(image, win.surface()); + win.update(); + break; + + case commands::timer: + timer = !timer; + break; + + case commands::invalid: + std::cerr << "Invalid command!" << '\n'; + std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); + break; + } + } + while(std::cin >> current_command); + + return 0; +} +catch(...) +{ + if(errno) + std::perror("ERROR"); + + const char* sdl_error = SDL_GetError(); + if(*sdl_error) + std::puts(sdl_error); + + throw; +} + +surface stb_load_surface(const char* filename) +{ + using namespace std::literals; + + int2 size; + int orig_format; + + auto data = stbi_load(filename, &size.x(), &size.y(), &orig_format, STBI_rgb_alpha); + + if(data == NULL) + throw std::runtime_error("Loading image failed: "s + stbi_failure_reason()); + + return + { + { data, [](surface::byte* data) { stbi_image_free(data); } }, + size, + pixel_format(pixel_format::type::rgba32) + }; +}